From 2accc9a95206c4f1ba0f93de9393678ee2e27060 Mon Sep 17 00:00:00 2001 From: Noah Metz Date: Wed, 9 Oct 2024 10:54:19 -0600 Subject: [PATCH] Initial commit of C vulkan code --- client/Makefile | 55 + client/include/render.h | 91 + client/include/vk_mem_alloc.h | 19109 ++++++++++++++++++++++++++++++++ client/main.go | 221 - client/src/main.c | 34 + client/src/main.o | Bin 0 -> 24944 bytes client/src/render.c | 1050 ++ client/src/render.o | Bin 0 -> 188296 bytes client/src/vma.cpp | 2 + client/src/vma.o | Bin 0 -> 915456 bytes client/src/vulkan.c | 0 11 files changed, 20341 insertions(+), 221 deletions(-) create mode 100644 client/Makefile create mode 100644 client/include/render.h create mode 100644 client/include/vk_mem_alloc.h delete mode 100644 client/main.go create mode 100644 client/src/main.c create mode 100644 client/src/main.o create mode 100644 client/src/render.c create mode 100644 client/src/render.o create mode 100644 client/src/vma.cpp create mode 100644 client/src/vma.o create mode 100644 client/src/vulkan.c diff --git a/client/Makefile b/client/Makefile new file mode 100644 index 0000000..51ac202 --- /dev/null +++ b/client/Makefile @@ -0,0 +1,55 @@ +ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) +CFLAGS = -I $(ROOT_DIR)/include -I/usr/local/include -O0 -g -Wall -Wextra +LDFLAGS = -L/opt/homebrew/opt/llvm/lib -L/opt/homebrew/opt/llvm/lib/c++ -L/opt/homebrew/lib -lglfw -lvulkan -ldl -Xlinker -rpath -Xlinker /opt/homebrew/lib +CC = /opt/homebrew/opt/llvm/bin/clang +CPP = /opt/homebrew/opt/llvm/bin/clang++ +DSYM = /opt/homebrew/opt/llvm/bin/dsymutil +GDB = /opt/homebrew/opt/llvm/bin/lldb + +SOURCES = src/main.c src/render.c src/vma.cpp +OBJECTS = $(addsuffix .o, $(basename $(SOURCES))) +VERT_SPV = $(addsuffix .vert.spv, $(basename $(wildcard shader_src/*.vert))) +FRAG_SPV = $(addsuffix .frag.spv, $(basename $(wildcard shader_src/*.frag))) + +export MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=1 + +.PHONY: all +all: roleplay $(VERT_SPV) $(FRAG_SPV) + +roleplay: $(OBJECTS) + $(CPP) $(CFLAGS) $(LDFLAGS) -o $@ $^ + +%.o: %.cpp + $(CPP) $(CFLAGS) -c -o $@ $< + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +.PHONY: clean clean_compdb + +clean: + rm -f $(FRAG_SPV) + rm -f $(VERT_SPV) + rm -f $(OBJECTS) + rm -f roleplay + rm -rf roleplay.dSYM + +clean_compdb: + rm -rf .compdb + rm compile_commands.json + +run: roleplay + ./roleplay + +roleplay.dSYM: roleplay + $(DSYM) roleplay + +debug: roleplay roleplay.dSYM + $(GDB) roleplay + + +%.vert.spv: %.vert + glslangValidator -V -o $@ $< + +%.frag.spv: %.frag + glslangValidator -V -o $@ $< diff --git a/client/include/render.h b/client/include/render.h new file mode 100644 index 0000000..edaedea --- /dev/null +++ b/client/include/render.h @@ -0,0 +1,91 @@ +#ifndef RENDER_H +#define RENDER_H + +#define VK_USE_PLATFORM_MACOS_MVK +#include "vulkan/vulkan_core.h" +#include "vulkan/vk_enum_string_helper.h" + +#include "vk_mem_alloc.h" + +#define GLFW_INCLUDE_VULKAN +#include +#define GLFW_EXPOSE_NATIVE_COCOA +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct QueueStruct { + VkQueue handle; + uint32_t family; + uint32_t index; +} Queue; + +typedef struct SwapchainDetailsStruct { + VkSurfaceCapabilitiesKHR capabilities; + + VkSurfaceFormatKHR* formats; + uint32_t formats_count; + + VkPresentModeKHR* present_modes; + uint32_t present_modes_count; +} SwapchainDetails; + +typedef struct RenderContextStruct { + VkInstance instance; + VkDebugUtilsMessengerEXT debug_messenger; + VkPhysicalDevice physical_device; + VkPhysicalDeviceMemoryProperties memories; + VkSurfaceKHR surface; + Queue graphics_queue; + Queue present_queue; + Queue transfer_queue; + VkDevice device; + VmaAllocator allocator; + + SwapchainDetails swapchain_details; + VkSurfaceFormatKHR swapchain_format; + VkPresentModeKHR swapchain_present_mode; + VkExtent2D swapchain_extent; + VkSwapchainKHR swapchain; + + uint32_t swapchain_image_count; + VkImage* swapchain_images; + VkImageView* swapchain_image_views; + VkFramebuffer* swapchain_framebuffers; + + VkFormat depth_format; + VkImageView depth_image_view; + VkImage depth_image; + VmaAllocation depth_image_memory; + + VkCommandPool extra_graphics_pool; + VkCommandPool graphics_pool; + VkCommandPool transfer_pool; + + VkRenderPass render_pass; + + VkCommandBuffer* swapchain_command_buffers; + + VkSemaphore* image_available_semaphores; + VkSemaphore* render_finished_semaphores; + + VkFence* in_flight_fences; + + VkPipeline ui_pipeline_rect; + VkPipeline ui_pipeline_text; +} RenderContext; + +GLFWwindow* init_window(); +VkResult init_vulkan(GLFWwindow* window, RenderContext* context); + +#endif diff --git a/client/include/vk_mem_alloc.h b/client/include/vk_mem_alloc.h new file mode 100644 index 0000000..47494fd --- /dev/null +++ b/client/include/vk_mem_alloc.h @@ -0,0 +1,19109 @@ +// +// Copyright (c) 2017-2024 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 AMD_VULKAN_MEMORY_ALLOCATOR_H +#define AMD_VULKAN_MEMORY_ALLOCATOR_H + +/** \mainpage Vulkan Memory Allocator + +Version 3.1.0 + +Copyright (c) 2017-2024 Advanced Micro Devices, Inc. All rights reserved. \n +License: MIT \n +See also: [product page on GPUOpen](https://gpuopen.com/gaming-product/vulkan-memory-allocator/), +[repository on GitHub](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator) + + +API documentation divided into groups: [Topics](topics.html) + +General documentation chapters: + +- User guide + - \subpage quick_start + - [Project setup](@ref quick_start_project_setup) + - [Initialization](@ref quick_start_initialization) + - [Resource allocation](@ref quick_start_resource_allocation) + - \subpage choosing_memory_type + - [Usage](@ref choosing_memory_type_usage) + - [Required and preferred flags](@ref choosing_memory_type_required_preferred_flags) + - [Explicit memory types](@ref choosing_memory_type_explicit_memory_types) + - [Custom memory pools](@ref choosing_memory_type_custom_memory_pools) + - [Dedicated allocations](@ref choosing_memory_type_dedicated_allocations) + - \subpage memory_mapping + - [Copy functions](@ref memory_mapping_copy_functions) + - [Mapping functions](@ref memory_mapping_mapping_functions) + - [Persistently mapped memory](@ref memory_mapping_persistently_mapped_memory) + - [Cache flush and invalidate](@ref memory_mapping_cache_control) + - \subpage staying_within_budget + - [Querying for budget](@ref staying_within_budget_querying_for_budget) + - [Controlling memory usage](@ref staying_within_budget_controlling_memory_usage) + - \subpage resource_aliasing + - \subpage custom_memory_pools + - [Choosing memory type index](@ref custom_memory_pools_MemTypeIndex) + - [When not to use custom pools](@ref custom_memory_pools_when_not_use) + - [Linear allocation algorithm](@ref linear_algorithm) + - [Free-at-once](@ref linear_algorithm_free_at_once) + - [Stack](@ref linear_algorithm_stack) + - [Double stack](@ref linear_algorithm_double_stack) + - [Ring buffer](@ref linear_algorithm_ring_buffer) + - \subpage defragmentation + - \subpage statistics + - [Numeric statistics](@ref statistics_numeric_statistics) + - [JSON dump](@ref statistics_json_dump) + - \subpage allocation_annotation + - [Allocation user data](@ref allocation_user_data) + - [Allocation names](@ref allocation_names) + - \subpage virtual_allocator + - \subpage debugging_memory_usage + - [Memory initialization](@ref debugging_memory_usage_initialization) + - [Margins](@ref debugging_memory_usage_margins) + - [Corruption detection](@ref debugging_memory_usage_corruption_detection) + - [Leak detection features](@ref debugging_memory_usage_leak_detection) + - \subpage other_api_interop +- \subpage usage_patterns + - [GPU-only resource](@ref usage_patterns_gpu_only) + - [Staging copy for upload](@ref usage_patterns_staging_copy_upload) + - [Readback](@ref usage_patterns_readback) + - [Advanced data uploading](@ref usage_patterns_advanced_data_uploading) + - [Other use cases](@ref usage_patterns_other_use_cases) +- \subpage configuration + - [Pointers to Vulkan functions](@ref config_Vulkan_functions) + - [Custom host memory allocator](@ref custom_memory_allocator) + - [Device memory allocation callbacks](@ref allocation_callbacks) + - [Device heap memory limit](@ref heap_memory_limit) +- Extension support + - \subpage vk_khr_dedicated_allocation + - \subpage enabling_buffer_device_address + - \subpage vk_ext_memory_priority + - \subpage vk_amd_device_coherent_memory + - \subpage vk_khr_external_memory_win32 +- \subpage general_considerations + - [Thread safety](@ref general_considerations_thread_safety) + - [Versioning and compatibility](@ref general_considerations_versioning_and_compatibility) + - [Validation layer warnings](@ref general_considerations_validation_layer_warnings) + - [Allocation algorithm](@ref general_considerations_allocation_algorithm) + - [Features not supported](@ref general_considerations_features_not_supported) + +\defgroup group_init Library initialization + +\brief API elements related to the initialization and management of the entire library, especially #VmaAllocator object. + +\defgroup group_alloc Memory allocation + +\brief API elements related to the allocation, deallocation, and management of Vulkan memory, buffers, images. +Most basic ones being: vmaCreateBuffer(), vmaCreateImage(). + +\defgroup group_virtual Virtual allocator + +\brief API elements related to the mechanism of \ref virtual_allocator - using the core allocation algorithm +for user-defined purpose without allocating any real GPU memory. + +\defgroup group_stats Statistics + +\brief API elements that query current status of the allocator, from memory usage, budget, to full dump of the internal state in JSON format. +See documentation chapter: \ref statistics. +*/ + + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(VULKAN_H_) +#include +#endif + +#if !defined(VMA_VULKAN_VERSION) + #if defined(VK_VERSION_1_3) + #define VMA_VULKAN_VERSION 1003000 + #elif defined(VK_VERSION_1_2) + #define VMA_VULKAN_VERSION 1002000 + #elif defined(VK_VERSION_1_1) + #define VMA_VULKAN_VERSION 1001000 + #else + #define VMA_VULKAN_VERSION 1000000 + #endif +#endif + +#if defined(__ANDROID__) && defined(VK_NO_PROTOTYPES) && VMA_STATIC_VULKAN_FUNCTIONS + extern PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + extern PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; + extern PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; + extern PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties; + extern PFN_vkAllocateMemory vkAllocateMemory; + extern PFN_vkFreeMemory vkFreeMemory; + extern PFN_vkMapMemory vkMapMemory; + extern PFN_vkUnmapMemory vkUnmapMemory; + extern PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges; + extern PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges; + extern PFN_vkBindBufferMemory vkBindBufferMemory; + extern PFN_vkBindImageMemory vkBindImageMemory; + extern PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements; + extern PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements; + extern PFN_vkCreateBuffer vkCreateBuffer; + extern PFN_vkDestroyBuffer vkDestroyBuffer; + extern PFN_vkCreateImage vkCreateImage; + extern PFN_vkDestroyImage vkDestroyImage; + extern PFN_vkCmdCopyBuffer vkCmdCopyBuffer; + #if VMA_VULKAN_VERSION >= 1001000 + extern PFN_vkGetBufferMemoryRequirements2 vkGetBufferMemoryRequirements2; + extern PFN_vkGetImageMemoryRequirements2 vkGetImageMemoryRequirements2; + extern PFN_vkBindBufferMemory2 vkBindBufferMemory2; + extern PFN_vkBindImageMemory2 vkBindImageMemory2; + extern PFN_vkGetPhysicalDeviceMemoryProperties2 vkGetPhysicalDeviceMemoryProperties2; + #endif // #if VMA_VULKAN_VERSION >= 1001000 +#endif // #if defined(__ANDROID__) && VMA_STATIC_VULKAN_FUNCTIONS && VK_NO_PROTOTYPES + +#if !defined(VMA_DEDICATED_ALLOCATION) + #if VK_KHR_get_memory_requirements2 && VK_KHR_dedicated_allocation + #define VMA_DEDICATED_ALLOCATION 1 + #else + #define VMA_DEDICATED_ALLOCATION 0 + #endif +#endif + +#if !defined(VMA_BIND_MEMORY2) + #if VK_KHR_bind_memory2 + #define VMA_BIND_MEMORY2 1 + #else + #define VMA_BIND_MEMORY2 0 + #endif +#endif + +#if !defined(VMA_MEMORY_BUDGET) + #if VK_EXT_memory_budget && (VK_KHR_get_physical_device_properties2 || VMA_VULKAN_VERSION >= 1001000) + #define VMA_MEMORY_BUDGET 1 + #else + #define VMA_MEMORY_BUDGET 0 + #endif +#endif + +// Defined to 1 when VK_KHR_buffer_device_address device extension or equivalent core Vulkan 1.2 feature is defined in its headers. +#if !defined(VMA_BUFFER_DEVICE_ADDRESS) + #if VK_KHR_buffer_device_address || VMA_VULKAN_VERSION >= 1002000 + #define VMA_BUFFER_DEVICE_ADDRESS 1 + #else + #define VMA_BUFFER_DEVICE_ADDRESS 0 + #endif +#endif + +// Defined to 1 when VK_EXT_memory_priority device extension is defined in Vulkan headers. +#if !defined(VMA_MEMORY_PRIORITY) + #if VK_EXT_memory_priority + #define VMA_MEMORY_PRIORITY 1 + #else + #define VMA_MEMORY_PRIORITY 0 + #endif +#endif + +// Defined to 1 when VK_KHR_maintenance4 device extension is defined in Vulkan headers. +#if !defined(VMA_KHR_MAINTENANCE4) + #if VK_KHR_maintenance4 + #define VMA_KHR_MAINTENANCE4 1 + #else + #define VMA_KHR_MAINTENANCE4 0 + #endif +#endif + +// Defined to 1 when VK_KHR_maintenance5 device extension is defined in Vulkan headers. +#if !defined(VMA_KHR_MAINTENANCE5) + #if VK_KHR_maintenance5 + #define VMA_KHR_MAINTENANCE5 1 + #else + #define VMA_KHR_MAINTENANCE5 0 + #endif +#endif + + +// Defined to 1 when VK_KHR_external_memory device extension is defined in Vulkan headers. +#if !defined(VMA_EXTERNAL_MEMORY) + #if VK_KHR_external_memory + #define VMA_EXTERNAL_MEMORY 1 + #else + #define VMA_EXTERNAL_MEMORY 0 + #endif +#endif + +// Defined to 1 when VK_KHR_external_memory_win32 device extension is defined in Vulkan headers. +#if !defined(VMA_EXTERNAL_MEMORY_WIN32) + #if VK_KHR_external_memory_win32 + #define VMA_EXTERNAL_MEMORY_WIN32 1 + #else + #define VMA_EXTERNAL_MEMORY_WIN32 0 + #endif +#endif + +// Define these macros to decorate all public functions with additional code, +// before and after returned type, appropriately. This may be useful for +// exporting the functions when compiling VMA as a separate library. Example: +// #define VMA_CALL_PRE __declspec(dllexport) +// #define VMA_CALL_POST __cdecl +#ifndef VMA_CALL_PRE + #define VMA_CALL_PRE +#endif +#ifndef VMA_CALL_POST + #define VMA_CALL_POST +#endif + +// Define this macro to decorate pNext pointers with an attribute specifying the Vulkan +// structure that will be extended via the pNext chain. +#ifndef VMA_EXTENDS_VK_STRUCT + #define VMA_EXTENDS_VK_STRUCT(vkStruct) +#endif + +// Define this macro to decorate pointers with an attribute specifying the +// length of the array they point to if they are not null. +// +// The length may be one of +// - The name of another parameter in the argument list where the pointer is declared +// - The name of another member in the struct where the pointer is declared +// - The name of a member of a struct type, meaning the value of that member in +// the context of the call. For example +// VMA_LEN_IF_NOT_NULL("VkPhysicalDeviceMemoryProperties::memoryHeapCount"), +// this means the number of memory heaps available in the device associated +// with the VmaAllocator being dealt with. +#ifndef VMA_LEN_IF_NOT_NULL + #define VMA_LEN_IF_NOT_NULL(len) +#endif + +// The VMA_NULLABLE macro is defined to be _Nullable when compiling with Clang. +// see: https://clang.llvm.org/docs/AttributeReference.html#nullable +#ifndef VMA_NULLABLE + #ifdef __clang__ + #define VMA_NULLABLE _Nullable + #else + #define VMA_NULLABLE + #endif +#endif + +// The VMA_NOT_NULL macro is defined to be _Nonnull when compiling with Clang. +// see: https://clang.llvm.org/docs/AttributeReference.html#nonnull +#ifndef VMA_NOT_NULL + #ifdef __clang__ + #define VMA_NOT_NULL _Nonnull + #else + #define VMA_NOT_NULL + #endif +#endif + +// If non-dispatchable handles are represented as pointers then we can give +// then nullability annotations +#ifndef VMA_NOT_NULL_NON_DISPATCHABLE + #if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) + #define VMA_NOT_NULL_NON_DISPATCHABLE VMA_NOT_NULL + #else + #define VMA_NOT_NULL_NON_DISPATCHABLE + #endif +#endif + +#ifndef VMA_NULLABLE_NON_DISPATCHABLE + #if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) + #define VMA_NULLABLE_NON_DISPATCHABLE VMA_NULLABLE + #else + #define VMA_NULLABLE_NON_DISPATCHABLE + #endif +#endif + +#ifndef VMA_STATS_STRING_ENABLED + #define VMA_STATS_STRING_ENABLED 1 +#endif + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// INTERFACE +// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +// Sections for managing code placement in file, only for development purposes e.g. for convenient folding inside an IDE. +#ifndef _VMA_ENUM_DECLARATIONS + +/** +\addtogroup group_init +@{ +*/ + +/// Flags for created #VmaAllocator. +typedef enum VmaAllocatorCreateFlagBits +{ + /** \brief Allocator and all objects created from it will not be synchronized internally, so you must guarantee they are used from only one thread at a time or synchronized externally by you. + + Using this flag may increase performance because internal mutexes are not used. + */ + VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT = 0x00000001, + /** \brief Enables usage of VK_KHR_dedicated_allocation extension. + + The flag works only if VmaAllocatorCreateInfo::vulkanApiVersion `== VK_API_VERSION_1_0`. + When it is `VK_API_VERSION_1_1`, the flag is ignored because the extension has been promoted to Vulkan 1.1. + + Using this extension will automatically allocate dedicated blocks of memory for + some buffers and images instead of suballocating place for them out of bigger + memory blocks (as if you explicitly used #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT + flag) when it is recommended by the driver. It may improve performance on some + GPUs. + + You may set this flag only if you found out that following device extensions are + supported, you enabled them while creating Vulkan device passed as + VmaAllocatorCreateInfo::device, and you want them to be used internally by this + library: + + - VK_KHR_get_memory_requirements2 (device extension) + - VK_KHR_dedicated_allocation (device extension) + + When this flag is set, you can experience following warnings reported by Vulkan + validation layer. You can ignore them. + + > vkBindBufferMemory(): Binding memory to buffer 0x2d but vkGetBufferMemoryRequirements() has not been called on that buffer. + */ + VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT = 0x00000002, + /** + Enables usage of VK_KHR_bind_memory2 extension. + + The flag works only if VmaAllocatorCreateInfo::vulkanApiVersion `== VK_API_VERSION_1_0`. + When it is `VK_API_VERSION_1_1`, the flag is ignored because the extension has been promoted to Vulkan 1.1. + + You may set this flag only if you found out that this device extension is supported, + you enabled it while creating Vulkan device passed as VmaAllocatorCreateInfo::device, + and you want it to be used internally by this library. + + The extension provides functions `vkBindBufferMemory2KHR` and `vkBindImageMemory2KHR`, + which allow to pass a chain of `pNext` structures while binding. + This flag is required if you use `pNext` parameter in vmaBindBufferMemory2() or vmaBindImageMemory2(). + */ + VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT = 0x00000004, + /** + Enables usage of VK_EXT_memory_budget extension. + + You may set this flag only if you found out that this device extension is supported, + you enabled it while creating Vulkan device passed as VmaAllocatorCreateInfo::device, + and you want it to be used internally by this library, along with another instance extension + VK_KHR_get_physical_device_properties2, which is required by it (or Vulkan 1.1, where this extension is promoted). + + The extension provides query for current memory usage and budget, which will probably + be more accurate than an estimation used by the library otherwise. + */ + VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT = 0x00000008, + /** + Enables usage of VK_AMD_device_coherent_memory extension. + + You may set this flag only if you: + + - found out that this device extension is supported and enabled it while creating Vulkan device passed as VmaAllocatorCreateInfo::device, + - checked that `VkPhysicalDeviceCoherentMemoryFeaturesAMD::deviceCoherentMemory` is true and set it while creating the Vulkan device, + - want it to be used internally by this library. + + The extension and accompanying device feature provide access to memory types with + `VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD` and `VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD` flags. + They are useful mostly for writing breadcrumb markers - a common method for debugging GPU crash/hang/TDR. + + When the extension is not enabled, such memory types are still enumerated, but their usage is illegal. + To protect from this error, if you don't create the allocator with this flag, it will refuse to allocate any memory or create a custom pool in such memory type, + returning `VK_ERROR_FEATURE_NOT_PRESENT`. + */ + VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT = 0x00000010, + /** + Enables usage of "buffer device address" feature, which allows you to use function + `vkGetBufferDeviceAddress*` to get raw GPU pointer to a buffer and pass it for usage inside a shader. + + You may set this flag only if you: + + 1. (For Vulkan version < 1.2) Found as available and enabled device extension + VK_KHR_buffer_device_address. + This extension is promoted to core Vulkan 1.2. + 2. Found as available and enabled device feature `VkPhysicalDeviceBufferDeviceAddressFeatures::bufferDeviceAddress`. + + When this flag is set, you can create buffers with `VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT` using VMA. + The library automatically adds `VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT` to + allocated memory blocks wherever it might be needed. + + For more information, see documentation chapter \ref enabling_buffer_device_address. + */ + VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT = 0x00000020, + /** + Enables usage of VK_EXT_memory_priority extension in the library. + + You may set this flag only if you found available and enabled this device extension, + along with `VkPhysicalDeviceMemoryPriorityFeaturesEXT::memoryPriority == VK_TRUE`, + while creating Vulkan device passed as VmaAllocatorCreateInfo::device. + + When this flag is used, VmaAllocationCreateInfo::priority and VmaPoolCreateInfo::priority + are used to set priorities of allocated Vulkan memory. Without it, these variables are ignored. + + A priority must be a floating-point value between 0 and 1, indicating the priority of the allocation relative to other memory allocations. + Larger values are higher priority. The granularity of the priorities is implementation-dependent. + It is automatically passed to every call to `vkAllocateMemory` done by the library using structure `VkMemoryPriorityAllocateInfoEXT`. + The value to be used for default priority is 0.5. + For more details, see the documentation of the VK_EXT_memory_priority extension. + */ + VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT = 0x00000040, + /** + Enables usage of VK_KHR_maintenance4 extension in the library. + + You may set this flag only if you found available and enabled this device extension, + while creating Vulkan device passed as VmaAllocatorCreateInfo::device. + */ + VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE4_BIT = 0x00000080, + /** + Enables usage of VK_KHR_maintenance5 extension in the library. + + You should set this flag if you found available and enabled this device extension, + while creating Vulkan device passed as VmaAllocatorCreateInfo::device. + */ + VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT = 0x00000100, + + /** + Enables usage of VK_KHR_external_memory_win32 extension in the library. + + You should set this flag if you found available and enabled this device extension, + while creating Vulkan device passed as VmaAllocatorCreateInfo::device. + For more information, see \ref vk_khr_external_memory_win32. + */ + VMA_ALLOCATOR_CREATE_KHR_EXTERNAL_MEMORY_WIN32_BIT = 0x00000200, + + VMA_ALLOCATOR_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VmaAllocatorCreateFlagBits; +/// See #VmaAllocatorCreateFlagBits. +typedef VkFlags VmaAllocatorCreateFlags; + +/** @} */ + +/** +\addtogroup group_alloc +@{ +*/ + +/// \brief Intended usage of the allocated memory. +typedef enum VmaMemoryUsage +{ + /** No intended memory usage specified. + Use other members of VmaAllocationCreateInfo to specify your requirements. + */ + VMA_MEMORY_USAGE_UNKNOWN = 0, + /** + \deprecated Obsolete, preserved for backward compatibility. + Prefers `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`. + */ + VMA_MEMORY_USAGE_GPU_ONLY = 1, + /** + \deprecated Obsolete, preserved for backward compatibility. + Guarantees `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` and `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT`. + */ + VMA_MEMORY_USAGE_CPU_ONLY = 2, + /** + \deprecated Obsolete, preserved for backward compatibility. + Guarantees `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`, prefers `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`. + */ + VMA_MEMORY_USAGE_CPU_TO_GPU = 3, + /** + \deprecated Obsolete, preserved for backward compatibility. + Guarantees `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`, prefers `VK_MEMORY_PROPERTY_HOST_CACHED_BIT`. + */ + VMA_MEMORY_USAGE_GPU_TO_CPU = 4, + /** + \deprecated Obsolete, preserved for backward compatibility. + Prefers not `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`. + */ + VMA_MEMORY_USAGE_CPU_COPY = 5, + /** + Lazily allocated GPU memory having `VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT`. + Exists mostly on mobile platforms. Using it on desktop PC or other GPUs with no such memory type present will fail the allocation. + + Usage: Memory for transient attachment images (color attachments, depth attachments etc.), created with `VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT`. + + Allocations with this usage are always created as dedicated - it implies #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. + */ + VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED = 6, + /** + Selects best memory type automatically. + This flag is recommended for most common use cases. + + When using this flag, if you want to map the allocation (using vmaMapMemory() or #VMA_ALLOCATION_CREATE_MAPPED_BIT), + you must pass one of the flags: #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT + in VmaAllocationCreateInfo::flags. + + It can be used only with functions that let the library know `VkBufferCreateInfo` or `VkImageCreateInfo`, e.g. + vmaCreateBuffer(), vmaCreateImage(), vmaFindMemoryTypeIndexForBufferInfo(), vmaFindMemoryTypeIndexForImageInfo() + and not with generic memory allocation functions. + */ + VMA_MEMORY_USAGE_AUTO = 7, + /** + Selects best memory type automatically with preference for GPU (device) memory. + + When using this flag, if you want to map the allocation (using vmaMapMemory() or #VMA_ALLOCATION_CREATE_MAPPED_BIT), + you must pass one of the flags: #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT + in VmaAllocationCreateInfo::flags. + + It can be used only with functions that let the library know `VkBufferCreateInfo` or `VkImageCreateInfo`, e.g. + vmaCreateBuffer(), vmaCreateImage(), vmaFindMemoryTypeIndexForBufferInfo(), vmaFindMemoryTypeIndexForImageInfo() + and not with generic memory allocation functions. + */ + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE = 8, + /** + Selects best memory type automatically with preference for CPU (host) memory. + + When using this flag, if you want to map the allocation (using vmaMapMemory() or #VMA_ALLOCATION_CREATE_MAPPED_BIT), + you must pass one of the flags: #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT + in VmaAllocationCreateInfo::flags. + + It can be used only with functions that let the library know `VkBufferCreateInfo` or `VkImageCreateInfo`, e.g. + vmaCreateBuffer(), vmaCreateImage(), vmaFindMemoryTypeIndexForBufferInfo(), vmaFindMemoryTypeIndexForImageInfo() + and not with generic memory allocation functions. + */ + VMA_MEMORY_USAGE_AUTO_PREFER_HOST = 9, + + VMA_MEMORY_USAGE_MAX_ENUM = 0x7FFFFFFF +} VmaMemoryUsage; + +/// Flags to be passed as VmaAllocationCreateInfo::flags. +typedef enum VmaAllocationCreateFlagBits +{ + /** \brief Set this flag if the allocation should have its own memory block. + + Use it for special, big resources, like fullscreen images used as attachments. + + If you use this flag while creating a buffer or an image, `VkMemoryDedicatedAllocateInfo` + structure is applied if possible. + */ + VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT = 0x00000001, + + /** \brief Set this flag to only try to allocate from existing `VkDeviceMemory` blocks and never create new such block. + + If new allocation cannot be placed in any of the existing blocks, allocation + fails with `VK_ERROR_OUT_OF_DEVICE_MEMORY` error. + + You should not use #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT and + #VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT at the same time. It makes no sense. + */ + VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT = 0x00000002, + /** \brief Set this flag to use a memory that will be persistently mapped and retrieve pointer to it. + + Pointer to mapped memory will be returned through VmaAllocationInfo::pMappedData. + + It is valid to use this flag for allocation made from memory type that is not + `HOST_VISIBLE`. This flag is then ignored and memory is not mapped. This is + useful if you need an allocation that is efficient to use on GPU + (`DEVICE_LOCAL`) and still want to map it directly if possible on platforms that + support it (e.g. Intel GPU). + */ + VMA_ALLOCATION_CREATE_MAPPED_BIT = 0x00000004, + /** \deprecated Preserved for backward compatibility. Consider using vmaSetAllocationName() instead. + + Set this flag to treat VmaAllocationCreateInfo::pUserData as pointer to a + null-terminated string. Instead of copying pointer value, a local copy of the + string is made and stored in allocation's `pName`. The string is automatically + freed together with the allocation. It is also used in vmaBuildStatsString(). + */ + VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT = 0x00000020, + /** Allocation will be created from upper stack in a double stack pool. + + This flag is only allowed for custom pools created with #VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT flag. + */ + VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT = 0x00000040, + /** Create both buffer/image and allocation, but don't bind them together. + It is useful when you want to bind yourself to do some more advanced binding, e.g. using some extensions. + The flag is meaningful only with functions that bind by default: vmaCreateBuffer(), vmaCreateImage(). + Otherwise it is ignored. + + If you want to make sure the new buffer/image is not tied to the new memory allocation + through `VkMemoryDedicatedAllocateInfoKHR` structure in case the allocation ends up in its own memory block, + use also flag #VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT. + */ + VMA_ALLOCATION_CREATE_DONT_BIND_BIT = 0x00000080, + /** Create allocation only if additional device memory required for it, if any, won't exceed + memory budget. Otherwise return `VK_ERROR_OUT_OF_DEVICE_MEMORY`. + */ + VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT = 0x00000100, + /** \brief Set this flag if the allocated memory will have aliasing resources. + + Usage of this flag prevents supplying `VkMemoryDedicatedAllocateInfoKHR` when #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT is specified. + Otherwise created dedicated memory will not be suitable for aliasing resources, resulting in Vulkan Validation Layer errors. + */ + VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT = 0x00000200, + /** + Requests possibility to map the allocation (using vmaMapMemory() or #VMA_ALLOCATION_CREATE_MAPPED_BIT). + + - If you use #VMA_MEMORY_USAGE_AUTO or other `VMA_MEMORY_USAGE_AUTO*` value, + you must use this flag to be able to map the allocation. Otherwise, mapping is incorrect. + - If you use other value of #VmaMemoryUsage, this flag is ignored and mapping is always possible in memory types that are `HOST_VISIBLE`. + This includes allocations created in \ref custom_memory_pools. + + Declares that mapped memory will only be written sequentially, e.g. using `memcpy()` or a loop writing number-by-number, + never read or accessed randomly, so a memory type can be selected that is uncached and write-combined. + + \warning Violating this declaration may work correctly, but will likely be very slow. + Watch out for implicit reads introduced by doing e.g. `pMappedData[i] += x;` + Better prepare your data in a local variable and `memcpy()` it to the mapped pointer all at once. + */ + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT = 0x00000400, + /** + Requests possibility to map the allocation (using vmaMapMemory() or #VMA_ALLOCATION_CREATE_MAPPED_BIT). + + - If you use #VMA_MEMORY_USAGE_AUTO or other `VMA_MEMORY_USAGE_AUTO*` value, + you must use this flag to be able to map the allocation. Otherwise, mapping is incorrect. + - If you use other value of #VmaMemoryUsage, this flag is ignored and mapping is always possible in memory types that are `HOST_VISIBLE`. + This includes allocations created in \ref custom_memory_pools. + + Declares that mapped memory can be read, written, and accessed in random order, + so a `HOST_CACHED` memory type is preferred. + */ + VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT = 0x00000800, + /** + Together with #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT, + it says that despite request for host access, a not-`HOST_VISIBLE` memory type can be selected + if it may improve performance. + + By using this flag, you declare that you will check if the allocation ended up in a `HOST_VISIBLE` memory type + (e.g. using vmaGetAllocationMemoryProperties()) and if not, you will create some "staging" buffer and + issue an explicit transfer to write/read your data. + To prepare for this possibility, don't forget to add appropriate flags like + `VK_BUFFER_USAGE_TRANSFER_DST_BIT`, `VK_BUFFER_USAGE_TRANSFER_SRC_BIT` to the parameters of created buffer or image. + */ + VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT = 0x00001000, + /** Allocation strategy that chooses smallest possible free range for the allocation + to minimize memory usage and fragmentation, possibly at the expense of allocation time. + */ + VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT = 0x00010000, + /** Allocation strategy that chooses first suitable free range for the allocation - + not necessarily in terms of the smallest offset but the one that is easiest and fastest to find + to minimize allocation time, possibly at the expense of allocation quality. + */ + VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT = 0x00020000, + /** Allocation strategy that chooses always the lowest offset in available space. + This is not the most efficient strategy but achieves highly packed data. + Used internally by defragmentation, not recommended in typical usage. + */ + VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT = 0x00040000, + /** Alias to #VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT. + */ + VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT = VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT, + /** Alias to #VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT. + */ + VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT, + /** A bit mask to extract only `STRATEGY` bits from entire set of flags. + */ + VMA_ALLOCATION_CREATE_STRATEGY_MASK = + VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT | + VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT | + VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT, + + VMA_ALLOCATION_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VmaAllocationCreateFlagBits; +/// See #VmaAllocationCreateFlagBits. +typedef VkFlags VmaAllocationCreateFlags; + +/// Flags to be passed as VmaPoolCreateInfo::flags. +typedef enum VmaPoolCreateFlagBits +{ + /** \brief Use this flag if you always allocate only buffers and linear images or only optimal images out of this pool and so Buffer-Image Granularity can be ignored. + + This is an optional optimization flag. + + If you always allocate using vmaCreateBuffer(), vmaCreateImage(), + vmaAllocateMemoryForBuffer(), then you don't need to use it because allocator + knows exact type of your allocations so it can handle Buffer-Image Granularity + in the optimal way. + + If you also allocate using vmaAllocateMemoryForImage() or vmaAllocateMemory(), + exact type of such allocations is not known, so allocator must be conservative + in handling Buffer-Image Granularity, which can lead to suboptimal allocation + (wasted memory). In that case, if you can make sure you always allocate only + buffers and linear images or only optimal images out of this pool, use this flag + to make allocator disregard Buffer-Image Granularity and so make allocations + faster and more optimal. + */ + VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT = 0x00000002, + + /** \brief Enables alternative, linear allocation algorithm in this pool. + + Specify this flag to enable linear allocation algorithm, which always creates + new allocations after last one and doesn't reuse space from allocations freed in + between. It trades memory consumption for simplified algorithm and data + structure, which has better performance and uses less memory for metadata. + + By using this flag, you can achieve behavior of free-at-once, stack, + ring buffer, and double stack. + For details, see documentation chapter \ref linear_algorithm. + */ + VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT = 0x00000004, + + /** Bit mask to extract only `ALGORITHM` bits from entire set of flags. + */ + VMA_POOL_CREATE_ALGORITHM_MASK = + VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT, + + VMA_POOL_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VmaPoolCreateFlagBits; +/// Flags to be passed as VmaPoolCreateInfo::flags. See #VmaPoolCreateFlagBits. +typedef VkFlags VmaPoolCreateFlags; + +/// Flags to be passed as VmaDefragmentationInfo::flags. +typedef enum VmaDefragmentationFlagBits +{ + /* \brief Use simple but fast algorithm for defragmentation. + May not achieve best results but will require least time to compute and least allocations to copy. + */ + VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT = 0x1, + /* \brief Default defragmentation algorithm, applied also when no `ALGORITHM` flag is specified. + Offers a balance between defragmentation quality and the amount of allocations and bytes that need to be moved. + */ + VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT = 0x2, + /* \brief Perform full defragmentation of memory. + Can result in notably more time to compute and allocations to copy, but will achieve best memory packing. + */ + VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT = 0x4, + /** \brief Use the most roboust algorithm at the cost of time to compute and number of copies to make. + Only available when bufferImageGranularity is greater than 1, since it aims to reduce + alignment issues between different types of resources. + Otherwise falls back to same behavior as #VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT. + */ + VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT = 0x8, + + /// A bit mask to extract only `ALGORITHM` bits from entire set of flags. + VMA_DEFRAGMENTATION_FLAG_ALGORITHM_MASK = + VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT | + VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT | + VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT | + VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT, + + VMA_DEFRAGMENTATION_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VmaDefragmentationFlagBits; +/// See #VmaDefragmentationFlagBits. +typedef VkFlags VmaDefragmentationFlags; + +/// Operation performed on single defragmentation move. See structure #VmaDefragmentationMove. +typedef enum VmaDefragmentationMoveOperation +{ + /// Buffer/image has been recreated at `dstTmpAllocation`, data has been copied, old buffer/image has been destroyed. `srcAllocation` should be changed to point to the new place. This is the default value set by vmaBeginDefragmentationPass(). + VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY = 0, + /// Set this value if you cannot move the allocation. New place reserved at `dstTmpAllocation` will be freed. `srcAllocation` will remain unchanged. + VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE = 1, + /// Set this value if you decide to abandon the allocation and you destroyed the buffer/image. New place reserved at `dstTmpAllocation` will be freed, along with `srcAllocation`, which will be destroyed. + VMA_DEFRAGMENTATION_MOVE_OPERATION_DESTROY = 2, +} VmaDefragmentationMoveOperation; + +/** @} */ + +/** +\addtogroup group_virtual +@{ +*/ + +/// Flags to be passed as VmaVirtualBlockCreateInfo::flags. +typedef enum VmaVirtualBlockCreateFlagBits +{ + /** \brief Enables alternative, linear allocation algorithm in this virtual block. + + Specify this flag to enable linear allocation algorithm, which always creates + new allocations after last one and doesn't reuse space from allocations freed in + between. It trades memory consumption for simplified algorithm and data + structure, which has better performance and uses less memory for metadata. + + By using this flag, you can achieve behavior of free-at-once, stack, + ring buffer, and double stack. + For details, see documentation chapter \ref linear_algorithm. + */ + VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT = 0x00000001, + + /** \brief Bit mask to extract only `ALGORITHM` bits from entire set of flags. + */ + VMA_VIRTUAL_BLOCK_CREATE_ALGORITHM_MASK = + VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT, + + VMA_VIRTUAL_BLOCK_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VmaVirtualBlockCreateFlagBits; +/// Flags to be passed as VmaVirtualBlockCreateInfo::flags. See #VmaVirtualBlockCreateFlagBits. +typedef VkFlags VmaVirtualBlockCreateFlags; + +/// Flags to be passed as VmaVirtualAllocationCreateInfo::flags. +typedef enum VmaVirtualAllocationCreateFlagBits +{ + /** \brief Allocation will be created from upper stack in a double stack pool. + + This flag is only allowed for virtual blocks created with #VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT flag. + */ + VMA_VIRTUAL_ALLOCATION_CREATE_UPPER_ADDRESS_BIT = VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT, + /** \brief Allocation strategy that tries to minimize memory usage. + */ + VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT = VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT, + /** \brief Allocation strategy that tries to minimize allocation time. + */ + VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT, + /** Allocation strategy that chooses always the lowest offset in available space. + This is not the most efficient strategy but achieves highly packed data. + */ + VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT = VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT, + /** \brief A bit mask to extract only `STRATEGY` bits from entire set of flags. + + These strategy flags are binary compatible with equivalent flags in #VmaAllocationCreateFlagBits. + */ + VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MASK = VMA_ALLOCATION_CREATE_STRATEGY_MASK, + + VMA_VIRTUAL_ALLOCATION_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF +} VmaVirtualAllocationCreateFlagBits; +/// Flags to be passed as VmaVirtualAllocationCreateInfo::flags. See #VmaVirtualAllocationCreateFlagBits. +typedef VkFlags VmaVirtualAllocationCreateFlags; + +/** @} */ + +#endif // _VMA_ENUM_DECLARATIONS + +#ifndef _VMA_DATA_TYPES_DECLARATIONS + +/** +\addtogroup group_init +@{ */ + +/** \struct VmaAllocator +\brief Represents main object of this library initialized. + +Fill structure #VmaAllocatorCreateInfo and call function vmaCreateAllocator() to create it. +Call function vmaDestroyAllocator() to destroy it. + +It is recommended to create just one object of this type per `VkDevice` object, +right after Vulkan is initialized and keep it alive until before Vulkan device is destroyed. +*/ +VK_DEFINE_HANDLE(VmaAllocator) + +/** @} */ + +/** +\addtogroup group_alloc +@{ +*/ + +/** \struct VmaPool +\brief Represents custom memory pool + +Fill structure VmaPoolCreateInfo and call function vmaCreatePool() to create it. +Call function vmaDestroyPool() to destroy it. + +For more information see [Custom memory pools](@ref choosing_memory_type_custom_memory_pools). +*/ +VK_DEFINE_HANDLE(VmaPool) + +/** \struct VmaAllocation +\brief Represents single memory allocation. + +It may be either dedicated block of `VkDeviceMemory` or a specific region of a bigger block of this type +plus unique offset. + +There are multiple ways to create such object. +You need to fill structure VmaAllocationCreateInfo. +For more information see [Choosing memory type](@ref choosing_memory_type). + +Although the library provides convenience functions that create Vulkan buffer or image, +allocate memory for it and bind them together, +binding of the allocation to a buffer or an image is out of scope of the allocation itself. +Allocation object can exist without buffer/image bound, +binding can be done manually by the user, and destruction of it can be done +independently of destruction of the allocation. + +The object also remembers its size and some other information. +To retrieve this information, use function vmaGetAllocationInfo() and inspect +returned structure VmaAllocationInfo. +*/ +VK_DEFINE_HANDLE(VmaAllocation) + +/** \struct VmaDefragmentationContext +\brief An opaque object that represents started defragmentation process. + +Fill structure #VmaDefragmentationInfo and call function vmaBeginDefragmentation() to create it. +Call function vmaEndDefragmentation() to destroy it. +*/ +VK_DEFINE_HANDLE(VmaDefragmentationContext) + +/** @} */ + +/** +\addtogroup group_virtual +@{ +*/ + +/** \struct VmaVirtualAllocation +\brief Represents single memory allocation done inside VmaVirtualBlock. + +Use it as a unique identifier to virtual allocation within the single block. + +Use value `VK_NULL_HANDLE` to represent a null/invalid allocation. +*/ +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VmaVirtualAllocation) + +/** @} */ + +/** +\addtogroup group_virtual +@{ +*/ + +/** \struct VmaVirtualBlock +\brief Handle to a virtual block object that allows to use core allocation algorithm without allocating any real GPU memory. + +Fill in #VmaVirtualBlockCreateInfo structure and use vmaCreateVirtualBlock() to create it. Use vmaDestroyVirtualBlock() to destroy it. +For more information, see documentation chapter \ref virtual_allocator. + +This object is not thread-safe - should not be used from multiple threads simultaneously, must be synchronized externally. +*/ +VK_DEFINE_HANDLE(VmaVirtualBlock) + +/** @} */ + +/** +\addtogroup group_init +@{ +*/ + +/// Callback function called after successful vkAllocateMemory. +typedef void (VKAPI_PTR* PFN_vmaAllocateDeviceMemoryFunction)( + VmaAllocator VMA_NOT_NULL allocator, + uint32_t memoryType, + VkDeviceMemory VMA_NOT_NULL_NON_DISPATCHABLE memory, + VkDeviceSize size, + void* VMA_NULLABLE pUserData); + +/// Callback function called before vkFreeMemory. +typedef void (VKAPI_PTR* PFN_vmaFreeDeviceMemoryFunction)( + VmaAllocator VMA_NOT_NULL allocator, + uint32_t memoryType, + VkDeviceMemory VMA_NOT_NULL_NON_DISPATCHABLE memory, + VkDeviceSize size, + void* VMA_NULLABLE pUserData); + +/** \brief Set of callbacks that the library will call for `vkAllocateMemory` and `vkFreeMemory`. + +Provided for informative purpose, e.g. to gather statistics about number of +allocations or total amount of memory allocated in Vulkan. + +Used in VmaAllocatorCreateInfo::pDeviceMemoryCallbacks. +*/ +typedef struct VmaDeviceMemoryCallbacks +{ + /// Optional, can be null. + PFN_vmaAllocateDeviceMemoryFunction VMA_NULLABLE pfnAllocate; + /// Optional, can be null. + PFN_vmaFreeDeviceMemoryFunction VMA_NULLABLE pfnFree; + /// Optional, can be null. + void* VMA_NULLABLE pUserData; +} VmaDeviceMemoryCallbacks; + +/** \brief Pointers to some Vulkan functions - a subset used by the library. + +Used in VmaAllocatorCreateInfo::pVulkanFunctions. +*/ +typedef struct VmaVulkanFunctions +{ + /// Required when using VMA_DYNAMIC_VULKAN_FUNCTIONS. + PFN_vkGetInstanceProcAddr VMA_NULLABLE vkGetInstanceProcAddr; + /// Required when using VMA_DYNAMIC_VULKAN_FUNCTIONS. + PFN_vkGetDeviceProcAddr VMA_NULLABLE vkGetDeviceProcAddr; + PFN_vkGetPhysicalDeviceProperties VMA_NULLABLE vkGetPhysicalDeviceProperties; + PFN_vkGetPhysicalDeviceMemoryProperties VMA_NULLABLE vkGetPhysicalDeviceMemoryProperties; + PFN_vkAllocateMemory VMA_NULLABLE vkAllocateMemory; + PFN_vkFreeMemory VMA_NULLABLE vkFreeMemory; + PFN_vkMapMemory VMA_NULLABLE vkMapMemory; + PFN_vkUnmapMemory VMA_NULLABLE vkUnmapMemory; + PFN_vkFlushMappedMemoryRanges VMA_NULLABLE vkFlushMappedMemoryRanges; + PFN_vkInvalidateMappedMemoryRanges VMA_NULLABLE vkInvalidateMappedMemoryRanges; + PFN_vkBindBufferMemory VMA_NULLABLE vkBindBufferMemory; + PFN_vkBindImageMemory VMA_NULLABLE vkBindImageMemory; + PFN_vkGetBufferMemoryRequirements VMA_NULLABLE vkGetBufferMemoryRequirements; + PFN_vkGetImageMemoryRequirements VMA_NULLABLE vkGetImageMemoryRequirements; + PFN_vkCreateBuffer VMA_NULLABLE vkCreateBuffer; + PFN_vkDestroyBuffer VMA_NULLABLE vkDestroyBuffer; + PFN_vkCreateImage VMA_NULLABLE vkCreateImage; + PFN_vkDestroyImage VMA_NULLABLE vkDestroyImage; + PFN_vkCmdCopyBuffer VMA_NULLABLE vkCmdCopyBuffer; +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + /// Fetch "vkGetBufferMemoryRequirements2" on Vulkan >= 1.1, fetch "vkGetBufferMemoryRequirements2KHR" when using VK_KHR_dedicated_allocation extension. + PFN_vkGetBufferMemoryRequirements2KHR VMA_NULLABLE vkGetBufferMemoryRequirements2KHR; + /// Fetch "vkGetImageMemoryRequirements2" on Vulkan >= 1.1, fetch "vkGetImageMemoryRequirements2KHR" when using VK_KHR_dedicated_allocation extension. + PFN_vkGetImageMemoryRequirements2KHR VMA_NULLABLE vkGetImageMemoryRequirements2KHR; +#endif +#if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000 + /// Fetch "vkBindBufferMemory2" on Vulkan >= 1.1, fetch "vkBindBufferMemory2KHR" when using VK_KHR_bind_memory2 extension. + PFN_vkBindBufferMemory2KHR VMA_NULLABLE vkBindBufferMemory2KHR; + /// Fetch "vkBindImageMemory2" on Vulkan >= 1.1, fetch "vkBindImageMemory2KHR" when using VK_KHR_bind_memory2 extension. + PFN_vkBindImageMemory2KHR VMA_NULLABLE vkBindImageMemory2KHR; +#endif +#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 + /// Fetch from "vkGetPhysicalDeviceMemoryProperties2" on Vulkan >= 1.1, but you can also fetch it from "vkGetPhysicalDeviceMemoryProperties2KHR" if you enabled extension VK_KHR_get_physical_device_properties2. + PFN_vkGetPhysicalDeviceMemoryProperties2KHR VMA_NULLABLE vkGetPhysicalDeviceMemoryProperties2KHR; +#endif +#if VMA_KHR_MAINTENANCE4 || VMA_VULKAN_VERSION >= 1003000 + /// Fetch from "vkGetDeviceBufferMemoryRequirements" on Vulkan >= 1.3, but you can also fetch it from "vkGetDeviceBufferMemoryRequirementsKHR" if you enabled extension VK_KHR_maintenance4. + PFN_vkGetDeviceBufferMemoryRequirementsKHR VMA_NULLABLE vkGetDeviceBufferMemoryRequirements; + /// Fetch from "vkGetDeviceImageMemoryRequirements" on Vulkan >= 1.3, but you can also fetch it from "vkGetDeviceImageMemoryRequirementsKHR" if you enabled extension VK_KHR_maintenance4. + PFN_vkGetDeviceImageMemoryRequirementsKHR VMA_NULLABLE vkGetDeviceImageMemoryRequirements; +#endif +#if VMA_EXTERNAL_MEMORY_WIN32 + PFN_vkGetMemoryWin32HandleKHR VMA_NULLABLE vkGetMemoryWin32HandleKHR; +#else + void* VMA_NULLABLE vkGetMemoryWin32HandleKHR; +#endif +} VmaVulkanFunctions; + +/// Description of a Allocator to be created. +typedef struct VmaAllocatorCreateInfo +{ + /// Flags for created allocator. Use #VmaAllocatorCreateFlagBits enum. + VmaAllocatorCreateFlags flags; + /// Vulkan physical device. + /** It must be valid throughout whole lifetime of created allocator. */ + VkPhysicalDevice VMA_NOT_NULL physicalDevice; + /// Vulkan device. + /** It must be valid throughout whole lifetime of created allocator. */ + VkDevice VMA_NOT_NULL device; + /// Preferred size of a single `VkDeviceMemory` block to be allocated from large heaps > 1 GiB. Optional. + /** Set to 0 to use default, which is currently 256 MiB. */ + VkDeviceSize preferredLargeHeapBlockSize; + /// Custom CPU memory allocation callbacks. Optional. + /** Optional, can be null. When specified, will also be used for all CPU-side memory allocations. */ + const VkAllocationCallbacks* VMA_NULLABLE pAllocationCallbacks; + /// Informative callbacks for `vkAllocateMemory`, `vkFreeMemory`. Optional. + /** Optional, can be null. */ + const VmaDeviceMemoryCallbacks* VMA_NULLABLE pDeviceMemoryCallbacks; + /** \brief Either null or a pointer to an array of limits on maximum number of bytes that can be allocated out of particular Vulkan memory heap. + + If not NULL, it must be a pointer to an array of + `VkPhysicalDeviceMemoryProperties::memoryHeapCount` elements, defining limit on + maximum number of bytes that can be allocated out of particular Vulkan memory + heap. + + Any of the elements may be equal to `VK_WHOLE_SIZE`, which means no limit on that + heap. This is also the default in case of `pHeapSizeLimit` = NULL. + + If there is a limit defined for a heap: + + - If user tries to allocate more memory from that heap using this allocator, + the allocation fails with `VK_ERROR_OUT_OF_DEVICE_MEMORY`. + - If the limit is smaller than heap size reported in `VkMemoryHeap::size`, the + value of this limit will be reported instead when using vmaGetMemoryProperties(). + + Warning! Using this feature may not be equivalent to installing a GPU with + smaller amount of memory, because graphics driver doesn't necessary fail new + allocations with `VK_ERROR_OUT_OF_DEVICE_MEMORY` result when memory capacity is + exceeded. It may return success and just silently migrate some device memory + blocks to system RAM. This driver behavior can also be controlled using + VK_AMD_memory_overallocation_behavior extension. + */ + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL("VkPhysicalDeviceMemoryProperties::memoryHeapCount") pHeapSizeLimit; + + /** \brief Pointers to Vulkan functions. Can be null. + + For details see [Pointers to Vulkan functions](@ref config_Vulkan_functions). + */ + const VmaVulkanFunctions* VMA_NULLABLE pVulkanFunctions; + /** \brief Handle to Vulkan instance object. + + Starting from version 3.0.0 this member is no longer optional, it must be set! + */ + VkInstance VMA_NOT_NULL instance; + /** \brief Optional. Vulkan version that the application uses. + + It must be a value in the format as created by macro `VK_MAKE_VERSION` or a constant like: `VK_API_VERSION_1_1`, `VK_API_VERSION_1_0`. + The patch version number specified is ignored. Only the major and minor versions are considered. + Only versions 1.0, 1.1, 1.2, 1.3 are supported by the current implementation. + Leaving it initialized to zero is equivalent to `VK_API_VERSION_1_0`. + It must match the Vulkan version used by the application and supported on the selected physical device, + so it must be no higher than `VkApplicationInfo::apiVersion` passed to `vkCreateInstance` + and no higher than `VkPhysicalDeviceProperties::apiVersion` found on the physical device used. + */ + uint32_t vulkanApiVersion; +#if VMA_EXTERNAL_MEMORY + /** \brief Either null or a pointer to an array of external memory handle types for each Vulkan memory type. + + If not NULL, it must be a pointer to an array of `VkPhysicalDeviceMemoryProperties::memoryTypeCount` + elements, defining external memory handle types of particular Vulkan memory type, + to be passed using `VkExportMemoryAllocateInfoKHR`. + + Any of the elements may be equal to 0, which means not to use `VkExportMemoryAllocateInfoKHR` on this memory type. + This is also the default in case of `pTypeExternalMemoryHandleTypes` = NULL. + */ + const VkExternalMemoryHandleTypeFlagsKHR* VMA_NULLABLE VMA_LEN_IF_NOT_NULL("VkPhysicalDeviceMemoryProperties::memoryTypeCount") pTypeExternalMemoryHandleTypes; +#endif // #if VMA_EXTERNAL_MEMORY +} VmaAllocatorCreateInfo; + +/// Information about existing #VmaAllocator object. +typedef struct VmaAllocatorInfo +{ + /** \brief Handle to Vulkan instance object. + + This is the same value as has been passed through VmaAllocatorCreateInfo::instance. + */ + VkInstance VMA_NOT_NULL instance; + /** \brief Handle to Vulkan physical device object. + + This is the same value as has been passed through VmaAllocatorCreateInfo::physicalDevice. + */ + VkPhysicalDevice VMA_NOT_NULL physicalDevice; + /** \brief Handle to Vulkan device object. + + This is the same value as has been passed through VmaAllocatorCreateInfo::device. + */ + VkDevice VMA_NOT_NULL device; +} VmaAllocatorInfo; + +/** @} */ + +/** +\addtogroup group_stats +@{ +*/ + +/** \brief Calculated statistics of memory usage e.g. in a specific memory type, heap, custom pool, or total. + +These are fast to calculate. +See functions: vmaGetHeapBudgets(), vmaGetPoolStatistics(). +*/ +typedef struct VmaStatistics +{ + /** \brief Number of `VkDeviceMemory` objects - Vulkan memory blocks allocated. + */ + uint32_t blockCount; + /** \brief Number of #VmaAllocation objects allocated. + + Dedicated allocations have their own blocks, so each one adds 1 to `allocationCount` as well as `blockCount`. + */ + uint32_t allocationCount; + /** \brief Number of bytes allocated in `VkDeviceMemory` blocks. + + \note To avoid confusion, please be aware that what Vulkan calls an "allocation" - a whole `VkDeviceMemory` object + (e.g. as in `VkPhysicalDeviceLimits::maxMemoryAllocationCount`) is called a "block" in VMA, while VMA calls + "allocation" a #VmaAllocation object that represents a memory region sub-allocated from such block, usually for a single buffer or image. + */ + VkDeviceSize blockBytes; + /** \brief Total number of bytes occupied by all #VmaAllocation objects. + + Always less or equal than `blockBytes`. + Difference `(blockBytes - allocationBytes)` is the amount of memory allocated from Vulkan + but unused by any #VmaAllocation. + */ + VkDeviceSize allocationBytes; +} VmaStatistics; + +/** \brief More detailed statistics than #VmaStatistics. + +These are slower to calculate. Use for debugging purposes. +See functions: vmaCalculateStatistics(), vmaCalculatePoolStatistics(). + +Previous version of the statistics API provided averages, but they have been removed +because they can be easily calculated as: + +\code +VkDeviceSize allocationSizeAvg = detailedStats.statistics.allocationBytes / detailedStats.statistics.allocationCount; +VkDeviceSize unusedBytes = detailedStats.statistics.blockBytes - detailedStats.statistics.allocationBytes; +VkDeviceSize unusedRangeSizeAvg = unusedBytes / detailedStats.unusedRangeCount; +\endcode +*/ +typedef struct VmaDetailedStatistics +{ + /// Basic statistics. + VmaStatistics statistics; + /// Number of free ranges of memory between allocations. + uint32_t unusedRangeCount; + /// Smallest allocation size. `VK_WHOLE_SIZE` if there are 0 allocations. + VkDeviceSize allocationSizeMin; + /// Largest allocation size. 0 if there are 0 allocations. + VkDeviceSize allocationSizeMax; + /// Smallest empty range size. `VK_WHOLE_SIZE` if there are 0 empty ranges. + VkDeviceSize unusedRangeSizeMin; + /// Largest empty range size. 0 if there are 0 empty ranges. + VkDeviceSize unusedRangeSizeMax; +} VmaDetailedStatistics; + +/** \brief General statistics from current state of the Allocator - +total memory usage across all memory heaps and types. + +These are slower to calculate. Use for debugging purposes. +See function vmaCalculateStatistics(). +*/ +typedef struct VmaTotalStatistics +{ + VmaDetailedStatistics memoryType[VK_MAX_MEMORY_TYPES]; + VmaDetailedStatistics memoryHeap[VK_MAX_MEMORY_HEAPS]; + VmaDetailedStatistics total; +} VmaTotalStatistics; + +/** \brief Statistics of current memory usage and available budget for a specific memory heap. + +These are fast to calculate. +See function vmaGetHeapBudgets(). +*/ +typedef struct VmaBudget +{ + /** \brief Statistics fetched from the library. + */ + VmaStatistics statistics; + /** \brief Estimated current memory usage of the program, in bytes. + + Fetched from system using VK_EXT_memory_budget extension if enabled. + + It might be different than `statistics.blockBytes` (usually higher) due to additional implicit objects + also occupying the memory, like swapchain, pipelines, descriptor heaps, command buffers, or + `VkDeviceMemory` blocks allocated outside of this library, if any. + */ + VkDeviceSize usage; + /** \brief Estimated amount of memory available to the program, in bytes. + + Fetched from system using VK_EXT_memory_budget extension if enabled. + + It might be different (most probably smaller) than `VkMemoryHeap::size[heapIndex]` due to factors + external to the program, decided by the operating system. + Difference `budget - usage` is the amount of additional memory that can probably + be allocated without problems. Exceeding the budget may result in various problems. + */ + VkDeviceSize budget; +} VmaBudget; + +/** @} */ + +/** +\addtogroup group_alloc +@{ +*/ + +/** \brief Parameters of new #VmaAllocation. + +To be used with functions like vmaCreateBuffer(), vmaCreateImage(), and many others. +*/ +typedef struct VmaAllocationCreateInfo +{ + /// Use #VmaAllocationCreateFlagBits enum. + VmaAllocationCreateFlags flags; + /** \brief Intended usage of memory. + + You can leave #VMA_MEMORY_USAGE_UNKNOWN if you specify memory requirements in other way. \n + If `pool` is not null, this member is ignored. + */ + VmaMemoryUsage usage; + /** \brief Flags that must be set in a Memory Type chosen for an allocation. + + Leave 0 if you specify memory requirements in other way. \n + If `pool` is not null, this member is ignored.*/ + VkMemoryPropertyFlags requiredFlags; + /** \brief Flags that preferably should be set in a memory type chosen for an allocation. + + Set to 0 if no additional flags are preferred. \n + If `pool` is not null, this member is ignored. */ + VkMemoryPropertyFlags preferredFlags; + /** \brief Bitmask containing one bit set for every memory type acceptable for this allocation. + + Value 0 is equivalent to `UINT32_MAX` - it means any memory type is accepted if + it meets other requirements specified by this structure, with no further + restrictions on memory type index. \n + If `pool` is not null, this member is ignored. + */ + uint32_t memoryTypeBits; + /** \brief Pool that this allocation should be created in. + + Leave `VK_NULL_HANDLE` to allocate from default pool. If not null, members: + `usage`, `requiredFlags`, `preferredFlags`, `memoryTypeBits` are ignored. + */ + VmaPool VMA_NULLABLE pool; + /** \brief Custom general-purpose pointer that will be stored in #VmaAllocation, can be read as VmaAllocationInfo::pUserData and changed using vmaSetAllocationUserData(). + + If #VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT is used, it must be either + null or pointer to a null-terminated string. The string will be then copied to + internal buffer, so it doesn't need to be valid after allocation call. + */ + void* VMA_NULLABLE pUserData; + /** \brief A floating-point value between 0 and 1, indicating the priority of the allocation relative to other memory allocations. + + It is used only when #VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT flag was used during creation of the #VmaAllocator object + and this allocation ends up as dedicated or is explicitly forced as dedicated using #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. + Otherwise, it has the priority of a memory block where it is placed and this variable is ignored. + */ + float priority; +} VmaAllocationCreateInfo; + +/// Describes parameter of created #VmaPool. +typedef struct VmaPoolCreateInfo +{ + /** \brief Vulkan memory type index to allocate this pool from. + */ + uint32_t memoryTypeIndex; + /** \brief Use combination of #VmaPoolCreateFlagBits. + */ + VmaPoolCreateFlags flags; + /** \brief Size of a single `VkDeviceMemory` block to be allocated as part of this pool, in bytes. Optional. + + Specify nonzero to set explicit, constant size of memory blocks used by this + pool. + + Leave 0 to use default and let the library manage block sizes automatically. + Sizes of particular blocks may vary. + In this case, the pool will also support dedicated allocations. + */ + VkDeviceSize blockSize; + /** \brief Minimum number of blocks to be always allocated in this pool, even if they stay empty. + + Set to 0 to have no preallocated blocks and allow the pool be completely empty. + */ + size_t minBlockCount; + /** \brief Maximum number of blocks that can be allocated in this pool. Optional. + + Set to 0 to use default, which is `SIZE_MAX`, which means no limit. + + Set to same value as VmaPoolCreateInfo::minBlockCount to have fixed amount of memory allocated + throughout whole lifetime of this pool. + */ + size_t maxBlockCount; + /** \brief A floating-point value between 0 and 1, indicating the priority of the allocations in this pool relative to other memory allocations. + + It is used only when #VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT flag was used during creation of the #VmaAllocator object. + Otherwise, this variable is ignored. + */ + float priority; + /** \brief Additional minimum alignment to be used for all allocations created from this pool. Can be 0. + + Leave 0 (default) not to impose any additional alignment. If not 0, it must be a power of two. + It can be useful in cases where alignment returned by Vulkan by functions like `vkGetBufferMemoryRequirements` is not enough, + e.g. when doing interop with OpenGL. + */ + VkDeviceSize minAllocationAlignment; + /** \brief Additional `pNext` chain to be attached to `VkMemoryAllocateInfo` used for every allocation made by this pool. Optional. + + Optional, can be null. If not null, it must point to a `pNext` chain of structures that can be attached to `VkMemoryAllocateInfo`. + It can be useful for special needs such as adding `VkExportMemoryAllocateInfoKHR`. + Structures pointed by this member must remain alive and unchanged for the whole lifetime of the custom pool. + + Please note that some structures, e.g. `VkMemoryPriorityAllocateInfoEXT`, `VkMemoryDedicatedAllocateInfoKHR`, + can be attached automatically by this library when using other, more convenient of its features. + */ + void* VMA_NULLABLE VMA_EXTENDS_VK_STRUCT(VkMemoryAllocateInfo) pMemoryAllocateNext; +} VmaPoolCreateInfo; + +/** @} */ + +/** +\addtogroup group_alloc +@{ +*/ + +/** +Parameters of #VmaAllocation objects, that can be retrieved using function vmaGetAllocationInfo(). + +There is also an extended version of this structure that carries additional parameters: #VmaAllocationInfo2. +*/ +typedef struct VmaAllocationInfo +{ + /** \brief Memory type index that this allocation was allocated from. + + It never changes. + */ + uint32_t memoryType; + /** \brief Handle to Vulkan memory object. + + Same memory object can be shared by multiple allocations. + + It can change after the allocation is moved during \ref defragmentation. + */ + VkDeviceMemory VMA_NULLABLE_NON_DISPATCHABLE deviceMemory; + /** \brief Offset in `VkDeviceMemory` object to the beginning of this allocation, in bytes. `(deviceMemory, offset)` pair is unique to this allocation. + + You usually don't need to use this offset. If you create a buffer or an image together with the allocation using e.g. function + vmaCreateBuffer(), vmaCreateImage(), functions that operate on these resources refer to the beginning of the buffer or image, + not entire device memory block. Functions like vmaMapMemory(), vmaBindBufferMemory() also refer to the beginning of the allocation + and apply this offset automatically. + + It can change after the allocation is moved during \ref defragmentation. + */ + VkDeviceSize offset; + /** \brief Size of this allocation, in bytes. + + It never changes. + + \note Allocation size returned in this variable may be greater than the size + requested for the resource e.g. as `VkBufferCreateInfo::size`. Whole size of the + allocation is accessible for operations on memory e.g. using a pointer after + mapping with vmaMapMemory(), but operations on the resource e.g. using + `vkCmdCopyBuffer` must be limited to the size of the resource. + */ + VkDeviceSize size; + /** \brief Pointer to the beginning of this allocation as mapped data. + + If the allocation hasn't been mapped using vmaMapMemory() and hasn't been + created with #VMA_ALLOCATION_CREATE_MAPPED_BIT flag, this value is null. + + It can change after call to vmaMapMemory(), vmaUnmapMemory(). + It can also change after the allocation is moved during \ref defragmentation. + */ + void* VMA_NULLABLE pMappedData; + /** \brief Custom general-purpose pointer that was passed as VmaAllocationCreateInfo::pUserData or set using vmaSetAllocationUserData(). + + It can change after call to vmaSetAllocationUserData() for this allocation. + */ + void* VMA_NULLABLE pUserData; + /** \brief Custom allocation name that was set with vmaSetAllocationName(). + + It can change after call to vmaSetAllocationName() for this allocation. + + Another way to set custom name is to pass it in VmaAllocationCreateInfo::pUserData with + additional flag #VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT set [DEPRECATED]. + */ + const char* VMA_NULLABLE pName; +} VmaAllocationInfo; + +/// Extended parameters of a #VmaAllocation object that can be retrieved using function vmaGetAllocationInfo2(). +typedef struct VmaAllocationInfo2 +{ + /** \brief Basic parameters of the allocation. + + If you need only these, you can use function vmaGetAllocationInfo() and structure #VmaAllocationInfo instead. + */ + VmaAllocationInfo allocationInfo; + /** \brief Size of the `VkDeviceMemory` block that the allocation belongs to. + + In case of an allocation with dedicated memory, it will be equal to `allocationInfo.size`. + */ + VkDeviceSize blockSize; + /** \brief `VK_TRUE` if the allocation has dedicated memory, `VK_FALSE` if it was placed as part of a larger memory block. + + When `VK_TRUE`, it also means `VkMemoryDedicatedAllocateInfo` was used when creating the allocation + (if VK_KHR_dedicated_allocation extension or Vulkan version >= 1.1 is enabled). + */ + VkBool32 dedicatedMemory; +} VmaAllocationInfo2; + +/** Callback function called during vmaBeginDefragmentation() to check custom criterion about ending current defragmentation pass. + +Should return true if the defragmentation needs to stop current pass. +*/ +typedef VkBool32 (VKAPI_PTR* PFN_vmaCheckDefragmentationBreakFunction)(void* VMA_NULLABLE pUserData); + +/** \brief Parameters for defragmentation. + +To be used with function vmaBeginDefragmentation(). +*/ +typedef struct VmaDefragmentationInfo +{ + /// \brief Use combination of #VmaDefragmentationFlagBits. + VmaDefragmentationFlags flags; + /** \brief Custom pool to be defragmented. + + If null then default pools will undergo defragmentation process. + */ + VmaPool VMA_NULLABLE pool; + /** \brief Maximum numbers of bytes that can be copied during single pass, while moving allocations to different places. + + `0` means no limit. + */ + VkDeviceSize maxBytesPerPass; + /** \brief Maximum number of allocations that can be moved during single pass to a different place. + + `0` means no limit. + */ + uint32_t maxAllocationsPerPass; + /** \brief Optional custom callback for stopping vmaBeginDefragmentation(). + + Have to return true for breaking current defragmentation pass. + */ + PFN_vmaCheckDefragmentationBreakFunction VMA_NULLABLE pfnBreakCallback; + /// \brief Optional data to pass to custom callback for stopping pass of defragmentation. + void* VMA_NULLABLE pBreakCallbackUserData; +} VmaDefragmentationInfo; + +/// Single move of an allocation to be done for defragmentation. +typedef struct VmaDefragmentationMove +{ + /// Operation to be performed on the allocation by vmaEndDefragmentationPass(). Default value is #VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY. You can modify it. + VmaDefragmentationMoveOperation operation; + /// Allocation that should be moved. + VmaAllocation VMA_NOT_NULL srcAllocation; + /** \brief Temporary allocation pointing to destination memory that will replace `srcAllocation`. + + \warning Do not store this allocation in your data structures! It exists only temporarily, for the duration of the defragmentation pass, + to be used for binding new buffer/image to the destination memory using e.g. vmaBindBufferMemory(). + vmaEndDefragmentationPass() will destroy it and make `srcAllocation` point to this memory. + */ + VmaAllocation VMA_NOT_NULL dstTmpAllocation; +} VmaDefragmentationMove; + +/** \brief Parameters for incremental defragmentation steps. + +To be used with function vmaBeginDefragmentationPass(). +*/ +typedef struct VmaDefragmentationPassMoveInfo +{ + /// Number of elements in the `pMoves` array. + uint32_t moveCount; + /** \brief Array of moves to be performed by the user in the current defragmentation pass. + + Pointer to an array of `moveCount` elements, owned by VMA, created in vmaBeginDefragmentationPass(), destroyed in vmaEndDefragmentationPass(). + + For each element, you should: + + 1. Create a new buffer/image in the place pointed by VmaDefragmentationMove::dstMemory + VmaDefragmentationMove::dstOffset. + 2. Copy data from the VmaDefragmentationMove::srcAllocation e.g. using `vkCmdCopyBuffer`, `vkCmdCopyImage`. + 3. Make sure these commands finished executing on the GPU. + 4. Destroy the old buffer/image. + + Only then you can finish defragmentation pass by calling vmaEndDefragmentationPass(). + After this call, the allocation will point to the new place in memory. + + Alternatively, if you cannot move specific allocation, you can set VmaDefragmentationMove::operation to #VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE. + + Alternatively, if you decide you want to completely remove the allocation: + + 1. Destroy its buffer/image. + 2. Set VmaDefragmentationMove::operation to #VMA_DEFRAGMENTATION_MOVE_OPERATION_DESTROY. + + Then, after vmaEndDefragmentationPass() the allocation will be freed. + */ + VmaDefragmentationMove* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(moveCount) pMoves; +} VmaDefragmentationPassMoveInfo; + +/// Statistics returned for defragmentation process in function vmaEndDefragmentation(). +typedef struct VmaDefragmentationStats +{ + /// Total number of bytes that have been copied while moving allocations to different places. + VkDeviceSize bytesMoved; + /// Total number of bytes that have been released to the system by freeing empty `VkDeviceMemory` objects. + VkDeviceSize bytesFreed; + /// Number of allocations that have been moved to different places. + uint32_t allocationsMoved; + /// Number of empty `VkDeviceMemory` objects that have been released to the system. + uint32_t deviceMemoryBlocksFreed; +} VmaDefragmentationStats; + +/** @} */ + +/** +\addtogroup group_virtual +@{ +*/ + +/// Parameters of created #VmaVirtualBlock object to be passed to vmaCreateVirtualBlock(). +typedef struct VmaVirtualBlockCreateInfo +{ + /** \brief Total size of the virtual block. + + Sizes can be expressed in bytes or any units you want as long as you are consistent in using them. + For example, if you allocate from some array of structures, 1 can mean single instance of entire structure. + */ + VkDeviceSize size; + + /** \brief Use combination of #VmaVirtualBlockCreateFlagBits. + */ + VmaVirtualBlockCreateFlags flags; + + /** \brief Custom CPU memory allocation callbacks. Optional. + + Optional, can be null. When specified, they will be used for all CPU-side memory allocations. + */ + const VkAllocationCallbacks* VMA_NULLABLE pAllocationCallbacks; +} VmaVirtualBlockCreateInfo; + +/// Parameters of created virtual allocation to be passed to vmaVirtualAllocate(). +typedef struct VmaVirtualAllocationCreateInfo +{ + /** \brief Size of the allocation. + + Cannot be zero. + */ + VkDeviceSize size; + /** \brief Required alignment of the allocation. Optional. + + Must be power of two. Special value 0 has the same meaning as 1 - means no special alignment is required, so allocation can start at any offset. + */ + VkDeviceSize alignment; + /** \brief Use combination of #VmaVirtualAllocationCreateFlagBits. + */ + VmaVirtualAllocationCreateFlags flags; + /** \brief Custom pointer to be associated with the allocation. Optional. + + It can be any value and can be used for user-defined purposes. It can be fetched or changed later. + */ + void* VMA_NULLABLE pUserData; +} VmaVirtualAllocationCreateInfo; + +/// Parameters of an existing virtual allocation, returned by vmaGetVirtualAllocationInfo(). +typedef struct VmaVirtualAllocationInfo +{ + /** \brief Offset of the allocation. + + Offset at which the allocation was made. + */ + VkDeviceSize offset; + /** \brief Size of the allocation. + + Same value as passed in VmaVirtualAllocationCreateInfo::size. + */ + VkDeviceSize size; + /** \brief Custom pointer associated with the allocation. + + Same value as passed in VmaVirtualAllocationCreateInfo::pUserData or to vmaSetVirtualAllocationUserData(). + */ + void* VMA_NULLABLE pUserData; +} VmaVirtualAllocationInfo; + +/** @} */ + +#endif // _VMA_DATA_TYPES_DECLARATIONS + +#ifndef _VMA_FUNCTION_HEADERS + +/** +\addtogroup group_init +@{ +*/ + +/// Creates #VmaAllocator object. +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAllocator( + const VmaAllocatorCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaAllocator VMA_NULLABLE* VMA_NOT_NULL pAllocator); + +/// Destroys allocator object. +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyAllocator( + VmaAllocator VMA_NULLABLE allocator); + +/** \brief Returns information about existing #VmaAllocator object - handle to Vulkan device etc. + +It might be useful if you want to keep just the #VmaAllocator handle and fetch other required handles to +`VkPhysicalDevice`, `VkDevice` etc. every time using this function. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocatorInfo( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocatorInfo* VMA_NOT_NULL pAllocatorInfo); + +/** +PhysicalDeviceProperties are fetched from physicalDevice by the allocator. +You can access it here, without fetching it again on your own. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetPhysicalDeviceProperties( + VmaAllocator VMA_NOT_NULL allocator, + const VkPhysicalDeviceProperties* VMA_NULLABLE* VMA_NOT_NULL ppPhysicalDeviceProperties); + +/** +PhysicalDeviceMemoryProperties are fetched from physicalDevice by the allocator. +You can access it here, without fetching it again on your own. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryProperties( + VmaAllocator VMA_NOT_NULL allocator, + const VkPhysicalDeviceMemoryProperties* VMA_NULLABLE* VMA_NOT_NULL ppPhysicalDeviceMemoryProperties); + +/** +\brief Given Memory Type Index, returns Property Flags of this memory type. + +This is just a convenience function. Same information can be obtained using +vmaGetMemoryProperties(). +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryTypeProperties( + VmaAllocator VMA_NOT_NULL allocator, + uint32_t memoryTypeIndex, + VkMemoryPropertyFlags* VMA_NOT_NULL pFlags); + +/** \brief Sets index of the current frame. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaSetCurrentFrameIndex( + VmaAllocator VMA_NOT_NULL allocator, + uint32_t frameIndex); + +/** @} */ + +/** +\addtogroup group_stats +@{ +*/ + +/** \brief Retrieves statistics from current state of the Allocator. + +This function is called "calculate" not "get" because it has to traverse all +internal data structures, so it may be quite slow. Use it for debugging purposes. +For faster but more brief statistics suitable to be called every frame or every allocation, +use vmaGetHeapBudgets(). + +Note that when using allocator from multiple threads, returned information may immediately +become outdated. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaCalculateStatistics( + VmaAllocator VMA_NOT_NULL allocator, + VmaTotalStatistics* VMA_NOT_NULL pStats); + +/** \brief Retrieves information about current memory usage and budget for all memory heaps. + +\param allocator +\param[out] pBudgets Must point to array with number of elements at least equal to number of memory heaps in physical device used. + +This function is called "get" not "calculate" because it is very fast, suitable to be called +every frame or every allocation. For more detailed statistics use vmaCalculateStatistics(). + +Note that when using allocator from multiple threads, returned information may immediately +become outdated. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetHeapBudgets( + VmaAllocator VMA_NOT_NULL allocator, + VmaBudget* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL("VkPhysicalDeviceMemoryProperties::memoryHeapCount") pBudgets); + +/** @} */ + +/** +\addtogroup group_alloc +@{ +*/ + +/** +\brief Helps to find memoryTypeIndex, given memoryTypeBits and VmaAllocationCreateInfo. + +This algorithm tries to find a memory type that: + +- Is allowed by memoryTypeBits. +- Contains all the flags from pAllocationCreateInfo->requiredFlags. +- Matches intended usage. +- Has as many flags from pAllocationCreateInfo->preferredFlags as possible. + +\return Returns VK_ERROR_FEATURE_NOT_PRESENT if not found. Receiving such result +from this function or any other allocating function probably means that your +device doesn't support any memory type with requested features for the specific +type of resource you want to use it for. Please check parameters of your +resource, like image layout (OPTIMAL versus LINEAR) or mip level count. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndex( + VmaAllocator VMA_NOT_NULL allocator, + uint32_t memoryTypeBits, + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + uint32_t* VMA_NOT_NULL pMemoryTypeIndex); + +/** +\brief Helps to find memoryTypeIndex, given VkBufferCreateInfo and VmaAllocationCreateInfo. + +It can be useful e.g. to determine value to be used as VmaPoolCreateInfo::memoryTypeIndex. +It internally creates a temporary, dummy buffer that never has memory bound. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForBufferInfo( + VmaAllocator VMA_NOT_NULL allocator, + const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo, + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + uint32_t* VMA_NOT_NULL pMemoryTypeIndex); + +/** +\brief Helps to find memoryTypeIndex, given VkImageCreateInfo and VmaAllocationCreateInfo. + +It can be useful e.g. to determine value to be used as VmaPoolCreateInfo::memoryTypeIndex. +It internally creates a temporary, dummy image that never has memory bound. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForImageInfo( + VmaAllocator VMA_NOT_NULL allocator, + const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo, + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + uint32_t* VMA_NOT_NULL pMemoryTypeIndex); + +/** \brief Allocates Vulkan device memory and creates #VmaPool object. + +\param allocator Allocator object. +\param pCreateInfo Parameters of pool to create. +\param[out] pPool Handle to created pool. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreatePool( + VmaAllocator VMA_NOT_NULL allocator, + const VmaPoolCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaPool VMA_NULLABLE* VMA_NOT_NULL pPool); + +/** \brief Destroys #VmaPool object and frees Vulkan device memory. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyPool( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NULLABLE pool); + +/** @} */ + +/** +\addtogroup group_stats +@{ +*/ + +/** \brief Retrieves statistics of existing #VmaPool object. + +\param allocator Allocator object. +\param pool Pool object. +\param[out] pPoolStats Statistics of specified pool. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolStatistics( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NOT_NULL pool, + VmaStatistics* VMA_NOT_NULL pPoolStats); + +/** \brief Retrieves detailed statistics of existing #VmaPool object. + +\param allocator Allocator object. +\param pool Pool object. +\param[out] pPoolStats Statistics of specified pool. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaCalculatePoolStatistics( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NOT_NULL pool, + VmaDetailedStatistics* VMA_NOT_NULL pPoolStats); + +/** @} */ + +/** +\addtogroup group_alloc +@{ +*/ + +/** \brief Checks magic number in margins around all allocations in given memory pool in search for corruptions. + +Corruption detection is enabled only when `VMA_DEBUG_DETECT_CORRUPTION` macro is defined to nonzero, +`VMA_DEBUG_MARGIN` is defined to nonzero and the pool is created in memory type that is +`HOST_VISIBLE` and `HOST_COHERENT`. For more information, see [Corruption detection](@ref debugging_memory_usage_corruption_detection). + +Possible return values: + +- `VK_ERROR_FEATURE_NOT_PRESENT` - corruption detection is not enabled for specified pool. +- `VK_SUCCESS` - corruption detection has been performed and succeeded. +- `VK_ERROR_UNKNOWN` - corruption detection has been performed and found memory corruptions around one of the allocations. + `VMA_ASSERT` is also fired in that case. +- Other value: Error returned by Vulkan, e.g. memory mapping failure. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckPoolCorruption( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NOT_NULL pool); + +/** \brief Retrieves name of a custom pool. + +After the call `ppName` is either null or points to an internally-owned null-terminated string +containing name of the pool that was previously set. The pointer becomes invalid when the pool is +destroyed or its name is changed using vmaSetPoolName(). +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolName( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NOT_NULL pool, + const char* VMA_NULLABLE* VMA_NOT_NULL ppName); + +/** \brief Sets name of a custom pool. + +`pName` can be either null or pointer to a null-terminated string with new name for the pool. +Function makes internal copy of the string, so it can be changed or freed immediately after this call. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaSetPoolName( + VmaAllocator VMA_NOT_NULL allocator, + VmaPool VMA_NOT_NULL pool, + const char* VMA_NULLABLE pName); + +/** \brief General purpose memory allocation. + +\param allocator +\param pVkMemoryRequirements +\param pCreateInfo +\param[out] pAllocation Handle to allocated memory. +\param[out] pAllocationInfo Optional. Information about allocated memory. It can be later fetched using function vmaGetAllocationInfo(). + +You should free the memory using vmaFreeMemory() or vmaFreeMemoryPages(). + +It is recommended to use vmaAllocateMemoryForBuffer(), vmaAllocateMemoryForImage(), +vmaCreateBuffer(), vmaCreateImage() instead whenever possible. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemory( + VmaAllocator VMA_NOT_NULL allocator, + const VkMemoryRequirements* VMA_NOT_NULL pVkMemoryRequirements, + const VmaAllocationCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaAllocation VMA_NULLABLE* VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); + +/** \brief General purpose memory allocation for multiple allocation objects at once. + +\param allocator Allocator object. +\param pVkMemoryRequirements Memory requirements for each allocation. +\param pCreateInfo Creation parameters for each allocation. +\param allocationCount Number of allocations to make. +\param[out] pAllocations Pointer to array that will be filled with handles to created allocations. +\param[out] pAllocationInfo Optional. Pointer to array that will be filled with parameters of created allocations. + +You should free the memory using vmaFreeMemory() or vmaFreeMemoryPages(). + +Word "pages" is just a suggestion to use this function to allocate pieces of memory needed for sparse binding. +It is just a general purpose allocation function able to make multiple allocations at once. +It may be internally optimized to be more efficient than calling vmaAllocateMemory() `allocationCount` times. + +All allocations are made using same parameters. All of them are created out of the same memory pool and type. +If any allocation fails, all allocations already made within this function call are also freed, so that when +returned result is not `VK_SUCCESS`, `pAllocation` array is always entirely filled with `VK_NULL_HANDLE`. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryPages( + VmaAllocator VMA_NOT_NULL allocator, + const VkMemoryRequirements* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pVkMemoryRequirements, + const VmaAllocationCreateInfo* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pCreateInfo, + size_t allocationCount, + VmaAllocation VMA_NULLABLE* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pAllocations, + VmaAllocationInfo* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) pAllocationInfo); + +/** \brief Allocates memory suitable for given `VkBuffer`. + +\param allocator +\param buffer +\param pCreateInfo +\param[out] pAllocation Handle to allocated memory. +\param[out] pAllocationInfo Optional. Information about allocated memory. It can be later fetched using function vmaGetAllocationInfo(). + +It only creates #VmaAllocation. To bind the memory to the buffer, use vmaBindBufferMemory(). + +This is a special-purpose function. In most cases you should use vmaCreateBuffer(). + +You must free the allocation using vmaFreeMemory() when no longer needed. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForBuffer( + VmaAllocator VMA_NOT_NULL allocator, + VkBuffer VMA_NOT_NULL_NON_DISPATCHABLE buffer, + const VmaAllocationCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaAllocation VMA_NULLABLE* VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); + +/** \brief Allocates memory suitable for given `VkImage`. + +\param allocator +\param image +\param pCreateInfo +\param[out] pAllocation Handle to allocated memory. +\param[out] pAllocationInfo Optional. Information about allocated memory. It can be later fetched using function vmaGetAllocationInfo(). + +It only creates #VmaAllocation. To bind the memory to the buffer, use vmaBindImageMemory(). + +This is a special-purpose function. In most cases you should use vmaCreateImage(). + +You must free the allocation using vmaFreeMemory() when no longer needed. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForImage( + VmaAllocator VMA_NOT_NULL allocator, + VkImage VMA_NOT_NULL_NON_DISPATCHABLE image, + const VmaAllocationCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaAllocation VMA_NULLABLE* VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); + +/** \brief Frees memory previously allocated using vmaAllocateMemory(), vmaAllocateMemoryForBuffer(), or vmaAllocateMemoryForImage(). + +Passing `VK_NULL_HANDLE` as `allocation` is valid. Such function call is just skipped. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemory( + VmaAllocator VMA_NOT_NULL allocator, + const VmaAllocation VMA_NULLABLE allocation); + +/** \brief Frees memory and destroys multiple allocations. + +Word "pages" is just a suggestion to use this function to free pieces of memory used for sparse binding. +It is just a general purpose function to free memory and destroy allocations made using e.g. vmaAllocateMemory(), +vmaAllocateMemoryPages() and other functions. +It may be internally optimized to be more efficient than calling vmaFreeMemory() `allocationCount` times. + +Allocations in `pAllocations` array can come from any memory pools and types. +Passing `VK_NULL_HANDLE` as elements of `pAllocations` array is valid. Such entries are just skipped. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemoryPages( + VmaAllocator VMA_NOT_NULL allocator, + size_t allocationCount, + const VmaAllocation VMA_NULLABLE* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(allocationCount) pAllocations); + +/** \brief Returns current information about specified allocation. + +Current parameters of given allocation are returned in `pAllocationInfo`. + +Although this function doesn't lock any mutex, so it should be quite efficient, +you should avoid calling it too often. +You can retrieve same VmaAllocationInfo structure while creating your resource, from function +vmaCreateBuffer(), vmaCreateImage(). You can remember it if you are sure parameters don't change +(e.g. due to defragmentation). + +There is also a new function vmaGetAllocationInfo2() that offers extended information +about the allocation, returned using new structure #VmaAllocationInfo2. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationInfo( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VmaAllocationInfo* VMA_NOT_NULL pAllocationInfo); + +/** \brief Returns extended information about specified allocation. + +Current parameters of given allocation are returned in `pAllocationInfo`. +Extended parameters in structure #VmaAllocationInfo2 include memory block size +and a flag telling whether the allocation has dedicated memory. +It can be useful e.g. for interop with OpenGL. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationInfo2( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VmaAllocationInfo2* VMA_NOT_NULL pAllocationInfo); + +/** \brief Sets pUserData in given allocation to new value. + +The value of pointer `pUserData` is copied to allocation's `pUserData`. +It is opaque, so you can use it however you want - e.g. +as a pointer, ordinal number or some handle to you own data. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationUserData( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + void* VMA_NULLABLE pUserData); + +/** \brief Sets pName in given allocation to new value. + +`pName` must be either null, or pointer to a null-terminated string. The function +makes local copy of the string and sets it as allocation's `pName`. String +passed as pName doesn't need to be valid for whole lifetime of the allocation - +you can free it after this call. String previously pointed by allocation's +`pName` is freed from memory. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationName( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + const char* VMA_NULLABLE pName); + +/** +\brief Given an allocation, returns Property Flags of its memory type. + +This is just a convenience function. Same information can be obtained using +vmaGetAllocationInfo() + vmaGetMemoryProperties(). +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationMemoryProperties( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkMemoryPropertyFlags* VMA_NOT_NULL pFlags); + + +#if VMA_EXTERNAL_MEMORY_WIN32 +/** +\brief Given an allocation, returns Win32 handle that may be imported by other processes or APIs. + +\param hTargetProcess Must be a valid handle to target process or null. If it's null, the function returns + handle for the current process. +\param[out] pHandle Output parameter that returns the handle. + +The function fills `pHandle` with handle that can be used in target process. +The handle is fetched using function `vkGetMemoryWin32HandleKHR`. +When no longer needed, you must close it using: + +\code +CloseHandle(handle); +\endcode + +You can close it any time, before or after destroying the allocation object. +It is reference-counted internally by Windows. + +Note the handle is returned for the entire `VkDeviceMemory` block that the allocation belongs to. +If the allocation is sub-allocated from a larger block, you may need to consider the offset of the allocation +(VmaAllocationInfo::offset). + +If the function fails with `VK_ERROR_FEATURE_NOT_PRESENT` error code, please double-check +that VmaVulkanFunctions::vkGetMemoryWin32HandleKHR function pointer is set, e.g. either by using `VMA_DYNAMIC_VULKAN_FUNCTIONS` +or by manually passing it through VmaAllocatorCreateInfo::pVulkanFunctions. + +For more information, see chapter \ref vk_khr_external_memory_win32. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaGetMemoryWin32Handle(VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, HANDLE hTargetProcess, HANDLE* VMA_NOT_NULL pHandle); +#endif // VMA_EXTERNAL_MEMORY_WIN32 + +/** \brief Maps memory represented by given allocation and returns pointer to it. + +Maps memory represented by given allocation to make it accessible to CPU code. +When succeeded, `*ppData` contains pointer to first byte of this memory. + +\warning +If the allocation is part of a bigger `VkDeviceMemory` block, returned pointer is +correctly offsetted to the beginning of region assigned to this particular allocation. +Unlike the result of `vkMapMemory`, it points to the allocation, not to the beginning of the whole block. +You should not add VmaAllocationInfo::offset to it! + +Mapping is internally reference-counted and synchronized, so despite raw Vulkan +function `vkMapMemory()` cannot be used to map same block of `VkDeviceMemory` +multiple times simultaneously, it is safe to call this function on allocations +assigned to the same memory block. Actual Vulkan memory will be mapped on first +mapping and unmapped on last unmapping. + +If the function succeeded, you must call vmaUnmapMemory() to unmap the +allocation when mapping is no longer needed or before freeing the allocation, at +the latest. + +It also safe to call this function multiple times on the same allocation. You +must call vmaUnmapMemory() same number of times as you called vmaMapMemory(). + +It is also safe to call this function on allocation created with +#VMA_ALLOCATION_CREATE_MAPPED_BIT flag. Its memory stays mapped all the time. +You must still call vmaUnmapMemory() same number of times as you called +vmaMapMemory(). You must not call vmaUnmapMemory() additional time to free the +"0-th" mapping made automatically due to #VMA_ALLOCATION_CREATE_MAPPED_BIT flag. + +This function fails when used on allocation made in memory type that is not +`HOST_VISIBLE`. + +This function doesn't automatically flush or invalidate caches. +If the allocation is made from a memory types that is not `HOST_COHERENT`, +you also need to use vmaInvalidateAllocation() / vmaFlushAllocation(), as required by Vulkan specification. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaMapMemory( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + void* VMA_NULLABLE* VMA_NOT_NULL ppData); + +/** \brief Unmaps memory represented by given allocation, mapped previously using vmaMapMemory(). + +For details, see description of vmaMapMemory(). + +This function doesn't automatically flush or invalidate caches. +If the allocation is made from a memory types that is not `HOST_COHERENT`, +you also need to use vmaInvalidateAllocation() / vmaFlushAllocation(), as required by Vulkan specification. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaUnmapMemory( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation); + +/** \brief Flushes memory of given allocation. + +Calls `vkFlushMappedMemoryRanges()` for memory associated with given range of given allocation. +It needs to be called after writing to a mapped memory for memory types that are not `HOST_COHERENT`. +Unmap operation doesn't do that automatically. + +- `offset` must be relative to the beginning of allocation. +- `size` can be `VK_WHOLE_SIZE`. It means all memory from `offset` the the end of given allocation. +- `offset` and `size` don't have to be aligned. + They are internally rounded down/up to multiply of `nonCoherentAtomSize`. +- If `size` is 0, this call is ignored. +- If memory type that the `allocation` belongs to is not `HOST_VISIBLE` or it is `HOST_COHERENT`, + this call is ignored. + +Warning! `offset` and `size` are relative to the contents of given `allocation`. +If you mean whole allocation, you can pass 0 and `VK_WHOLE_SIZE`, respectively. +Do not pass allocation's offset as `offset`!!! + +This function returns the `VkResult` from `vkFlushMappedMemoryRanges` if it is +called, otherwise `VK_SUCCESS`. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocation( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize offset, + VkDeviceSize size); + +/** \brief Invalidates memory of given allocation. + +Calls `vkInvalidateMappedMemoryRanges()` for memory associated with given range of given allocation. +It needs to be called before reading from a mapped memory for memory types that are not `HOST_COHERENT`. +Map operation doesn't do that automatically. + +- `offset` must be relative to the beginning of allocation. +- `size` can be `VK_WHOLE_SIZE`. It means all memory from `offset` the the end of given allocation. +- `offset` and `size` don't have to be aligned. + They are internally rounded down/up to multiply of `nonCoherentAtomSize`. +- If `size` is 0, this call is ignored. +- If memory type that the `allocation` belongs to is not `HOST_VISIBLE` or it is `HOST_COHERENT`, + this call is ignored. + +Warning! `offset` and `size` are relative to the contents of given `allocation`. +If you mean whole allocation, you can pass 0 and `VK_WHOLE_SIZE`, respectively. +Do not pass allocation's offset as `offset`!!! + +This function returns the `VkResult` from `vkInvalidateMappedMemoryRanges` if +it is called, otherwise `VK_SUCCESS`. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocation( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize offset, + VkDeviceSize size); + +/** \brief Flushes memory of given set of allocations. + +Calls `vkFlushMappedMemoryRanges()` for memory associated with given ranges of given allocations. +For more information, see documentation of vmaFlushAllocation(). + +\param allocator +\param allocationCount +\param allocations +\param offsets If not null, it must point to an array of offsets of regions to flush, relative to the beginning of respective allocations. Null means all offsets are zero. +\param sizes If not null, it must point to an array of sizes of regions to flush in respective allocations. Null means `VK_WHOLE_SIZE` for all allocations. + +This function returns the `VkResult` from `vkFlushMappedMemoryRanges` if it is +called, otherwise `VK_SUCCESS`. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocations( + VmaAllocator VMA_NOT_NULL allocator, + uint32_t allocationCount, + const VmaAllocation VMA_NOT_NULL* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) allocations, + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) offsets, + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) sizes); + +/** \brief Invalidates memory of given set of allocations. + +Calls `vkInvalidateMappedMemoryRanges()` for memory associated with given ranges of given allocations. +For more information, see documentation of vmaInvalidateAllocation(). + +\param allocator +\param allocationCount +\param allocations +\param offsets If not null, it must point to an array of offsets of regions to flush, relative to the beginning of respective allocations. Null means all offsets are zero. +\param sizes If not null, it must point to an array of sizes of regions to flush in respective allocations. Null means `VK_WHOLE_SIZE` for all allocations. + +This function returns the `VkResult` from `vkInvalidateMappedMemoryRanges` if it is +called, otherwise `VK_SUCCESS`. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocations( + VmaAllocator VMA_NOT_NULL allocator, + uint32_t allocationCount, + const VmaAllocation VMA_NOT_NULL* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) allocations, + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) offsets, + const VkDeviceSize* VMA_NULLABLE VMA_LEN_IF_NOT_NULL(allocationCount) sizes); + +/** \brief Maps the allocation temporarily if needed, copies data from specified host pointer to it, and flushes the memory from the host caches if needed. + +\param allocator +\param pSrcHostPointer Pointer to the host data that become source of the copy. +\param dstAllocation Handle to the allocation that becomes destination of the copy. +\param dstAllocationLocalOffset Offset within `dstAllocation` where to write copied data, in bytes. +\param size Number of bytes to copy. + +This is a convenience function that allows to copy data from a host pointer to an allocation easily. +Same behavior can be achieved by calling vmaMapMemory(), `memcpy()`, vmaUnmapMemory(), vmaFlushAllocation(). + +This function can be called only for allocations created in a memory type that has `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` flag. +It can be ensured e.g. by using #VMA_MEMORY_USAGE_AUTO and #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or +#VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT. +Otherwise, the function will fail and generate a Validation Layers error. + +`dstAllocationLocalOffset` is relative to the contents of given `dstAllocation`. +If you mean whole allocation, you should pass 0. +Do not pass allocation's offset within device memory block this parameter! +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCopyMemoryToAllocation( + VmaAllocator VMA_NOT_NULL allocator, + const void* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(size) pSrcHostPointer, + VmaAllocation VMA_NOT_NULL dstAllocation, + VkDeviceSize dstAllocationLocalOffset, + VkDeviceSize size); + +/** \brief Invalidates memory in the host caches if needed, maps the allocation temporarily if needed, and copies data from it to a specified host pointer. + +\param allocator +\param srcAllocation Handle to the allocation that becomes source of the copy. +\param srcAllocationLocalOffset Offset within `srcAllocation` where to read copied data, in bytes. +\param pDstHostPointer Pointer to the host memory that become destination of the copy. +\param size Number of bytes to copy. + +This is a convenience function that allows to copy data from an allocation to a host pointer easily. +Same behavior can be achieved by calling vmaInvalidateAllocation(), vmaMapMemory(), `memcpy()`, vmaUnmapMemory(). + +This function should be called only for allocations created in a memory type that has `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` +and `VK_MEMORY_PROPERTY_HOST_CACHED_BIT` flag. +It can be ensured e.g. by using #VMA_MEMORY_USAGE_AUTO and #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT. +Otherwise, the function may fail and generate a Validation Layers error. +It may also work very slowly when reading from an uncached memory. + +`srcAllocationLocalOffset` is relative to the contents of given `srcAllocation`. +If you mean whole allocation, you should pass 0. +Do not pass allocation's offset within device memory block as this parameter! +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCopyAllocationToMemory( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL srcAllocation, + VkDeviceSize srcAllocationLocalOffset, + void* VMA_NOT_NULL VMA_LEN_IF_NOT_NULL(size) pDstHostPointer, + VkDeviceSize size); + +/** \brief Checks magic number in margins around all allocations in given memory types (in both default and custom pools) in search for corruptions. + +\param allocator +\param memoryTypeBits Bit mask, where each bit set means that a memory type with that index should be checked. + +Corruption detection is enabled only when `VMA_DEBUG_DETECT_CORRUPTION` macro is defined to nonzero, +`VMA_DEBUG_MARGIN` is defined to nonzero and only for memory types that are +`HOST_VISIBLE` and `HOST_COHERENT`. For more information, see [Corruption detection](@ref debugging_memory_usage_corruption_detection). + +Possible return values: + +- `VK_ERROR_FEATURE_NOT_PRESENT` - corruption detection is not enabled for any of specified memory types. +- `VK_SUCCESS` - corruption detection has been performed and succeeded. +- `VK_ERROR_UNKNOWN` - corruption detection has been performed and found memory corruptions around one of the allocations. + `VMA_ASSERT` is also fired in that case. +- Other value: Error returned by Vulkan, e.g. memory mapping failure. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckCorruption( + VmaAllocator VMA_NOT_NULL allocator, + uint32_t memoryTypeBits); + +/** \brief Begins defragmentation process. + +\param allocator Allocator object. +\param pInfo Structure filled with parameters of defragmentation. +\param[out] pContext Context object that must be passed to vmaEndDefragmentation() to finish defragmentation. +\returns +- `VK_SUCCESS` if defragmentation can begin. +- `VK_ERROR_FEATURE_NOT_PRESENT` if defragmentation is not supported. + +For more information about defragmentation, see documentation chapter: +[Defragmentation](@ref defragmentation). +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBeginDefragmentation( + VmaAllocator VMA_NOT_NULL allocator, + const VmaDefragmentationInfo* VMA_NOT_NULL pInfo, + VmaDefragmentationContext VMA_NULLABLE* VMA_NOT_NULL pContext); + +/** \brief Ends defragmentation process. + +\param allocator Allocator object. +\param context Context object that has been created by vmaBeginDefragmentation(). +\param[out] pStats Optional stats for the defragmentation. Can be null. + +Use this function to finish defragmentation started by vmaBeginDefragmentation(). +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaEndDefragmentation( + VmaAllocator VMA_NOT_NULL allocator, + VmaDefragmentationContext VMA_NOT_NULL context, + VmaDefragmentationStats* VMA_NULLABLE pStats); + +/** \brief Starts single defragmentation pass. + +\param allocator Allocator object. +\param context Context object that has been created by vmaBeginDefragmentation(). +\param[out] pPassInfo Computed information for current pass. +\returns +- `VK_SUCCESS` if no more moves are possible. Then you can omit call to vmaEndDefragmentationPass() and simply end whole defragmentation. +- `VK_INCOMPLETE` if there are pending moves returned in `pPassInfo`. You need to perform them, call vmaEndDefragmentationPass(), + and then preferably try another pass with vmaBeginDefragmentationPass(). +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBeginDefragmentationPass( + VmaAllocator VMA_NOT_NULL allocator, + VmaDefragmentationContext VMA_NOT_NULL context, + VmaDefragmentationPassMoveInfo* VMA_NOT_NULL pPassInfo); + +/** \brief Ends single defragmentation pass. + +\param allocator Allocator object. +\param context Context object that has been created by vmaBeginDefragmentation(). +\param pPassInfo Computed information for current pass filled by vmaBeginDefragmentationPass() and possibly modified by you. + +Returns `VK_SUCCESS` if no more moves are possible or `VK_INCOMPLETE` if more defragmentations are possible. + +Ends incremental defragmentation pass and commits all defragmentation moves from `pPassInfo`. +After this call: + +- Allocations at `pPassInfo[i].srcAllocation` that had `pPassInfo[i].operation ==` #VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY + (which is the default) will be pointing to the new destination place. +- Allocation at `pPassInfo[i].srcAllocation` that had `pPassInfo[i].operation ==` #VMA_DEFRAGMENTATION_MOVE_OPERATION_DESTROY + will be freed. + +If no more moves are possible you can end whole defragmentation. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaEndDefragmentationPass( + VmaAllocator VMA_NOT_NULL allocator, + VmaDefragmentationContext VMA_NOT_NULL context, + VmaDefragmentationPassMoveInfo* VMA_NOT_NULL pPassInfo); + +/** \brief Binds buffer to allocation. + +Binds specified buffer to region of memory represented by specified allocation. +Gets `VkDeviceMemory` handle and offset from the allocation. +If you want to create a buffer, allocate memory for it and bind them together separately, +you should use this function for binding instead of standard `vkBindBufferMemory()`, +because it ensures proper synchronization so that when a `VkDeviceMemory` object is used by multiple +allocations, calls to `vkBind*Memory()` or `vkMapMemory()` won't happen from multiple threads simultaneously +(which is illegal in Vulkan). + +It is recommended to use function vmaCreateBuffer() instead of this one. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkBuffer VMA_NOT_NULL_NON_DISPATCHABLE buffer); + +/** \brief Binds buffer to allocation with additional parameters. + +\param allocator +\param allocation +\param allocationLocalOffset Additional offset to be added while binding, relative to the beginning of the `allocation`. Normally it should be 0. +\param buffer +\param pNext A chain of structures to be attached to `VkBindBufferMemoryInfoKHR` structure used internally. Normally it should be null. + +This function is similar to vmaBindBufferMemory(), but it provides additional parameters. + +If `pNext` is not null, #VmaAllocator object must have been created with #VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT flag +or with VmaAllocatorCreateInfo::vulkanApiVersion `>= VK_API_VERSION_1_1`. Otherwise the call fails. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory2( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize allocationLocalOffset, + VkBuffer VMA_NOT_NULL_NON_DISPATCHABLE buffer, + const void* VMA_NULLABLE VMA_EXTENDS_VK_STRUCT(VkBindBufferMemoryInfoKHR) pNext); + +/** \brief Binds image to allocation. + +Binds specified image to region of memory represented by specified allocation. +Gets `VkDeviceMemory` handle and offset from the allocation. +If you want to create an image, allocate memory for it and bind them together separately, +you should use this function for binding instead of standard `vkBindImageMemory()`, +because it ensures proper synchronization so that when a `VkDeviceMemory` object is used by multiple +allocations, calls to `vkBind*Memory()` or `vkMapMemory()` won't happen from multiple threads simultaneously +(which is illegal in Vulkan). + +It is recommended to use function vmaCreateImage() instead of this one. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkImage VMA_NOT_NULL_NON_DISPATCHABLE image); + +/** \brief Binds image to allocation with additional parameters. + +\param allocator +\param allocation +\param allocationLocalOffset Additional offset to be added while binding, relative to the beginning of the `allocation`. Normally it should be 0. +\param image +\param pNext A chain of structures to be attached to `VkBindImageMemoryInfoKHR` structure used internally. Normally it should be null. + +This function is similar to vmaBindImageMemory(), but it provides additional parameters. + +If `pNext` is not null, #VmaAllocator object must have been created with #VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT flag +or with VmaAllocatorCreateInfo::vulkanApiVersion `>= VK_API_VERSION_1_1`. Otherwise the call fails. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory2( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize allocationLocalOffset, + VkImage VMA_NOT_NULL_NON_DISPATCHABLE image, + const void* VMA_NULLABLE VMA_EXTENDS_VK_STRUCT(VkBindImageMemoryInfoKHR) pNext); + +/** \brief Creates a new `VkBuffer`, allocates and binds memory for it. + +\param allocator +\param pBufferCreateInfo +\param pAllocationCreateInfo +\param[out] pBuffer Buffer that was created. +\param[out] pAllocation Allocation that was created. +\param[out] pAllocationInfo Optional. Information about allocated memory. It can be later fetched using function vmaGetAllocationInfo(). + +This function automatically: + +-# Creates buffer. +-# Allocates appropriate memory for it. +-# Binds the buffer with the memory. + +If any of these operations fail, buffer and allocation are not created, +returned value is negative error code, `*pBuffer` and `*pAllocation` are null. + +If the function succeeded, you must destroy both buffer and allocation when you +no longer need them using either convenience function vmaDestroyBuffer() or +separately, using `vkDestroyBuffer()` and vmaFreeMemory(). + +If #VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT flag was used, +VK_KHR_dedicated_allocation extension is used internally to query driver whether +it requires or prefers the new buffer to have dedicated allocation. If yes, +and if dedicated allocation is possible +(#VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT is not used), it creates dedicated +allocation for this buffer, just like when using +#VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. + +\note This function creates a new `VkBuffer`. Sub-allocation of parts of one large buffer, +although recommended as a good practice, is out of scope of this library and could be implemented +by the user as a higher-level logic on top of VMA. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer( + VmaAllocator VMA_NOT_NULL allocator, + const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo, + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + VkBuffer VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pBuffer, + VmaAllocation VMA_NULLABLE* VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); + +/** \brief Creates a buffer with additional minimum alignment. + +Similar to vmaCreateBuffer() but provides additional parameter `minAlignment` which allows to specify custom, +minimum alignment to be used when placing the buffer inside a larger memory block, which may be needed e.g. +for interop with OpenGL. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBufferWithAlignment( + VmaAllocator VMA_NOT_NULL allocator, + const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo, + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + VkDeviceSize minAlignment, + VkBuffer VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pBuffer, + VmaAllocation VMA_NULLABLE* VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); + +/** \brief Creates a new `VkBuffer`, binds already created memory for it. + +\param allocator +\param allocation Allocation that provides memory to be used for binding new buffer to it. +\param pBufferCreateInfo +\param[out] pBuffer Buffer that was created. + +This function automatically: + +-# Creates buffer. +-# Binds the buffer with the supplied memory. + +If any of these operations fail, buffer is not created, +returned value is negative error code and `*pBuffer` is null. + +If the function succeeded, you must destroy the buffer when you +no longer need it using `vkDestroyBuffer()`. If you want to also destroy the corresponding +allocation you can use convenience function vmaDestroyBuffer(). + +\note There is a new version of this function augmented with parameter `allocationLocalOffset` - see vmaCreateAliasingBuffer2(). +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAliasingBuffer( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo, + VkBuffer VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pBuffer); + +/** \brief Creates a new `VkBuffer`, binds already created memory for it. + +\param allocator +\param allocation Allocation that provides memory to be used for binding new buffer to it. +\param allocationLocalOffset Additional offset to be added while binding, relative to the beginning of the allocation. Normally it should be 0. +\param pBufferCreateInfo +\param[out] pBuffer Buffer that was created. + +This function automatically: + +-# Creates buffer. +-# Binds the buffer with the supplied memory. + +If any of these operations fail, buffer is not created, +returned value is negative error code and `*pBuffer` is null. + +If the function succeeded, you must destroy the buffer when you +no longer need it using `vkDestroyBuffer()`. If you want to also destroy the corresponding +allocation you can use convenience function vmaDestroyBuffer(). + +\note This is a new version of the function augmented with parameter `allocationLocalOffset`. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAliasingBuffer2( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize allocationLocalOffset, + const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo, + VkBuffer VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pBuffer); + +/** \brief Destroys Vulkan buffer and frees allocated memory. + +This is just a convenience function equivalent to: + +\code +vkDestroyBuffer(device, buffer, allocationCallbacks); +vmaFreeMemory(allocator, allocation); +\endcode + +It is safe to pass null as buffer and/or allocation. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyBuffer( + VmaAllocator VMA_NOT_NULL allocator, + VkBuffer VMA_NULLABLE_NON_DISPATCHABLE buffer, + VmaAllocation VMA_NULLABLE allocation); + +/// Function similar to vmaCreateBuffer(). +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateImage( + VmaAllocator VMA_NOT_NULL allocator, + const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo, + const VmaAllocationCreateInfo* VMA_NOT_NULL pAllocationCreateInfo, + VkImage VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pImage, + VmaAllocation VMA_NULLABLE* VMA_NOT_NULL pAllocation, + VmaAllocationInfo* VMA_NULLABLE pAllocationInfo); + +/// Function similar to vmaCreateAliasingBuffer() but for images. +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAliasingImage( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo, + VkImage VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pImage); + +/// Function similar to vmaCreateAliasingBuffer2() but for images. +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAliasingImage2( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize allocationLocalOffset, + const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo, + VkImage VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pImage); + +/** \brief Destroys Vulkan image and frees allocated memory. + +This is just a convenience function equivalent to: + +\code +vkDestroyImage(device, image, allocationCallbacks); +vmaFreeMemory(allocator, allocation); +\endcode + +It is safe to pass null as image and/or allocation. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyImage( + VmaAllocator VMA_NOT_NULL allocator, + VkImage VMA_NULLABLE_NON_DISPATCHABLE image, + VmaAllocation VMA_NULLABLE allocation); + +/** @} */ + +/** +\addtogroup group_virtual +@{ +*/ + +/** \brief Creates new #VmaVirtualBlock object. + +\param pCreateInfo Parameters for creation. +\param[out] pVirtualBlock Returned virtual block object or `VMA_NULL` if creation failed. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateVirtualBlock( + const VmaVirtualBlockCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaVirtualBlock VMA_NULLABLE* VMA_NOT_NULL pVirtualBlock); + +/** \brief Destroys #VmaVirtualBlock object. + +Please note that you should consciously handle virtual allocations that could remain unfreed in the block. +You should either free them individually using vmaVirtualFree() or call vmaClearVirtualBlock() +if you are sure this is what you want. If you do neither, an assert is called. + +If you keep pointers to some additional metadata associated with your virtual allocations in their `pUserData`, +don't forget to free them. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyVirtualBlock( + VmaVirtualBlock VMA_NULLABLE virtualBlock); + +/** \brief Returns true of the #VmaVirtualBlock is empty - contains 0 virtual allocations and has all its space available for new allocations. +*/ +VMA_CALL_PRE VkBool32 VMA_CALL_POST vmaIsVirtualBlockEmpty( + VmaVirtualBlock VMA_NOT_NULL virtualBlock); + +/** \brief Returns information about a specific virtual allocation within a virtual block, like its size and `pUserData` pointer. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetVirtualAllocationInfo( + VmaVirtualBlock VMA_NOT_NULL virtualBlock, + VmaVirtualAllocation VMA_NOT_NULL_NON_DISPATCHABLE allocation, VmaVirtualAllocationInfo* VMA_NOT_NULL pVirtualAllocInfo); + +/** \brief Allocates new virtual allocation inside given #VmaVirtualBlock. + +If the allocation fails due to not enough free space available, `VK_ERROR_OUT_OF_DEVICE_MEMORY` is returned +(despite the function doesn't ever allocate actual GPU memory). +`pAllocation` is then set to `VK_NULL_HANDLE` and `pOffset`, if not null, it set to `UINT64_MAX`. + +\param virtualBlock Virtual block +\param pCreateInfo Parameters for the allocation +\param[out] pAllocation Returned handle of the new allocation +\param[out] pOffset Returned offset of the new allocation. Optional, can be null. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaVirtualAllocate( + VmaVirtualBlock VMA_NOT_NULL virtualBlock, + const VmaVirtualAllocationCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaVirtualAllocation VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pAllocation, + VkDeviceSize* VMA_NULLABLE pOffset); + +/** \brief Frees virtual allocation inside given #VmaVirtualBlock. + +It is correct to call this function with `allocation == VK_NULL_HANDLE` - it does nothing. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaVirtualFree( + VmaVirtualBlock VMA_NOT_NULL virtualBlock, + VmaVirtualAllocation VMA_NULLABLE_NON_DISPATCHABLE allocation); + +/** \brief Frees all virtual allocations inside given #VmaVirtualBlock. + +You must either call this function or free each virtual allocation individually with vmaVirtualFree() +before destroying a virtual block. Otherwise, an assert is called. + +If you keep pointer to some additional metadata associated with your virtual allocation in its `pUserData`, +don't forget to free it as well. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaClearVirtualBlock( + VmaVirtualBlock VMA_NOT_NULL virtualBlock); + +/** \brief Changes custom pointer associated with given virtual allocation. +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaSetVirtualAllocationUserData( + VmaVirtualBlock VMA_NOT_NULL virtualBlock, + VmaVirtualAllocation VMA_NOT_NULL_NON_DISPATCHABLE allocation, + void* VMA_NULLABLE pUserData); + +/** \brief Calculates and returns statistics about virtual allocations and memory usage in given #VmaVirtualBlock. + +This function is fast to call. For more detailed statistics, see vmaCalculateVirtualBlockStatistics(). +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaGetVirtualBlockStatistics( + VmaVirtualBlock VMA_NOT_NULL virtualBlock, + VmaStatistics* VMA_NOT_NULL pStats); + +/** \brief Calculates and returns detailed statistics about virtual allocations and memory usage in given #VmaVirtualBlock. + +This function is slow to call. Use for debugging purposes. +For less detailed statistics, see vmaGetVirtualBlockStatistics(). +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaCalculateVirtualBlockStatistics( + VmaVirtualBlock VMA_NOT_NULL virtualBlock, + VmaDetailedStatistics* VMA_NOT_NULL pStats); + +/** @} */ + +#if VMA_STATS_STRING_ENABLED +/** +\addtogroup group_stats +@{ +*/ + +/** \brief Builds and returns a null-terminated string in JSON format with information about given #VmaVirtualBlock. +\param virtualBlock Virtual block. +\param[out] ppStatsString Returned string. +\param detailedMap Pass `VK_FALSE` to only obtain statistics as returned by vmaCalculateVirtualBlockStatistics(). Pass `VK_TRUE` to also obtain full list of allocations and free spaces. + +Returned string must be freed using vmaFreeVirtualBlockStatsString(). +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaBuildVirtualBlockStatsString( + VmaVirtualBlock VMA_NOT_NULL virtualBlock, + char* VMA_NULLABLE* VMA_NOT_NULL ppStatsString, + VkBool32 detailedMap); + +/// Frees a string returned by vmaBuildVirtualBlockStatsString(). +VMA_CALL_PRE void VMA_CALL_POST vmaFreeVirtualBlockStatsString( + VmaVirtualBlock VMA_NOT_NULL virtualBlock, + char* VMA_NULLABLE pStatsString); + +/** \brief Builds and returns statistics as a null-terminated string in JSON format. +\param allocator +\param[out] ppStatsString Must be freed using vmaFreeStatsString() function. +\param detailedMap +*/ +VMA_CALL_PRE void VMA_CALL_POST vmaBuildStatsString( + VmaAllocator VMA_NOT_NULL allocator, + char* VMA_NULLABLE* VMA_NOT_NULL ppStatsString, + VkBool32 detailedMap); + +VMA_CALL_PRE void VMA_CALL_POST vmaFreeStatsString( + VmaAllocator VMA_NOT_NULL allocator, + char* VMA_NULLABLE pStatsString); + +/** @} */ + +#endif // VMA_STATS_STRING_ENABLED + +#endif // _VMA_FUNCTION_HEADERS + +#ifdef __cplusplus +} +#endif + +#endif // AMD_VULKAN_MEMORY_ALLOCATOR_H + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +// For Visual Studio IntelliSense. +#if defined(__cplusplus) && defined(__INTELLISENSE__) +#define VMA_IMPLEMENTATION +#endif + +#ifdef VMA_IMPLEMENTATION +#undef VMA_IMPLEMENTATION + +#include +#include +#include +#include +#include +#include + +#if !defined(VMA_CPP20) + #if __cplusplus >= 202002L || _MSVC_LANG >= 202002L // C++20 + #define VMA_CPP20 1 + #else + #define VMA_CPP20 0 + #endif +#endif + +#ifdef _MSC_VER + #include // For functions like __popcnt, _BitScanForward etc. +#endif +#if VMA_CPP20 + #include +#endif + +#if VMA_STATS_STRING_ENABLED + #include // For snprintf +#endif + +/******************************************************************************* +CONFIGURATION SECTION + +Define some of these macros before each #include of this header or change them +here if you need other then default behavior depending on your environment. +*/ +#ifndef _VMA_CONFIGURATION + +/* +Define this macro to 1 to make the library fetch pointers to Vulkan functions +internally, like: + + vulkanFunctions.vkAllocateMemory = &vkAllocateMemory; +*/ +#if !defined(VMA_STATIC_VULKAN_FUNCTIONS) && !defined(VK_NO_PROTOTYPES) + #define VMA_STATIC_VULKAN_FUNCTIONS 1 +#endif + +/* +Define this macro to 1 to make the library fetch pointers to Vulkan functions +internally, like: + + vulkanFunctions.vkAllocateMemory = (PFN_vkAllocateMemory)vkGetDeviceProcAddr(device, "vkAllocateMemory"); + +To use this feature in new versions of VMA you now have to pass +VmaVulkanFunctions::vkGetInstanceProcAddr and vkGetDeviceProcAddr as +VmaAllocatorCreateInfo::pVulkanFunctions. Other members can be null. +*/ +#if !defined(VMA_DYNAMIC_VULKAN_FUNCTIONS) + #define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#endif + +#ifndef VMA_USE_STL_SHARED_MUTEX + #if __cplusplus >= 201703L || _MSVC_LANG >= 201703L // C++17 + #define VMA_USE_STL_SHARED_MUTEX 1 + // Visual studio defines __cplusplus properly only when passed additional parameter: /Zc:__cplusplus + // Otherwise it is always 199711L, despite shared_mutex works since Visual Studio 2015 Update 2. + #elif defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023918 && __cplusplus == 199711L && _MSVC_LANG >= 201703L + #define VMA_USE_STL_SHARED_MUTEX 1 + #else + #define VMA_USE_STL_SHARED_MUTEX 0 + #endif +#endif + +/* +Define this macro to include custom header files without having to edit this file directly, e.g.: + + // Inside of "my_vma_configuration_user_includes.h": + + #include "my_custom_assert.h" // for MY_CUSTOM_ASSERT + #include "my_custom_min.h" // for my_custom_min + #include + #include + + // Inside a different file, which includes "vk_mem_alloc.h": + + #define VMA_CONFIGURATION_USER_INCLUDES_H "my_vma_configuration_user_includes.h" + #define VMA_ASSERT(expr) MY_CUSTOM_ASSERT(expr) + #define VMA_MIN(v1, v2) (my_custom_min(v1, v2)) + #include "vk_mem_alloc.h" + ... + +The following headers are used in this CONFIGURATION section only, so feel free to +remove them if not needed. +*/ +#if !defined(VMA_CONFIGURATION_USER_INCLUDES_H) + #include // for assert + #include // for min, max, swap + #include +#else + #include VMA_CONFIGURATION_USER_INCLUDES_H +#endif + +#ifndef VMA_NULL + // Value used as null pointer. Define it to e.g.: nullptr, NULL, 0, (void*)0. + #define VMA_NULL nullptr +#endif + +#ifndef VMA_FALLTHROUGH + #if __cplusplus >= 201703L || _MSVC_LANG >= 201703L // C++17 + #define VMA_FALLTHROUGH [[fallthrough]] + #else + #define VMA_FALLTHROUGH + #endif +#endif + +// Normal assert to check for programmer's errors, especially in Debug configuration. +#ifndef VMA_ASSERT + #ifdef NDEBUG + #define VMA_ASSERT(expr) + #else + #define VMA_ASSERT(expr) assert(expr) + #endif +#endif + +// Assert that will be called very often, like inside data structures e.g. operator[]. +// Making it non-empty can make program slow. +#ifndef VMA_HEAVY_ASSERT + #ifdef NDEBUG + #define VMA_HEAVY_ASSERT(expr) + #else + #define VMA_HEAVY_ASSERT(expr) //VMA_ASSERT(expr) + #endif +#endif + +// Assert used for reporting memory leaks - unfreed allocations. +#ifndef VMA_ASSERT_LEAK + #define VMA_ASSERT_LEAK(expr) VMA_ASSERT(expr) +#endif + +// If your compiler is not compatible with C++17 and definition of +// aligned_alloc() function is missing, uncommenting following line may help: + +//#include + +#if defined(__ANDROID_API__) && (__ANDROID_API__ < 16) +#include +static void* vma_aligned_alloc(size_t alignment, size_t size) +{ + // alignment must be >= sizeof(void*) + if(alignment < sizeof(void*)) + { + alignment = sizeof(void*); + } + + return memalign(alignment, size); +} +#elif defined(__APPLE__) || defined(__ANDROID__) || (defined(__linux__) && defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC)) +#include + +#if defined(__APPLE__) +#include +#endif + +static void* vma_aligned_alloc(size_t alignment, size_t size) +{ + // Unfortunately, aligned_alloc causes VMA to crash due to it returning null pointers. (At least under 11.4) + // Therefore, for now disable this specific exception until a proper solution is found. + //#if defined(__APPLE__) && (defined(MAC_OS_X_VERSION_10_16) || defined(__IPHONE_14_0)) + //#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_16 || __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0 + // // For C++14, usr/include/malloc/_malloc.h declares aligned_alloc()) only + // // with the MacOSX11.0 SDK in Xcode 12 (which is what adds + // // MAC_OS_X_VERSION_10_16), even though the function is marked + // // available for 10.15. That is why the preprocessor checks for 10.16 but + // // the __builtin_available checks for 10.15. + // // People who use C++17 could call aligned_alloc with the 10.15 SDK already. + // if (__builtin_available(macOS 10.15, iOS 13, *)) + // return aligned_alloc(alignment, size); + //#endif + //#endif + + // alignment must be >= sizeof(void*) + if(alignment < sizeof(void*)) + { + alignment = sizeof(void*); + } + + void *pointer; + if(posix_memalign(&pointer, alignment, size) == 0) + return pointer; + return VMA_NULL; +} +#elif defined(_WIN32) +static void* vma_aligned_alloc(size_t alignment, size_t size) +{ + return _aligned_malloc(size, alignment); +} +#elif __cplusplus >= 201703L || _MSVC_LANG >= 201703L // C++17 +static void* vma_aligned_alloc(size_t alignment, size_t size) +{ + return aligned_alloc(alignment, size); +} +#else +static void* vma_aligned_alloc(size_t alignment, size_t size) +{ + VMA_ASSERT(0 && "Could not implement aligned_alloc automatically. Please enable C++17 or later in your compiler or provide custom implementation of macro VMA_SYSTEM_ALIGNED_MALLOC (and VMA_SYSTEM_ALIGNED_FREE if needed) using the API of your system."); + return VMA_NULL; +} +#endif + +#if defined(_WIN32) +static void vma_aligned_free(void* ptr) +{ + _aligned_free(ptr); +} +#else +static void vma_aligned_free(void* VMA_NULLABLE ptr) +{ + free(ptr); +} +#endif + +#ifndef VMA_ALIGN_OF + #define VMA_ALIGN_OF(type) (alignof(type)) +#endif + +#ifndef VMA_SYSTEM_ALIGNED_MALLOC + #define VMA_SYSTEM_ALIGNED_MALLOC(size, alignment) vma_aligned_alloc((alignment), (size)) +#endif + +#ifndef VMA_SYSTEM_ALIGNED_FREE + // VMA_SYSTEM_FREE is the old name, but might have been defined by the user + #if defined(VMA_SYSTEM_FREE) + #define VMA_SYSTEM_ALIGNED_FREE(ptr) VMA_SYSTEM_FREE(ptr) + #else + #define VMA_SYSTEM_ALIGNED_FREE(ptr) vma_aligned_free(ptr) + #endif +#endif + +#ifndef VMA_COUNT_BITS_SET + // Returns number of bits set to 1 in (v) + #define VMA_COUNT_BITS_SET(v) VmaCountBitsSet(v) +#endif + +#ifndef VMA_BITSCAN_LSB + // Scans integer for index of first nonzero value from the Least Significant Bit (LSB). If mask is 0 then returns UINT8_MAX + #define VMA_BITSCAN_LSB(mask) VmaBitScanLSB(mask) +#endif + +#ifndef VMA_BITSCAN_MSB + // Scans integer for index of first nonzero value from the Most Significant Bit (MSB). If mask is 0 then returns UINT8_MAX + #define VMA_BITSCAN_MSB(mask) VmaBitScanMSB(mask) +#endif + +#ifndef VMA_MIN + #define VMA_MIN(v1, v2) ((std::min)((v1), (v2))) +#endif + +#ifndef VMA_MAX + #define VMA_MAX(v1, v2) ((std::max)((v1), (v2))) +#endif + +#ifndef VMA_SORT + #define VMA_SORT(beg, end, cmp) std::sort(beg, end, cmp) +#endif + +#ifndef VMA_DEBUG_LOG_FORMAT + #define VMA_DEBUG_LOG_FORMAT(format, ...) + /* + #define VMA_DEBUG_LOG_FORMAT(format, ...) do { \ + printf((format), __VA_ARGS__); \ + printf("\n"); \ + } while(false) + */ +#endif + +#ifndef VMA_DEBUG_LOG + #define VMA_DEBUG_LOG(str) VMA_DEBUG_LOG_FORMAT("%s", (str)) +#endif + +#ifndef VMA_LEAK_LOG_FORMAT + #define VMA_LEAK_LOG_FORMAT(format, ...) VMA_DEBUG_LOG_FORMAT(format, __VA_ARGS__) +#endif + +#ifndef VMA_CLASS_NO_COPY + #define VMA_CLASS_NO_COPY(className) \ + private: \ + className(const className&) = delete; \ + className& operator=(const className&) = delete; +#endif +#ifndef VMA_CLASS_NO_COPY_NO_MOVE + #define VMA_CLASS_NO_COPY_NO_MOVE(className) \ + private: \ + className(const className&) = delete; \ + className(className&&) = delete; \ + className& operator=(const className&) = delete; \ + className& operator=(className&&) = delete; +#endif + +// Define this macro to 1 to enable functions: vmaBuildStatsString, vmaFreeStatsString. +#if VMA_STATS_STRING_ENABLED + static inline void VmaUint32ToStr(char* VMA_NOT_NULL outStr, size_t strLen, uint32_t num) + { + snprintf(outStr, strLen, "%" PRIu32, num); + } + static inline void VmaUint64ToStr(char* VMA_NOT_NULL outStr, size_t strLen, uint64_t num) + { + snprintf(outStr, strLen, "%" PRIu64, num); + } + static inline void VmaPtrToStr(char* VMA_NOT_NULL outStr, size_t strLen, const void* ptr) + { + snprintf(outStr, strLen, "%p", ptr); + } +#endif + +#ifndef VMA_MUTEX + class VmaMutex + { + VMA_CLASS_NO_COPY_NO_MOVE(VmaMutex) + public: + VmaMutex() { } + void Lock() { m_Mutex.lock(); } + void Unlock() { m_Mutex.unlock(); } + bool TryLock() { return m_Mutex.try_lock(); } + private: + std::mutex m_Mutex; + }; + #define VMA_MUTEX VmaMutex +#endif + +// Read-write mutex, where "read" is shared access, "write" is exclusive access. +#ifndef VMA_RW_MUTEX + #if VMA_USE_STL_SHARED_MUTEX + // Use std::shared_mutex from C++17. + #include + class VmaRWMutex + { + public: + void LockRead() { m_Mutex.lock_shared(); } + void UnlockRead() { m_Mutex.unlock_shared(); } + bool TryLockRead() { return m_Mutex.try_lock_shared(); } + void LockWrite() { m_Mutex.lock(); } + void UnlockWrite() { m_Mutex.unlock(); } + bool TryLockWrite() { return m_Mutex.try_lock(); } + private: + std::shared_mutex m_Mutex; + }; + #define VMA_RW_MUTEX VmaRWMutex + #elif defined(_WIN32) && defined(WINVER) && defined(SRWLOCK_INIT) && WINVER >= 0x0600 + // Use SRWLOCK from WinAPI. + // Minimum supported client = Windows Vista, server = Windows Server 2008. + class VmaRWMutex + { + public: + VmaRWMutex() { InitializeSRWLock(&m_Lock); } + void LockRead() { AcquireSRWLockShared(&m_Lock); } + void UnlockRead() { ReleaseSRWLockShared(&m_Lock); } + bool TryLockRead() { return TryAcquireSRWLockShared(&m_Lock) != FALSE; } + void LockWrite() { AcquireSRWLockExclusive(&m_Lock); } + void UnlockWrite() { ReleaseSRWLockExclusive(&m_Lock); } + bool TryLockWrite() { return TryAcquireSRWLockExclusive(&m_Lock) != FALSE; } + private: + SRWLOCK m_Lock; + }; + #define VMA_RW_MUTEX VmaRWMutex + #else + // Less efficient fallback: Use normal mutex. + class VmaRWMutex + { + public: + void LockRead() { m_Mutex.Lock(); } + void UnlockRead() { m_Mutex.Unlock(); } + bool TryLockRead() { return m_Mutex.TryLock(); } + void LockWrite() { m_Mutex.Lock(); } + void UnlockWrite() { m_Mutex.Unlock(); } + bool TryLockWrite() { return m_Mutex.TryLock(); } + private: + VMA_MUTEX m_Mutex; + }; + #define VMA_RW_MUTEX VmaRWMutex + #endif // #if VMA_USE_STL_SHARED_MUTEX +#endif // #ifndef VMA_RW_MUTEX + +/* +If providing your own implementation, you need to implement a subset of std::atomic. +*/ +#ifndef VMA_ATOMIC_UINT32 + #include + #define VMA_ATOMIC_UINT32 std::atomic +#endif + +#ifndef VMA_ATOMIC_UINT64 + #include + #define VMA_ATOMIC_UINT64 std::atomic +#endif + +#ifndef VMA_DEBUG_ALWAYS_DEDICATED_MEMORY + /** + Every allocation will have its own memory block. + Define to 1 for debugging purposes only. + */ + #define VMA_DEBUG_ALWAYS_DEDICATED_MEMORY (0) +#endif + +#ifndef VMA_MIN_ALIGNMENT + /** + Minimum alignment of all allocations, in bytes. + Set to more than 1 for debugging purposes. Must be power of two. + */ + #ifdef VMA_DEBUG_ALIGNMENT // Old name + #define VMA_MIN_ALIGNMENT VMA_DEBUG_ALIGNMENT + #else + #define VMA_MIN_ALIGNMENT (1) + #endif +#endif + +#ifndef VMA_DEBUG_MARGIN + /** + Minimum margin after every allocation, in bytes. + Set nonzero for debugging purposes only. + */ + #define VMA_DEBUG_MARGIN (0) +#endif + +#ifndef VMA_DEBUG_INITIALIZE_ALLOCATIONS + /** + Define this macro to 1 to automatically fill new allocations and destroyed + allocations with some bit pattern. + */ + #define VMA_DEBUG_INITIALIZE_ALLOCATIONS (0) +#endif + +#ifndef VMA_DEBUG_DETECT_CORRUPTION + /** + Define this macro to 1 together with non-zero value of VMA_DEBUG_MARGIN to + enable writing magic value to the margin after every allocation and + validating it, so that memory corruptions (out-of-bounds writes) are detected. + */ + #define VMA_DEBUG_DETECT_CORRUPTION (0) +#endif + +#ifndef VMA_DEBUG_GLOBAL_MUTEX + /** + Set this to 1 for debugging purposes only, to enable single mutex protecting all + entry calls to the library. Can be useful for debugging multithreading issues. + */ + #define VMA_DEBUG_GLOBAL_MUTEX (0) +#endif + +#ifndef VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY + /** + Minimum value for VkPhysicalDeviceLimits::bufferImageGranularity. + Set to more than 1 for debugging purposes only. Must be power of two. + */ + #define VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY (1) +#endif + +#ifndef VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT + /* + Set this to 1 to make VMA never exceed VkPhysicalDeviceLimits::maxMemoryAllocationCount + and return error instead of leaving up to Vulkan implementation what to do in such cases. + */ + #define VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT (0) +#endif + +#ifndef VMA_SMALL_HEAP_MAX_SIZE + /// Maximum size of a memory heap in Vulkan to consider it "small". + #define VMA_SMALL_HEAP_MAX_SIZE (1024ull * 1024 * 1024) +#endif + +#ifndef VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE + /// Default size of a block allocated as single VkDeviceMemory from a "large" heap. + #define VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE (256ull * 1024 * 1024) +#endif + +/* +Mapping hysteresis is a logic that launches when vmaMapMemory/vmaUnmapMemory is called +or a persistently mapped allocation is created and destroyed several times in a row. +It keeps additional +1 mapping of a device memory block to prevent calling actual +vkMapMemory/vkUnmapMemory too many times, which may improve performance and help +tools like RenderDoc. +*/ +#ifndef VMA_MAPPING_HYSTERESIS_ENABLED + #define VMA_MAPPING_HYSTERESIS_ENABLED 1 +#endif + +#define VMA_VALIDATE(cond) do { if(!(cond)) { \ + VMA_ASSERT(0 && "Validation failed: " #cond); \ + return false; \ + } } while(false) + +/******************************************************************************* +END OF CONFIGURATION +*/ +#endif // _VMA_CONFIGURATION + + +static const uint8_t VMA_ALLOCATION_FILL_PATTERN_CREATED = 0xDC; +static const uint8_t VMA_ALLOCATION_FILL_PATTERN_DESTROYED = 0xEF; +// Decimal 2139416166, float NaN, little-endian binary 66 E6 84 7F. +static const uint32_t VMA_CORRUPTION_DETECTION_MAGIC_VALUE = 0x7F84E666; + +// Copy of some Vulkan definitions so we don't need to check their existence just to handle few constants. +static const uint32_t VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY = 0x00000040; +static const uint32_t VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY = 0x00000080; +static const uint32_t VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_COPY = 0x00020000; +static const uint32_t VK_IMAGE_CREATE_DISJOINT_BIT_COPY = 0x00000200; +static const int32_t VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT_COPY = 1000158000; +static const uint32_t VMA_ALLOCATION_INTERNAL_STRATEGY_MIN_OFFSET = 0x10000000u; +static const uint32_t VMA_ALLOCATION_TRY_COUNT = 32; +static const uint32_t VMA_VENDOR_ID_AMD = 4098; + +// This one is tricky. Vulkan specification defines this code as available since +// Vulkan 1.0, but doesn't actually define it in Vulkan SDK earlier than 1.2.131. +// See pull request #207. +#define VK_ERROR_UNKNOWN_COPY ((VkResult)-13) + + +#if VMA_STATS_STRING_ENABLED +// Correspond to values of enum VmaSuballocationType. +static const char* VMA_SUBALLOCATION_TYPE_NAMES[] = +{ + "FREE", + "UNKNOWN", + "BUFFER", + "IMAGE_UNKNOWN", + "IMAGE_LINEAR", + "IMAGE_OPTIMAL", +}; +#endif + +static VkAllocationCallbacks VmaEmptyAllocationCallbacks = + { VMA_NULL, VMA_NULL, VMA_NULL, VMA_NULL, VMA_NULL, VMA_NULL }; + + +#ifndef _VMA_ENUM_DECLARATIONS + +enum VmaSuballocationType +{ + VMA_SUBALLOCATION_TYPE_FREE = 0, + VMA_SUBALLOCATION_TYPE_UNKNOWN = 1, + VMA_SUBALLOCATION_TYPE_BUFFER = 2, + VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN = 3, + VMA_SUBALLOCATION_TYPE_IMAGE_LINEAR = 4, + VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL = 5, + VMA_SUBALLOCATION_TYPE_MAX_ENUM = 0x7FFFFFFF +}; + +enum VMA_CACHE_OPERATION +{ + VMA_CACHE_FLUSH, + VMA_CACHE_INVALIDATE +}; + +enum class VmaAllocationRequestType +{ + Normal, + TLSF, + // Used by "Linear" algorithm. + UpperAddress, + EndOf1st, + EndOf2nd, +}; + +#endif // _VMA_ENUM_DECLARATIONS + +#ifndef _VMA_FORWARD_DECLARATIONS +// Opaque handle used by allocation algorithms to identify single allocation in any conforming way. +VK_DEFINE_NON_DISPATCHABLE_HANDLE(VmaAllocHandle); + +struct VmaMutexLock; +struct VmaMutexLockRead; +struct VmaMutexLockWrite; + +template +struct AtomicTransactionalIncrement; + +template +struct VmaStlAllocator; + +template +class VmaVector; + +template +class VmaSmallVector; + +template +class VmaPoolAllocator; + +template +struct VmaListItem; + +template +class VmaRawList; + +template +class VmaList; + +template +class VmaIntrusiveLinkedList; + +#if VMA_STATS_STRING_ENABLED +class VmaStringBuilder; +class VmaJsonWriter; +#endif + +class VmaDeviceMemoryBlock; + +struct VmaDedicatedAllocationListItemTraits; +class VmaDedicatedAllocationList; + +struct VmaSuballocation; +struct VmaSuballocationOffsetLess; +struct VmaSuballocationOffsetGreater; +struct VmaSuballocationItemSizeLess; + +typedef VmaList> VmaSuballocationList; + +struct VmaAllocationRequest; + +class VmaBlockMetadata; +class VmaBlockMetadata_Linear; +class VmaBlockMetadata_TLSF; + +class VmaBlockVector; + +struct VmaPoolListItemTraits; + +struct VmaCurrentBudgetData; + +class VmaAllocationObjectAllocator; + +#endif // _VMA_FORWARD_DECLARATIONS + + +#ifndef _VMA_FUNCTIONS + +/* +Returns number of bits set to 1 in (v). + +On specific platforms and compilers you can use intrinsics like: + +Visual Studio: + return __popcnt(v); +GCC, Clang: + return static_cast(__builtin_popcount(v)); + +Define macro VMA_COUNT_BITS_SET to provide your optimized implementation. +But you need to check in runtime whether user's CPU supports these, as some old processors don't. +*/ +static inline uint32_t VmaCountBitsSet(uint32_t v) +{ +#if VMA_CPP20 + return std::popcount(v); +#else + uint32_t c = v - ((v >> 1) & 0x55555555); + c = ((c >> 2) & 0x33333333) + (c & 0x33333333); + c = ((c >> 4) + c) & 0x0F0F0F0F; + c = ((c >> 8) + c) & 0x00FF00FF; + c = ((c >> 16) + c) & 0x0000FFFF; + return c; +#endif +} + +static inline uint8_t VmaBitScanLSB(uint64_t mask) +{ +#if defined(_MSC_VER) && defined(_WIN64) + unsigned long pos; + if (_BitScanForward64(&pos, mask)) + return static_cast(pos); + return UINT8_MAX; +#elif VMA_CPP20 + if(mask) + return static_cast(std::countr_zero(mask)); + return UINT8_MAX; +#elif defined __GNUC__ || defined __clang__ + return static_cast(__builtin_ffsll(mask)) - 1U; +#else + uint8_t pos = 0; + uint64_t bit = 1; + do + { + if (mask & bit) + return pos; + bit <<= 1; + } while (pos++ < 63); + return UINT8_MAX; +#endif +} + +static inline uint8_t VmaBitScanLSB(uint32_t mask) +{ +#ifdef _MSC_VER + unsigned long pos; + if (_BitScanForward(&pos, mask)) + return static_cast(pos); + return UINT8_MAX; +#elif VMA_CPP20 + if(mask) + return static_cast(std::countr_zero(mask)); + return UINT8_MAX; +#elif defined __GNUC__ || defined __clang__ + return static_cast(__builtin_ffs(mask)) - 1U; +#else + uint8_t pos = 0; + uint32_t bit = 1; + do + { + if (mask & bit) + return pos; + bit <<= 1; + } while (pos++ < 31); + return UINT8_MAX; +#endif +} + +static inline uint8_t VmaBitScanMSB(uint64_t mask) +{ +#if defined(_MSC_VER) && defined(_WIN64) + unsigned long pos; + if (_BitScanReverse64(&pos, mask)) + return static_cast(pos); +#elif VMA_CPP20 + if(mask) + return 63 - static_cast(std::countl_zero(mask)); +#elif defined __GNUC__ || defined __clang__ + if (mask) + return 63 - static_cast(__builtin_clzll(mask)); +#else + uint8_t pos = 63; + uint64_t bit = 1ULL << 63; + do + { + if (mask & bit) + return pos; + bit >>= 1; + } while (pos-- > 0); +#endif + return UINT8_MAX; +} + +static inline uint8_t VmaBitScanMSB(uint32_t mask) +{ +#ifdef _MSC_VER + unsigned long pos; + if (_BitScanReverse(&pos, mask)) + return static_cast(pos); +#elif VMA_CPP20 + if(mask) + return 31 - static_cast(std::countl_zero(mask)); +#elif defined __GNUC__ || defined __clang__ + if (mask) + return 31 - static_cast(__builtin_clz(mask)); +#else + uint8_t pos = 31; + uint32_t bit = 1UL << 31; + do + { + if (mask & bit) + return pos; + bit >>= 1; + } while (pos-- > 0); +#endif + return UINT8_MAX; +} + +/* +Returns true if given number is a power of two. +T must be unsigned integer number or signed integer but always nonnegative. +For 0 returns true. +*/ +template +inline bool VmaIsPow2(T x) +{ + return (x & (x - 1)) == 0; +} + +// Aligns given value up to nearest multiply of align value. For example: VmaAlignUp(11, 8) = 16. +// Use types like uint32_t, uint64_t as T. +template +static inline T VmaAlignUp(T val, T alignment) +{ + VMA_HEAVY_ASSERT(VmaIsPow2(alignment)); + return (val + alignment - 1) & ~(alignment - 1); +} + +// Aligns given value down to nearest multiply of align value. For example: VmaAlignDown(11, 8) = 8. +// Use types like uint32_t, uint64_t as T. +template +static inline T VmaAlignDown(T val, T alignment) +{ + VMA_HEAVY_ASSERT(VmaIsPow2(alignment)); + return val & ~(alignment - 1); +} + +// Division with mathematical rounding to nearest number. +template +static inline T VmaRoundDiv(T x, T y) +{ + return (x + (y / (T)2)) / y; +} + +// Divide by 'y' and round up to nearest integer. +template +static inline T VmaDivideRoundingUp(T x, T y) +{ + return (x + y - (T)1) / y; +} + +// Returns smallest power of 2 greater or equal to v. +static inline uint32_t VmaNextPow2(uint32_t v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; +} + +static inline uint64_t VmaNextPow2(uint64_t v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v |= v >> 32; + v++; + return v; +} + +// Returns largest power of 2 less or equal to v. +static inline uint32_t VmaPrevPow2(uint32_t v) +{ + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v = v ^ (v >> 1); + return v; +} + +static inline uint64_t VmaPrevPow2(uint64_t v) +{ + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v |= v >> 32; + v = v ^ (v >> 1); + return v; +} + +static inline bool VmaStrIsEmpty(const char* pStr) +{ + return pStr == VMA_NULL || *pStr == '\0'; +} + +/* +Returns true if two memory blocks occupy overlapping pages. +ResourceA must be in less memory offset than ResourceB. + +Algorithm is based on "Vulkan 1.0.39 - A Specification (with all registered Vulkan extensions)" +chapter 11.6 "Resource Memory Association", paragraph "Buffer-Image Granularity". +*/ +static inline bool VmaBlocksOnSamePage( + VkDeviceSize resourceAOffset, + VkDeviceSize resourceASize, + VkDeviceSize resourceBOffset, + VkDeviceSize pageSize) +{ + VMA_ASSERT(resourceAOffset + resourceASize <= resourceBOffset && resourceASize > 0 && pageSize > 0); + VkDeviceSize resourceAEnd = resourceAOffset + resourceASize - 1; + VkDeviceSize resourceAEndPage = resourceAEnd & ~(pageSize - 1); + VkDeviceSize resourceBStart = resourceBOffset; + VkDeviceSize resourceBStartPage = resourceBStart & ~(pageSize - 1); + return resourceAEndPage == resourceBStartPage; +} + +/* +Returns true if given suballocation types could conflict and must respect +VkPhysicalDeviceLimits::bufferImageGranularity. They conflict if one is buffer +or linear image and another one is optimal image. If type is unknown, behave +conservatively. +*/ +static inline bool VmaIsBufferImageGranularityConflict( + VmaSuballocationType suballocType1, + VmaSuballocationType suballocType2) +{ + if (suballocType1 > suballocType2) + { + std::swap(suballocType1, suballocType2); + } + + switch (suballocType1) + { + case VMA_SUBALLOCATION_TYPE_FREE: + return false; + case VMA_SUBALLOCATION_TYPE_UNKNOWN: + return true; + case VMA_SUBALLOCATION_TYPE_BUFFER: + return + suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN || + suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL; + case VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN: + return + suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN || + suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_LINEAR || + suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL; + case VMA_SUBALLOCATION_TYPE_IMAGE_LINEAR: + return + suballocType2 == VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL; + case VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL: + return false; + default: + VMA_ASSERT(0); + return true; + } +} + +static void VmaWriteMagicValue(void* pData, VkDeviceSize offset) +{ +#if VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_DETECT_CORRUPTION + uint32_t* pDst = (uint32_t*)((char*)pData + offset); + const size_t numberCount = VMA_DEBUG_MARGIN / sizeof(uint32_t); + for (size_t i = 0; i < numberCount; ++i, ++pDst) + { + *pDst = VMA_CORRUPTION_DETECTION_MAGIC_VALUE; + } +#else + // no-op +#endif +} + +static bool VmaValidateMagicValue(const void* pData, VkDeviceSize offset) +{ +#if VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_DETECT_CORRUPTION + const uint32_t* pSrc = (const uint32_t*)((const char*)pData + offset); + const size_t numberCount = VMA_DEBUG_MARGIN / sizeof(uint32_t); + for (size_t i = 0; i < numberCount; ++i, ++pSrc) + { + if (*pSrc != VMA_CORRUPTION_DETECTION_MAGIC_VALUE) + { + return false; + } + } +#endif + return true; +} + +/* +Fills structure with parameters of an example buffer to be used for transfers +during GPU memory defragmentation. +*/ +static void VmaFillGpuDefragmentationBufferCreateInfo(VkBufferCreateInfo& outBufCreateInfo) +{ + memset(&outBufCreateInfo, 0, sizeof(outBufCreateInfo)); + outBufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + outBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + outBufCreateInfo.size = (VkDeviceSize)VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE; // Example size. +} + + +/* +Performs binary search and returns iterator to first element that is greater or +equal to (key), according to comparison (cmp). + +Cmp should return true if first argument is less than second argument. + +Returned value is the found element, if present in the collection or place where +new element with value (key) should be inserted. +*/ +template +static IterT VmaBinaryFindFirstNotLess(IterT beg, IterT end, const KeyT& key, const CmpLess& cmp) +{ + size_t down = 0, up = size_t(end - beg); + while (down < up) + { + const size_t mid = down + (up - down) / 2; // Overflow-safe midpoint calculation + if (cmp(*(beg + mid), key)) + { + down = mid + 1; + } + else + { + up = mid; + } + } + return beg + down; +} + +template +IterT VmaBinaryFindSorted(const IterT& beg, const IterT& end, const KeyT& value, const CmpLess& cmp) +{ + IterT it = VmaBinaryFindFirstNotLess( + beg, end, value, cmp); + if (it == end || + (!cmp(*it, value) && !cmp(value, *it))) + { + return it; + } + return end; +} + +/* +Returns true if all pointers in the array are not-null and unique. +Warning! O(n^2) complexity. Use only inside VMA_HEAVY_ASSERT. +T must be pointer type, e.g. VmaAllocation, VmaPool. +*/ +template +static bool VmaValidatePointerArray(uint32_t count, const T* arr) +{ + for (uint32_t i = 0; i < count; ++i) + { + const T iPtr = arr[i]; + if (iPtr == VMA_NULL) + { + return false; + } + for (uint32_t j = i + 1; j < count; ++j) + { + if (iPtr == arr[j]) + { + return false; + } + } + } + return true; +} + +template +static inline void VmaPnextChainPushFront(MainT* mainStruct, NewT* newStruct) +{ + newStruct->pNext = mainStruct->pNext; + mainStruct->pNext = newStruct; +} +// Finds structure with s->sType == sType in mainStruct->pNext chain. +// Returns pointer to it. If not found, returns null. +template +static inline const FindT* VmaPnextChainFind(const MainT* mainStruct, VkStructureType sType) +{ + for(const VkBaseInStructure* s = (const VkBaseInStructure*)mainStruct->pNext; + s != VMA_NULL; s = s->pNext) + { + if(s->sType == sType) + { + return (const FindT*)s; + } + } + return VMA_NULL; +} + +// An abstraction over buffer or image `usage` flags, depending on available extensions. +struct VmaBufferImageUsage +{ +#if VMA_KHR_MAINTENANCE5 + typedef uint64_t BaseType; // VkFlags64 +#else + typedef uint32_t BaseType; // VkFlags32 +#endif + + static const VmaBufferImageUsage UNKNOWN; + + BaseType Value; + + VmaBufferImageUsage() { *this = UNKNOWN; } + explicit VmaBufferImageUsage(BaseType usage) : Value(usage) { } + VmaBufferImageUsage(const VkBufferCreateInfo &createInfo, bool useKhrMaintenance5); + explicit VmaBufferImageUsage(const VkImageCreateInfo &createInfo); + + bool operator==(const VmaBufferImageUsage& rhs) const { return Value == rhs.Value; } + bool operator!=(const VmaBufferImageUsage& rhs) const { return Value != rhs.Value; } + + bool Contains(BaseType flag) const { return (Value & flag) != 0; } + bool ContainsDeviceAccess() const + { + // This relies on values of VK_IMAGE_USAGE_TRANSFER* being the same as VK_BUFFER_IMAGE_TRANSFER*. + return (Value & ~BaseType(VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT)) != 0; + } +}; + +const VmaBufferImageUsage VmaBufferImageUsage::UNKNOWN = VmaBufferImageUsage(0); + +VmaBufferImageUsage::VmaBufferImageUsage(const VkBufferCreateInfo &createInfo, + bool useKhrMaintenance5) +{ +#if VMA_KHR_MAINTENANCE5 + if(useKhrMaintenance5) + { + // If VkBufferCreateInfo::pNext chain contains VkBufferUsageFlags2CreateInfoKHR, + // take usage from it and ignore VkBufferCreateInfo::usage, per specification + // of the VK_KHR_maintenance5 extension. + const VkBufferUsageFlags2CreateInfoKHR* const usageFlags2 = + VmaPnextChainFind(&createInfo, VK_STRUCTURE_TYPE_BUFFER_USAGE_FLAGS_2_CREATE_INFO_KHR); + if(usageFlags2) + { + this->Value = usageFlags2->usage; + return; + } + } +#endif + + this->Value = (BaseType)createInfo.usage; +} + +VmaBufferImageUsage::VmaBufferImageUsage(const VkImageCreateInfo &createInfo) +{ + // Maybe in the future there will be VK_KHR_maintenanceN extension with structure + // VkImageUsageFlags2CreateInfoKHR, like the one for buffers... + + this->Value = (BaseType)createInfo.usage; +} + +// This is the main algorithm that guides the selection of a memory type best for an allocation - +// converts usage to required/preferred/not preferred flags. +static bool FindMemoryPreferences( + bool isIntegratedGPU, + const VmaAllocationCreateInfo& allocCreateInfo, + VmaBufferImageUsage bufImgUsage, + VkMemoryPropertyFlags& outRequiredFlags, + VkMemoryPropertyFlags& outPreferredFlags, + VkMemoryPropertyFlags& outNotPreferredFlags) +{ + outRequiredFlags = allocCreateInfo.requiredFlags; + outPreferredFlags = allocCreateInfo.preferredFlags; + outNotPreferredFlags = 0; + + switch(allocCreateInfo.usage) + { + case VMA_MEMORY_USAGE_UNKNOWN: + break; + case VMA_MEMORY_USAGE_GPU_ONLY: + if(!isIntegratedGPU || (outPreferredFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) + { + outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + } + break; + case VMA_MEMORY_USAGE_CPU_ONLY: + outRequiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + break; + case VMA_MEMORY_USAGE_CPU_TO_GPU: + outRequiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + if(!isIntegratedGPU || (outPreferredFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) + { + outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + } + break; + case VMA_MEMORY_USAGE_GPU_TO_CPU: + outRequiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + outPreferredFlags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + break; + case VMA_MEMORY_USAGE_CPU_COPY: + outNotPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + break; + case VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED: + outRequiredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT; + break; + case VMA_MEMORY_USAGE_AUTO: + case VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE: + case VMA_MEMORY_USAGE_AUTO_PREFER_HOST: + { + if(bufImgUsage == VmaBufferImageUsage::UNKNOWN) + { + VMA_ASSERT(0 && "VMA_MEMORY_USAGE_AUTO* values can only be used with functions like vmaCreateBuffer, vmaCreateImage so that the details of the created resource are known." + " Maybe you use VkBufferUsageFlags2CreateInfoKHR but forgot to use VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT?" ); + return false; + } + + const bool deviceAccess = bufImgUsage.ContainsDeviceAccess(); + const bool hostAccessSequentialWrite = (allocCreateInfo.flags & VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT) != 0; + const bool hostAccessRandom = (allocCreateInfo.flags & VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT) != 0; + const bool hostAccessAllowTransferInstead = (allocCreateInfo.flags & VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT) != 0; + const bool preferDevice = allocCreateInfo.usage == VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; + const bool preferHost = allocCreateInfo.usage == VMA_MEMORY_USAGE_AUTO_PREFER_HOST; + + // CPU random access - e.g. a buffer written to or transferred from GPU to read back on CPU. + if(hostAccessRandom) + { + // Prefer cached. Cannot require it, because some platforms don't have it (e.g. Raspberry Pi - see #362)! + outPreferredFlags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + + if (!isIntegratedGPU && deviceAccess && hostAccessAllowTransferInstead && !preferHost) + { + // Nice if it will end up in HOST_VISIBLE, but more importantly prefer DEVICE_LOCAL. + // Omitting HOST_VISIBLE here is intentional. + // In case there is DEVICE_LOCAL | HOST_VISIBLE | HOST_CACHED, it will pick that one. + // Otherwise, this will give same weight to DEVICE_LOCAL as HOST_VISIBLE | HOST_CACHED and select the former if occurs first on the list. + outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + } + else + { + // Always CPU memory. + outRequiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + } + } + // CPU sequential write - may be CPU or host-visible GPU memory, uncached and write-combined. + else if(hostAccessSequentialWrite) + { + // Want uncached and write-combined. + outNotPreferredFlags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + + if(!isIntegratedGPU && deviceAccess && hostAccessAllowTransferInstead && !preferHost) + { + outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + } + else + { + outRequiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + // Direct GPU access, CPU sequential write (e.g. a dynamic uniform buffer updated every frame) + if(deviceAccess) + { + // Could go to CPU memory or GPU BAR/unified. Up to the user to decide. If no preference, choose GPU memory. + if(preferHost) + outNotPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + else + outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + } + // GPU no direct access, CPU sequential write (e.g. an upload buffer to be transferred to the GPU) + else + { + // Could go to CPU memory or GPU BAR/unified. Up to the user to decide. If no preference, choose CPU memory. + if(preferDevice) + outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + else + outNotPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + } + } + } + // No CPU access + else + { + // if(deviceAccess) + // + // GPU access, no CPU access (e.g. a color attachment image) - prefer GPU memory, + // unless there is a clear preference from the user not to do so. + // + // else: + // + // No direct GPU access, no CPU access, just transfers. + // It may be staging copy intended for e.g. preserving image for next frame (then better GPU memory) or + // a "swap file" copy to free some GPU memory (then better CPU memory). + // Up to the user to decide. If no preferece, assume the former and choose GPU memory. + + if(preferHost) + outNotPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + else + outPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + } + break; + } + default: + VMA_ASSERT(0); + } + + // Avoid DEVICE_COHERENT unless explicitly requested. + if(((allocCreateInfo.requiredFlags | allocCreateInfo.preferredFlags) & + (VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY | VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY)) == 0) + { + outNotPreferredFlags |= VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// Memory allocation + +static void* VmaMalloc(const VkAllocationCallbacks* pAllocationCallbacks, size_t size, size_t alignment) +{ + void* result = VMA_NULL; + if ((pAllocationCallbacks != VMA_NULL) && + (pAllocationCallbacks->pfnAllocation != VMA_NULL)) + { + result = (*pAllocationCallbacks->pfnAllocation)( + pAllocationCallbacks->pUserData, + size, + alignment, + VK_SYSTEM_ALLOCATION_SCOPE_OBJECT); + } + else + { + result = VMA_SYSTEM_ALIGNED_MALLOC(size, alignment); + } + VMA_ASSERT(result != VMA_NULL && "CPU memory allocation failed."); + return result; +} + +static void VmaFree(const VkAllocationCallbacks* pAllocationCallbacks, void* ptr) +{ + if ((pAllocationCallbacks != VMA_NULL) && + (pAllocationCallbacks->pfnFree != VMA_NULL)) + { + (*pAllocationCallbacks->pfnFree)(pAllocationCallbacks->pUserData, ptr); + } + else + { + VMA_SYSTEM_ALIGNED_FREE(ptr); + } +} + +template +static T* VmaAllocate(const VkAllocationCallbacks* pAllocationCallbacks) +{ + return (T*)VmaMalloc(pAllocationCallbacks, sizeof(T), VMA_ALIGN_OF(T)); +} + +template +static T* VmaAllocateArray(const VkAllocationCallbacks* pAllocationCallbacks, size_t count) +{ + return (T*)VmaMalloc(pAllocationCallbacks, sizeof(T) * count, VMA_ALIGN_OF(T)); +} + +#define vma_new(allocator, type) new(VmaAllocate(allocator))(type) + +#define vma_new_array(allocator, type, count) new(VmaAllocateArray((allocator), (count)))(type) + +template +static void vma_delete(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr) +{ + ptr->~T(); + VmaFree(pAllocationCallbacks, ptr); +} + +template +static void vma_delete_array(const VkAllocationCallbacks* pAllocationCallbacks, T* ptr, size_t count) +{ + if (ptr != VMA_NULL) + { + for (size_t i = count; i--; ) + { + ptr[i].~T(); + } + VmaFree(pAllocationCallbacks, ptr); + } +} + +static char* VmaCreateStringCopy(const VkAllocationCallbacks* allocs, const char* srcStr) +{ + if (srcStr != VMA_NULL) + { + const size_t len = strlen(srcStr); + char* const result = vma_new_array(allocs, char, len + 1); + memcpy(result, srcStr, len + 1); + return result; + } + return VMA_NULL; +} + +#if VMA_STATS_STRING_ENABLED +static char* VmaCreateStringCopy(const VkAllocationCallbacks* allocs, const char* srcStr, size_t strLen) +{ + if (srcStr != VMA_NULL) + { + char* const result = vma_new_array(allocs, char, strLen + 1); + memcpy(result, srcStr, strLen); + result[strLen] = '\0'; + return result; + } + return VMA_NULL; +} +#endif // VMA_STATS_STRING_ENABLED + +static void VmaFreeString(const VkAllocationCallbacks* allocs, char* str) +{ + if (str != VMA_NULL) + { + const size_t len = strlen(str); + vma_delete_array(allocs, str, len + 1); + } +} + +template +size_t VmaVectorInsertSorted(VectorT& vector, const typename VectorT::value_type& value) +{ + const size_t indexToInsert = VmaBinaryFindFirstNotLess( + vector.data(), + vector.data() + vector.size(), + value, + CmpLess()) - vector.data(); + VmaVectorInsert(vector, indexToInsert, value); + return indexToInsert; +} + +template +bool VmaVectorRemoveSorted(VectorT& vector, const typename VectorT::value_type& value) +{ + CmpLess comparator; + typename VectorT::iterator it = VmaBinaryFindFirstNotLess( + vector.begin(), + vector.end(), + value, + comparator); + if ((it != vector.end()) && !comparator(*it, value) && !comparator(value, *it)) + { + size_t indexToRemove = it - vector.begin(); + VmaVectorRemove(vector, indexToRemove); + return true; + } + return false; +} +#endif // _VMA_FUNCTIONS + +#ifndef _VMA_STATISTICS_FUNCTIONS + +static void VmaClearStatistics(VmaStatistics& outStats) +{ + outStats.blockCount = 0; + outStats.allocationCount = 0; + outStats.blockBytes = 0; + outStats.allocationBytes = 0; +} + +static void VmaAddStatistics(VmaStatistics& inoutStats, const VmaStatistics& src) +{ + inoutStats.blockCount += src.blockCount; + inoutStats.allocationCount += src.allocationCount; + inoutStats.blockBytes += src.blockBytes; + inoutStats.allocationBytes += src.allocationBytes; +} + +static void VmaClearDetailedStatistics(VmaDetailedStatistics& outStats) +{ + VmaClearStatistics(outStats.statistics); + outStats.unusedRangeCount = 0; + outStats.allocationSizeMin = VK_WHOLE_SIZE; + outStats.allocationSizeMax = 0; + outStats.unusedRangeSizeMin = VK_WHOLE_SIZE; + outStats.unusedRangeSizeMax = 0; +} + +static void VmaAddDetailedStatisticsAllocation(VmaDetailedStatistics& inoutStats, VkDeviceSize size) +{ + inoutStats.statistics.allocationCount++; + inoutStats.statistics.allocationBytes += size; + inoutStats.allocationSizeMin = VMA_MIN(inoutStats.allocationSizeMin, size); + inoutStats.allocationSizeMax = VMA_MAX(inoutStats.allocationSizeMax, size); +} + +static void VmaAddDetailedStatisticsUnusedRange(VmaDetailedStatistics& inoutStats, VkDeviceSize size) +{ + inoutStats.unusedRangeCount++; + inoutStats.unusedRangeSizeMin = VMA_MIN(inoutStats.unusedRangeSizeMin, size); + inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, size); +} + +static void VmaAddDetailedStatistics(VmaDetailedStatistics& inoutStats, const VmaDetailedStatistics& src) +{ + VmaAddStatistics(inoutStats.statistics, src.statistics); + inoutStats.unusedRangeCount += src.unusedRangeCount; + inoutStats.allocationSizeMin = VMA_MIN(inoutStats.allocationSizeMin, src.allocationSizeMin); + inoutStats.allocationSizeMax = VMA_MAX(inoutStats.allocationSizeMax, src.allocationSizeMax); + inoutStats.unusedRangeSizeMin = VMA_MIN(inoutStats.unusedRangeSizeMin, src.unusedRangeSizeMin); + inoutStats.unusedRangeSizeMax = VMA_MAX(inoutStats.unusedRangeSizeMax, src.unusedRangeSizeMax); +} + +#endif // _VMA_STATISTICS_FUNCTIONS + +#ifndef _VMA_MUTEX_LOCK +// Helper RAII class to lock a mutex in constructor and unlock it in destructor (at the end of scope). +struct VmaMutexLock +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaMutexLock) +public: + VmaMutexLock(VMA_MUTEX& mutex, bool useMutex = true) : + m_pMutex(useMutex ? &mutex : VMA_NULL) + { + if (m_pMutex) { m_pMutex->Lock(); } + } + ~VmaMutexLock() { if (m_pMutex) { m_pMutex->Unlock(); } } + +private: + VMA_MUTEX* m_pMutex; +}; + +// Helper RAII class to lock a RW mutex in constructor and unlock it in destructor (at the end of scope), for reading. +struct VmaMutexLockRead +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaMutexLockRead) +public: + VmaMutexLockRead(VMA_RW_MUTEX& mutex, bool useMutex) : + m_pMutex(useMutex ? &mutex : VMA_NULL) + { + if (m_pMutex) { m_pMutex->LockRead(); } + } + ~VmaMutexLockRead() { if (m_pMutex) { m_pMutex->UnlockRead(); } } + +private: + VMA_RW_MUTEX* m_pMutex; +}; + +// Helper RAII class to lock a RW mutex in constructor and unlock it in destructor (at the end of scope), for writing. +struct VmaMutexLockWrite +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaMutexLockWrite) +public: + VmaMutexLockWrite(VMA_RW_MUTEX& mutex, bool useMutex) + : m_pMutex(useMutex ? &mutex : VMA_NULL) + { + if (m_pMutex) { m_pMutex->LockWrite(); } + } + ~VmaMutexLockWrite() { if (m_pMutex) { m_pMutex->UnlockWrite(); } } + +private: + VMA_RW_MUTEX* m_pMutex; +}; + +#if VMA_DEBUG_GLOBAL_MUTEX + static VMA_MUTEX gDebugGlobalMutex; + #define VMA_DEBUG_GLOBAL_MUTEX_LOCK VmaMutexLock debugGlobalMutexLock(gDebugGlobalMutex, true); +#else + #define VMA_DEBUG_GLOBAL_MUTEX_LOCK +#endif +#endif // _VMA_MUTEX_LOCK + +#ifndef _VMA_ATOMIC_TRANSACTIONAL_INCREMENT +// An object that increments given atomic but decrements it back in the destructor unless Commit() is called. +template +struct AtomicTransactionalIncrement +{ +public: + using T = decltype(AtomicT().load()); + + ~AtomicTransactionalIncrement() + { + if(m_Atomic) + --(*m_Atomic); + } + + void Commit() { m_Atomic = VMA_NULL; } + T Increment(AtomicT* atomic) + { + m_Atomic = atomic; + return m_Atomic->fetch_add(1); + } + +private: + AtomicT* m_Atomic = VMA_NULL; +}; +#endif // _VMA_ATOMIC_TRANSACTIONAL_INCREMENT + +#ifndef _VMA_STL_ALLOCATOR +// STL-compatible allocator. +template +struct VmaStlAllocator +{ + const VkAllocationCallbacks* const m_pCallbacks; + typedef T value_type; + + VmaStlAllocator(const VkAllocationCallbacks* pCallbacks) : m_pCallbacks(pCallbacks) {} + template + VmaStlAllocator(const VmaStlAllocator& src) : m_pCallbacks(src.m_pCallbacks) {} + VmaStlAllocator(const VmaStlAllocator&) = default; + VmaStlAllocator& operator=(const VmaStlAllocator&) = delete; + + T* allocate(size_t n) { return VmaAllocateArray(m_pCallbacks, n); } + void deallocate(T* p, size_t n) { VmaFree(m_pCallbacks, p); } + + template + bool operator==(const VmaStlAllocator& rhs) const + { + return m_pCallbacks == rhs.m_pCallbacks; + } + template + bool operator!=(const VmaStlAllocator& rhs) const + { + return m_pCallbacks != rhs.m_pCallbacks; + } +}; +#endif // _VMA_STL_ALLOCATOR + +#ifndef _VMA_VECTOR +/* Class with interface compatible with subset of std::vector. +T must be POD because constructors and destructors are not called and memcpy is +used for these objects. */ +template +class VmaVector +{ +public: + typedef T value_type; + typedef T* iterator; + typedef const T* const_iterator; + + VmaVector(const AllocatorT& allocator); + VmaVector(size_t count, const AllocatorT& allocator); + // This version of the constructor is here for compatibility with pre-C++14 std::vector. + // value is unused. + VmaVector(size_t count, const T& value, const AllocatorT& allocator) : VmaVector(count, allocator) {} + VmaVector(const VmaVector& src); + VmaVector& operator=(const VmaVector& rhs); + ~VmaVector() { VmaFree(m_Allocator.m_pCallbacks, m_pArray); } + + bool empty() const { return m_Count == 0; } + size_t size() const { return m_Count; } + T* data() { return m_pArray; } + T& front() { VMA_HEAVY_ASSERT(m_Count > 0); return m_pArray[0]; } + T& back() { VMA_HEAVY_ASSERT(m_Count > 0); return m_pArray[m_Count - 1]; } + const T* data() const { return m_pArray; } + const T& front() const { VMA_HEAVY_ASSERT(m_Count > 0); return m_pArray[0]; } + const T& back() const { VMA_HEAVY_ASSERT(m_Count > 0); return m_pArray[m_Count - 1]; } + + iterator begin() { return m_pArray; } + iterator end() { return m_pArray + m_Count; } + const_iterator cbegin() const { return m_pArray; } + const_iterator cend() const { return m_pArray + m_Count; } + const_iterator begin() const { return cbegin(); } + const_iterator end() const { return cend(); } + + void pop_front() { VMA_HEAVY_ASSERT(m_Count > 0); remove(0); } + void pop_back() { VMA_HEAVY_ASSERT(m_Count > 0); resize(size() - 1); } + void push_front(const T& src) { insert(0, src); } + + void push_back(const T& src); + void reserve(size_t newCapacity, bool freeMemory = false); + void resize(size_t newCount); + void clear() { resize(0); } + void shrink_to_fit(); + void insert(size_t index, const T& src); + void remove(size_t index); + + T& operator[](size_t index) { VMA_HEAVY_ASSERT(index < m_Count); return m_pArray[index]; } + const T& operator[](size_t index) const { VMA_HEAVY_ASSERT(index < m_Count); return m_pArray[index]; } + +private: + AllocatorT m_Allocator; + T* m_pArray; + size_t m_Count; + size_t m_Capacity; +}; + +#ifndef _VMA_VECTOR_FUNCTIONS +template +VmaVector::VmaVector(const AllocatorT& allocator) + : m_Allocator(allocator), + m_pArray(VMA_NULL), + m_Count(0), + m_Capacity(0) {} + +template +VmaVector::VmaVector(size_t count, const AllocatorT& allocator) + : m_Allocator(allocator), + m_pArray(count ? (T*)VmaAllocateArray(allocator.m_pCallbacks, count) : VMA_NULL), + m_Count(count), + m_Capacity(count) {} + +template +VmaVector::VmaVector(const VmaVector& src) + : m_Allocator(src.m_Allocator), + m_pArray(src.m_Count ? (T*)VmaAllocateArray(src.m_Allocator.m_pCallbacks, src.m_Count) : VMA_NULL), + m_Count(src.m_Count), + m_Capacity(src.m_Count) +{ + if (m_Count != 0) + { + memcpy(m_pArray, src.m_pArray, m_Count * sizeof(T)); + } +} + +template +VmaVector& VmaVector::operator=(const VmaVector& rhs) +{ + if (&rhs != this) + { + resize(rhs.m_Count); + if (m_Count != 0) + { + memcpy(m_pArray, rhs.m_pArray, m_Count * sizeof(T)); + } + } + return *this; +} + +template +void VmaVector::push_back(const T& src) +{ + const size_t newIndex = size(); + resize(newIndex + 1); + m_pArray[newIndex] = src; +} + +template +void VmaVector::reserve(size_t newCapacity, bool freeMemory) +{ + newCapacity = VMA_MAX(newCapacity, m_Count); + + if ((newCapacity < m_Capacity) && !freeMemory) + { + newCapacity = m_Capacity; + } + + if (newCapacity != m_Capacity) + { + T* const newArray = newCapacity ? VmaAllocateArray(m_Allocator, newCapacity) : VMA_NULL; + if (m_Count != 0) + { + memcpy(newArray, m_pArray, m_Count * sizeof(T)); + } + VmaFree(m_Allocator.m_pCallbacks, m_pArray); + m_Capacity = newCapacity; + m_pArray = newArray; + } +} + +template +void VmaVector::resize(size_t newCount) +{ + size_t newCapacity = m_Capacity; + if (newCount > m_Capacity) + { + newCapacity = VMA_MAX(newCount, VMA_MAX(m_Capacity * 3 / 2, (size_t)8)); + } + + if (newCapacity != m_Capacity) + { + T* const newArray = newCapacity ? VmaAllocateArray(m_Allocator.m_pCallbacks, newCapacity) : VMA_NULL; + const size_t elementsToCopy = VMA_MIN(m_Count, newCount); + if (elementsToCopy != 0) + { + memcpy(newArray, m_pArray, elementsToCopy * sizeof(T)); + } + VmaFree(m_Allocator.m_pCallbacks, m_pArray); + m_Capacity = newCapacity; + m_pArray = newArray; + } + + m_Count = newCount; +} + +template +void VmaVector::shrink_to_fit() +{ + if (m_Capacity > m_Count) + { + T* newArray = VMA_NULL; + if (m_Count > 0) + { + newArray = VmaAllocateArray(m_Allocator.m_pCallbacks, m_Count); + memcpy(newArray, m_pArray, m_Count * sizeof(T)); + } + VmaFree(m_Allocator.m_pCallbacks, m_pArray); + m_Capacity = m_Count; + m_pArray = newArray; + } +} + +template +void VmaVector::insert(size_t index, const T& src) +{ + VMA_HEAVY_ASSERT(index <= m_Count); + const size_t oldCount = size(); + resize(oldCount + 1); + if (index < oldCount) + { + memmove(m_pArray + (index + 1), m_pArray + index, (oldCount - index) * sizeof(T)); + } + m_pArray[index] = src; +} + +template +void VmaVector::remove(size_t index) +{ + VMA_HEAVY_ASSERT(index < m_Count); + const size_t oldCount = size(); + if (index < oldCount - 1) + { + memmove(m_pArray + index, m_pArray + (index + 1), (oldCount - index - 1) * sizeof(T)); + } + resize(oldCount - 1); +} +#endif // _VMA_VECTOR_FUNCTIONS + +template +static void VmaVectorInsert(VmaVector& vec, size_t index, const T& item) +{ + vec.insert(index, item); +} + +template +static void VmaVectorRemove(VmaVector& vec, size_t index) +{ + vec.remove(index); +} +#endif // _VMA_VECTOR + +#ifndef _VMA_SMALL_VECTOR +/* +This is a vector (a variable-sized array), optimized for the case when the array is small. + +It contains some number of elements in-place, which allows it to avoid heap allocation +when the actual number of elements is below that threshold. This allows normal "small" +cases to be fast without losing generality for large inputs. +*/ +template +class VmaSmallVector +{ +public: + typedef T value_type; + typedef T* iterator; + + VmaSmallVector(const AllocatorT& allocator); + VmaSmallVector(size_t count, const AllocatorT& allocator); + template + VmaSmallVector(const VmaSmallVector&) = delete; + template + VmaSmallVector& operator=(const VmaSmallVector&) = delete; + ~VmaSmallVector() = default; + + bool empty() const { return m_Count == 0; } + size_t size() const { return m_Count; } + T* data() { return m_Count > N ? m_DynamicArray.data() : m_StaticArray; } + T& front() { VMA_HEAVY_ASSERT(m_Count > 0); return data()[0]; } + T& back() { VMA_HEAVY_ASSERT(m_Count > 0); return data()[m_Count - 1]; } + const T* data() const { return m_Count > N ? m_DynamicArray.data() : m_StaticArray; } + const T& front() const { VMA_HEAVY_ASSERT(m_Count > 0); return data()[0]; } + const T& back() const { VMA_HEAVY_ASSERT(m_Count > 0); return data()[m_Count - 1]; } + + iterator begin() { return data(); } + iterator end() { return data() + m_Count; } + + void pop_front() { VMA_HEAVY_ASSERT(m_Count > 0); remove(0); } + void pop_back() { VMA_HEAVY_ASSERT(m_Count > 0); resize(size() - 1); } + void push_front(const T& src) { insert(0, src); } + + void push_back(const T& src); + void resize(size_t newCount, bool freeMemory = false); + void clear(bool freeMemory = false); + void insert(size_t index, const T& src); + void remove(size_t index); + + T& operator[](size_t index) { VMA_HEAVY_ASSERT(index < m_Count); return data()[index]; } + const T& operator[](size_t index) const { VMA_HEAVY_ASSERT(index < m_Count); return data()[index]; } + +private: + size_t m_Count; + T m_StaticArray[N]; // Used when m_Size <= N + VmaVector m_DynamicArray; // Used when m_Size > N +}; + +#ifndef _VMA_SMALL_VECTOR_FUNCTIONS +template +VmaSmallVector::VmaSmallVector(const AllocatorT& allocator) + : m_Count(0), + m_DynamicArray(allocator) {} + +template +VmaSmallVector::VmaSmallVector(size_t count, const AllocatorT& allocator) + : m_Count(count), + m_DynamicArray(count > N ? count : 0, allocator) {} + +template +void VmaSmallVector::push_back(const T& src) +{ + const size_t newIndex = size(); + resize(newIndex + 1); + data()[newIndex] = src; +} + +template +void VmaSmallVector::resize(size_t newCount, bool freeMemory) +{ + if (newCount > N && m_Count > N) + { + // Any direction, staying in m_DynamicArray + m_DynamicArray.resize(newCount); + if (freeMemory) + { + m_DynamicArray.shrink_to_fit(); + } + } + else if (newCount > N && m_Count <= N) + { + // Growing, moving from m_StaticArray to m_DynamicArray + m_DynamicArray.resize(newCount); + if (m_Count > 0) + { + memcpy(m_DynamicArray.data(), m_StaticArray, m_Count * sizeof(T)); + } + } + else if (newCount <= N && m_Count > N) + { + // Shrinking, moving from m_DynamicArray to m_StaticArray + if (newCount > 0) + { + memcpy(m_StaticArray, m_DynamicArray.data(), newCount * sizeof(T)); + } + m_DynamicArray.resize(0); + if (freeMemory) + { + m_DynamicArray.shrink_to_fit(); + } + } + else + { + // Any direction, staying in m_StaticArray - nothing to do here + } + m_Count = newCount; +} + +template +void VmaSmallVector::clear(bool freeMemory) +{ + m_DynamicArray.clear(); + if (freeMemory) + { + m_DynamicArray.shrink_to_fit(); + } + m_Count = 0; +} + +template +void VmaSmallVector::insert(size_t index, const T& src) +{ + VMA_HEAVY_ASSERT(index <= m_Count); + const size_t oldCount = size(); + resize(oldCount + 1); + T* const dataPtr = data(); + if (index < oldCount) + { + // I know, this could be more optimal for case where memmove can be memcpy directly from m_StaticArray to m_DynamicArray. + memmove(dataPtr + (index + 1), dataPtr + index, (oldCount - index) * sizeof(T)); + } + dataPtr[index] = src; +} + +template +void VmaSmallVector::remove(size_t index) +{ + VMA_HEAVY_ASSERT(index < m_Count); + const size_t oldCount = size(); + if (index < oldCount - 1) + { + // I know, this could be more optimal for case where memmove can be memcpy directly from m_DynamicArray to m_StaticArray. + T* const dataPtr = data(); + memmove(dataPtr + index, dataPtr + (index + 1), (oldCount - index - 1) * sizeof(T)); + } + resize(oldCount - 1); +} +#endif // _VMA_SMALL_VECTOR_FUNCTIONS +#endif // _VMA_SMALL_VECTOR + +#ifndef _VMA_POOL_ALLOCATOR +/* +Allocator for objects of type T using a list of arrays (pools) to speed up +allocation. Number of elements that can be allocated is not bounded because +allocator can create multiple blocks. +*/ +template +class VmaPoolAllocator +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaPoolAllocator) +public: + VmaPoolAllocator(const VkAllocationCallbacks* pAllocationCallbacks, uint32_t firstBlockCapacity); + ~VmaPoolAllocator(); + template T* Alloc(Types&&... args); + void Free(T* ptr); + +private: + union Item + { + uint32_t NextFreeIndex; + alignas(T) char Value[sizeof(T)]; + }; + struct ItemBlock + { + Item* pItems; + uint32_t Capacity; + uint32_t FirstFreeIndex; + }; + + const VkAllocationCallbacks* m_pAllocationCallbacks; + const uint32_t m_FirstBlockCapacity; + VmaVector> m_ItemBlocks; + + ItemBlock& CreateNewBlock(); +}; + +#ifndef _VMA_POOL_ALLOCATOR_FUNCTIONS +template +VmaPoolAllocator::VmaPoolAllocator(const VkAllocationCallbacks* pAllocationCallbacks, uint32_t firstBlockCapacity) + : m_pAllocationCallbacks(pAllocationCallbacks), + m_FirstBlockCapacity(firstBlockCapacity), + m_ItemBlocks(VmaStlAllocator(pAllocationCallbacks)) +{ + VMA_ASSERT(m_FirstBlockCapacity > 1); +} + +template +VmaPoolAllocator::~VmaPoolAllocator() +{ + for (size_t i = m_ItemBlocks.size(); i--;) + vma_delete_array(m_pAllocationCallbacks, m_ItemBlocks[i].pItems, m_ItemBlocks[i].Capacity); + m_ItemBlocks.clear(); +} + +template +template T* VmaPoolAllocator::Alloc(Types&&... args) +{ + for (size_t i = m_ItemBlocks.size(); i--; ) + { + ItemBlock& block = m_ItemBlocks[i]; + // This block has some free items: Use first one. + if (block.FirstFreeIndex != UINT32_MAX) + { + Item* const pItem = &block.pItems[block.FirstFreeIndex]; + block.FirstFreeIndex = pItem->NextFreeIndex; + T* result = (T*)&pItem->Value; + new(result)T(std::forward(args)...); // Explicit constructor call. + return result; + } + } + + // No block has free item: Create new one and use it. + ItemBlock& newBlock = CreateNewBlock(); + Item* const pItem = &newBlock.pItems[0]; + newBlock.FirstFreeIndex = pItem->NextFreeIndex; + T* result = (T*)&pItem->Value; + new(result) T(std::forward(args)...); // Explicit constructor call. + return result; +} + +template +void VmaPoolAllocator::Free(T* ptr) +{ + // Search all memory blocks to find ptr. + for (size_t i = m_ItemBlocks.size(); i--; ) + { + ItemBlock& block = m_ItemBlocks[i]; + + // Casting to union. + Item* pItemPtr; + memcpy(&pItemPtr, &ptr, sizeof(pItemPtr)); + + // Check if pItemPtr is in address range of this block. + if ((pItemPtr >= block.pItems) && (pItemPtr < block.pItems + block.Capacity)) + { + ptr->~T(); // Explicit destructor call. + const uint32_t index = static_cast(pItemPtr - block.pItems); + pItemPtr->NextFreeIndex = block.FirstFreeIndex; + block.FirstFreeIndex = index; + return; + } + } + VMA_ASSERT(0 && "Pointer doesn't belong to this memory pool."); +} + +template +typename VmaPoolAllocator::ItemBlock& VmaPoolAllocator::CreateNewBlock() +{ + const uint32_t newBlockCapacity = m_ItemBlocks.empty() ? + m_FirstBlockCapacity : m_ItemBlocks.back().Capacity * 3 / 2; + + const ItemBlock newBlock = + { + vma_new_array(m_pAllocationCallbacks, Item, newBlockCapacity), + newBlockCapacity, + 0 + }; + + m_ItemBlocks.push_back(newBlock); + + // Setup singly-linked list of all free items in this block. + for (uint32_t i = 0; i < newBlockCapacity - 1; ++i) + newBlock.pItems[i].NextFreeIndex = i + 1; + newBlock.pItems[newBlockCapacity - 1].NextFreeIndex = UINT32_MAX; + return m_ItemBlocks.back(); +} +#endif // _VMA_POOL_ALLOCATOR_FUNCTIONS +#endif // _VMA_POOL_ALLOCATOR + +#ifndef _VMA_RAW_LIST +template +struct VmaListItem +{ + VmaListItem* pPrev; + VmaListItem* pNext; + T Value; +}; + +// Doubly linked list. +template +class VmaRawList +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaRawList) +public: + typedef VmaListItem ItemType; + + VmaRawList(const VkAllocationCallbacks* pAllocationCallbacks); + // Intentionally not calling Clear, because that would be unnecessary + // computations to return all items to m_ItemAllocator as free. + ~VmaRawList() = default; + + size_t GetCount() const { return m_Count; } + bool IsEmpty() const { return m_Count == 0; } + + ItemType* Front() { return m_pFront; } + ItemType* Back() { return m_pBack; } + const ItemType* Front() const { return m_pFront; } + const ItemType* Back() const { return m_pBack; } + + ItemType* PushFront(); + ItemType* PushBack(); + ItemType* PushFront(const T& value); + ItemType* PushBack(const T& value); + void PopFront(); + void PopBack(); + + // Item can be null - it means PushBack. + ItemType* InsertBefore(ItemType* pItem); + // Item can be null - it means PushFront. + ItemType* InsertAfter(ItemType* pItem); + ItemType* InsertBefore(ItemType* pItem, const T& value); + ItemType* InsertAfter(ItemType* pItem, const T& value); + + void Clear(); + void Remove(ItemType* pItem); + +private: + const VkAllocationCallbacks* const m_pAllocationCallbacks; + VmaPoolAllocator m_ItemAllocator; + ItemType* m_pFront; + ItemType* m_pBack; + size_t m_Count; +}; + +#ifndef _VMA_RAW_LIST_FUNCTIONS +template +VmaRawList::VmaRawList(const VkAllocationCallbacks* pAllocationCallbacks) + : m_pAllocationCallbacks(pAllocationCallbacks), + m_ItemAllocator(pAllocationCallbacks, 128), + m_pFront(VMA_NULL), + m_pBack(VMA_NULL), + m_Count(0) {} + +template +VmaListItem* VmaRawList::PushFront() +{ + ItemType* const pNewItem = m_ItemAllocator.Alloc(); + pNewItem->pPrev = VMA_NULL; + if (IsEmpty()) + { + pNewItem->pNext = VMA_NULL; + m_pFront = pNewItem; + m_pBack = pNewItem; + m_Count = 1; + } + else + { + pNewItem->pNext = m_pFront; + m_pFront->pPrev = pNewItem; + m_pFront = pNewItem; + ++m_Count; + } + return pNewItem; +} + +template +VmaListItem* VmaRawList::PushBack() +{ + ItemType* const pNewItem = m_ItemAllocator.Alloc(); + pNewItem->pNext = VMA_NULL; + if(IsEmpty()) + { + pNewItem->pPrev = VMA_NULL; + m_pFront = pNewItem; + m_pBack = pNewItem; + m_Count = 1; + } + else + { + pNewItem->pPrev = m_pBack; + m_pBack->pNext = pNewItem; + m_pBack = pNewItem; + ++m_Count; + } + return pNewItem; +} + +template +VmaListItem* VmaRawList::PushFront(const T& value) +{ + ItemType* const pNewItem = PushFront(); + pNewItem->Value = value; + return pNewItem; +} + +template +VmaListItem* VmaRawList::PushBack(const T& value) +{ + ItemType* const pNewItem = PushBack(); + pNewItem->Value = value; + return pNewItem; +} + +template +void VmaRawList::PopFront() +{ + VMA_HEAVY_ASSERT(m_Count > 0); + ItemType* const pFrontItem = m_pFront; + ItemType* const pNextItem = pFrontItem->pNext; + if (pNextItem != VMA_NULL) + { + pNextItem->pPrev = VMA_NULL; + } + m_pFront = pNextItem; + m_ItemAllocator.Free(pFrontItem); + --m_Count; +} + +template +void VmaRawList::PopBack() +{ + VMA_HEAVY_ASSERT(m_Count > 0); + ItemType* const pBackItem = m_pBack; + ItemType* const pPrevItem = pBackItem->pPrev; + if(pPrevItem != VMA_NULL) + { + pPrevItem->pNext = VMA_NULL; + } + m_pBack = pPrevItem; + m_ItemAllocator.Free(pBackItem); + --m_Count; +} + +template +void VmaRawList::Clear() +{ + if (IsEmpty() == false) + { + ItemType* pItem = m_pBack; + while (pItem != VMA_NULL) + { + ItemType* const pPrevItem = pItem->pPrev; + m_ItemAllocator.Free(pItem); + pItem = pPrevItem; + } + m_pFront = VMA_NULL; + m_pBack = VMA_NULL; + m_Count = 0; + } +} + +template +void VmaRawList::Remove(ItemType* pItem) +{ + VMA_HEAVY_ASSERT(pItem != VMA_NULL); + VMA_HEAVY_ASSERT(m_Count > 0); + + if(pItem->pPrev != VMA_NULL) + { + pItem->pPrev->pNext = pItem->pNext; + } + else + { + VMA_HEAVY_ASSERT(m_pFront == pItem); + m_pFront = pItem->pNext; + } + + if(pItem->pNext != VMA_NULL) + { + pItem->pNext->pPrev = pItem->pPrev; + } + else + { + VMA_HEAVY_ASSERT(m_pBack == pItem); + m_pBack = pItem->pPrev; + } + + m_ItemAllocator.Free(pItem); + --m_Count; +} + +template +VmaListItem* VmaRawList::InsertBefore(ItemType* pItem) +{ + if(pItem != VMA_NULL) + { + ItemType* const prevItem = pItem->pPrev; + ItemType* const newItem = m_ItemAllocator.Alloc(); + newItem->pPrev = prevItem; + newItem->pNext = pItem; + pItem->pPrev = newItem; + if(prevItem != VMA_NULL) + { + prevItem->pNext = newItem; + } + else + { + VMA_HEAVY_ASSERT(m_pFront == pItem); + m_pFront = newItem; + } + ++m_Count; + return newItem; + } + else + return PushBack(); +} + +template +VmaListItem* VmaRawList::InsertAfter(ItemType* pItem) +{ + if(pItem != VMA_NULL) + { + ItemType* const nextItem = pItem->pNext; + ItemType* const newItem = m_ItemAllocator.Alloc(); + newItem->pNext = nextItem; + newItem->pPrev = pItem; + pItem->pNext = newItem; + if(nextItem != VMA_NULL) + { + nextItem->pPrev = newItem; + } + else + { + VMA_HEAVY_ASSERT(m_pBack == pItem); + m_pBack = newItem; + } + ++m_Count; + return newItem; + } + else + return PushFront(); +} + +template +VmaListItem* VmaRawList::InsertBefore(ItemType* pItem, const T& value) +{ + ItemType* const newItem = InsertBefore(pItem); + newItem->Value = value; + return newItem; +} + +template +VmaListItem* VmaRawList::InsertAfter(ItemType* pItem, const T& value) +{ + ItemType* const newItem = InsertAfter(pItem); + newItem->Value = value; + return newItem; +} +#endif // _VMA_RAW_LIST_FUNCTIONS +#endif // _VMA_RAW_LIST + +#ifndef _VMA_LIST +template +class VmaList +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaList) +public: + class reverse_iterator; + class const_iterator; + class const_reverse_iterator; + + class iterator + { + friend class const_iterator; + friend class VmaList; + public: + iterator() : m_pList(VMA_NULL), m_pItem(VMA_NULL) {} + iterator(const reverse_iterator& src) : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + + T& operator*() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return m_pItem->Value; } + T* operator->() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return &m_pItem->Value; } + + bool operator==(const iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem == rhs.m_pItem; } + bool operator!=(const iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem != rhs.m_pItem; } + + iterator operator++(int) { iterator result = *this; ++*this; return result; } + iterator operator--(int) { iterator result = *this; --*this; return result; } + + iterator& operator++() { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); m_pItem = m_pItem->pNext; return *this; } + iterator& operator--(); + + private: + VmaRawList* m_pList; + VmaListItem* m_pItem; + + iterator(VmaRawList* pList, VmaListItem* pItem) : m_pList(pList), m_pItem(pItem) {} + }; + class reverse_iterator + { + friend class const_reverse_iterator; + friend class VmaList; + public: + reverse_iterator() : m_pList(VMA_NULL), m_pItem(VMA_NULL) {} + reverse_iterator(const iterator& src) : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + + T& operator*() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return m_pItem->Value; } + T* operator->() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return &m_pItem->Value; } + + bool operator==(const reverse_iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem == rhs.m_pItem; } + bool operator!=(const reverse_iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem != rhs.m_pItem; } + + reverse_iterator operator++(int) { reverse_iterator result = *this; ++* this; return result; } + reverse_iterator operator--(int) { reverse_iterator result = *this; --* this; return result; } + + reverse_iterator& operator++() { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); m_pItem = m_pItem->pPrev; return *this; } + reverse_iterator& operator--(); + + private: + VmaRawList* m_pList; + VmaListItem* m_pItem; + + reverse_iterator(VmaRawList* pList, VmaListItem* pItem) : m_pList(pList), m_pItem(pItem) {} + }; + class const_iterator + { + friend class VmaList; + public: + const_iterator() : m_pList(VMA_NULL), m_pItem(VMA_NULL) {} + const_iterator(const iterator& src) : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + const_iterator(const reverse_iterator& src) : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + + iterator drop_const() { return { const_cast*>(m_pList), const_cast*>(m_pItem) }; } + + const T& operator*() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return m_pItem->Value; } + const T* operator->() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return &m_pItem->Value; } + + bool operator==(const const_iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem == rhs.m_pItem; } + bool operator!=(const const_iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem != rhs.m_pItem; } + + const_iterator operator++(int) { const_iterator result = *this; ++* this; return result; } + const_iterator operator--(int) { const_iterator result = *this; --* this; return result; } + + const_iterator& operator++() { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); m_pItem = m_pItem->pNext; return *this; } + const_iterator& operator--(); + + private: + const VmaRawList* m_pList; + const VmaListItem* m_pItem; + + const_iterator(const VmaRawList* pList, const VmaListItem* pItem) : m_pList(pList), m_pItem(pItem) {} + }; + class const_reverse_iterator + { + friend class VmaList; + public: + const_reverse_iterator() : m_pList(VMA_NULL), m_pItem(VMA_NULL) {} + const_reverse_iterator(const reverse_iterator& src) : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + const_reverse_iterator(const iterator& src) : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + + reverse_iterator drop_const() { return { const_cast*>(m_pList), const_cast*>(m_pItem) }; } + + const T& operator*() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return m_pItem->Value; } + const T* operator->() const { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); return &m_pItem->Value; } + + bool operator==(const const_reverse_iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem == rhs.m_pItem; } + bool operator!=(const const_reverse_iterator& rhs) const { VMA_HEAVY_ASSERT(m_pList == rhs.m_pList); return m_pItem != rhs.m_pItem; } + + const_reverse_iterator operator++(int) { const_reverse_iterator result = *this; ++* this; return result; } + const_reverse_iterator operator--(int) { const_reverse_iterator result = *this; --* this; return result; } + + const_reverse_iterator& operator++() { VMA_HEAVY_ASSERT(m_pItem != VMA_NULL); m_pItem = m_pItem->pPrev; return *this; } + const_reverse_iterator& operator--(); + + private: + const VmaRawList* m_pList; + const VmaListItem* m_pItem; + + const_reverse_iterator(const VmaRawList* pList, const VmaListItem* pItem) : m_pList(pList), m_pItem(pItem) {} + }; + + VmaList(const AllocatorT& allocator) : m_RawList(allocator.m_pCallbacks) {} + + bool empty() const { return m_RawList.IsEmpty(); } + size_t size() const { return m_RawList.GetCount(); } + + iterator begin() { return iterator(&m_RawList, m_RawList.Front()); } + iterator end() { return iterator(&m_RawList, VMA_NULL); } + + const_iterator cbegin() const { return const_iterator(&m_RawList, m_RawList.Front()); } + const_iterator cend() const { return const_iterator(&m_RawList, VMA_NULL); } + + const_iterator begin() const { return cbegin(); } + const_iterator end() const { return cend(); } + + reverse_iterator rbegin() { return reverse_iterator(&m_RawList, m_RawList.Back()); } + reverse_iterator rend() { return reverse_iterator(&m_RawList, VMA_NULL); } + + const_reverse_iterator crbegin() const { return const_reverse_iterator(&m_RawList, m_RawList.Back()); } + const_reverse_iterator crend() const { return const_reverse_iterator(&m_RawList, VMA_NULL); } + + const_reverse_iterator rbegin() const { return crbegin(); } + const_reverse_iterator rend() const { return crend(); } + + void push_back(const T& value) { m_RawList.PushBack(value); } + iterator insert(iterator it, const T& value) { return iterator(&m_RawList, m_RawList.InsertBefore(it.m_pItem, value)); } + + void clear() { m_RawList.Clear(); } + void erase(iterator it) { m_RawList.Remove(it.m_pItem); } + +private: + VmaRawList m_RawList; +}; + +#ifndef _VMA_LIST_FUNCTIONS +template +typename VmaList::iterator& VmaList::iterator::operator--() +{ + if (m_pItem != VMA_NULL) + { + m_pItem = m_pItem->pPrev; + } + else + { + VMA_HEAVY_ASSERT(!m_pList->IsEmpty()); + m_pItem = m_pList->Back(); + } + return *this; +} + +template +typename VmaList::reverse_iterator& VmaList::reverse_iterator::operator--() +{ + if (m_pItem != VMA_NULL) + { + m_pItem = m_pItem->pNext; + } + else + { + VMA_HEAVY_ASSERT(!m_pList->IsEmpty()); + m_pItem = m_pList->Front(); + } + return *this; +} + +template +typename VmaList::const_iterator& VmaList::const_iterator::operator--() +{ + if (m_pItem != VMA_NULL) + { + m_pItem = m_pItem->pPrev; + } + else + { + VMA_HEAVY_ASSERT(!m_pList->IsEmpty()); + m_pItem = m_pList->Back(); + } + return *this; +} + +template +typename VmaList::const_reverse_iterator& VmaList::const_reverse_iterator::operator--() +{ + if (m_pItem != VMA_NULL) + { + m_pItem = m_pItem->pNext; + } + else + { + VMA_HEAVY_ASSERT(!m_pList->IsEmpty()); + m_pItem = m_pList->Back(); + } + return *this; +} +#endif // _VMA_LIST_FUNCTIONS +#endif // _VMA_LIST + +#ifndef _VMA_INTRUSIVE_LINKED_LIST +/* +Expected interface of ItemTypeTraits: +struct MyItemTypeTraits +{ + typedef MyItem ItemType; + static ItemType* GetPrev(const ItemType* item) { return item->myPrevPtr; } + static ItemType* GetNext(const ItemType* item) { return item->myNextPtr; } + static ItemType*& AccessPrev(ItemType* item) { return item->myPrevPtr; } + static ItemType*& AccessNext(ItemType* item) { return item->myNextPtr; } +}; +*/ +template +class VmaIntrusiveLinkedList +{ +public: + typedef typename ItemTypeTraits::ItemType ItemType; + static ItemType* GetPrev(const ItemType* item) { return ItemTypeTraits::GetPrev(item); } + static ItemType* GetNext(const ItemType* item) { return ItemTypeTraits::GetNext(item); } + + // Movable, not copyable. + VmaIntrusiveLinkedList() = default; + VmaIntrusiveLinkedList(VmaIntrusiveLinkedList && src); + VmaIntrusiveLinkedList(const VmaIntrusiveLinkedList&) = delete; + VmaIntrusiveLinkedList& operator=(VmaIntrusiveLinkedList&& src); + VmaIntrusiveLinkedList& operator=(const VmaIntrusiveLinkedList&) = delete; + ~VmaIntrusiveLinkedList() { VMA_HEAVY_ASSERT(IsEmpty()); } + + size_t GetCount() const { return m_Count; } + bool IsEmpty() const { return m_Count == 0; } + ItemType* Front() { return m_Front; } + ItemType* Back() { return m_Back; } + const ItemType* Front() const { return m_Front; } + const ItemType* Back() const { return m_Back; } + + void PushBack(ItemType* item); + void PushFront(ItemType* item); + ItemType* PopBack(); + ItemType* PopFront(); + + // MyItem can be null - it means PushBack. + void InsertBefore(ItemType* existingItem, ItemType* newItem); + // MyItem can be null - it means PushFront. + void InsertAfter(ItemType* existingItem, ItemType* newItem); + void Remove(ItemType* item); + void RemoveAll(); + +private: + ItemType* m_Front = VMA_NULL; + ItemType* m_Back = VMA_NULL; + size_t m_Count = 0; +}; + +#ifndef _VMA_INTRUSIVE_LINKED_LIST_FUNCTIONS +template +VmaIntrusiveLinkedList::VmaIntrusiveLinkedList(VmaIntrusiveLinkedList&& src) + : m_Front(src.m_Front), m_Back(src.m_Back), m_Count(src.m_Count) +{ + src.m_Front = src.m_Back = VMA_NULL; + src.m_Count = 0; +} + +template +VmaIntrusiveLinkedList& VmaIntrusiveLinkedList::operator=(VmaIntrusiveLinkedList&& src) +{ + if (&src != this) + { + VMA_HEAVY_ASSERT(IsEmpty()); + m_Front = src.m_Front; + m_Back = src.m_Back; + m_Count = src.m_Count; + src.m_Front = src.m_Back = VMA_NULL; + src.m_Count = 0; + } + return *this; +} + +template +void VmaIntrusiveLinkedList::PushBack(ItemType* item) +{ + VMA_HEAVY_ASSERT(ItemTypeTraits::GetPrev(item) == VMA_NULL && ItemTypeTraits::GetNext(item) == VMA_NULL); + if (IsEmpty()) + { + m_Front = item; + m_Back = item; + m_Count = 1; + } + else + { + ItemTypeTraits::AccessPrev(item) = m_Back; + ItemTypeTraits::AccessNext(m_Back) = item; + m_Back = item; + ++m_Count; + } +} + +template +void VmaIntrusiveLinkedList::PushFront(ItemType* item) +{ + VMA_HEAVY_ASSERT(ItemTypeTraits::GetPrev(item) == VMA_NULL && ItemTypeTraits::GetNext(item) == VMA_NULL); + if (IsEmpty()) + { + m_Front = item; + m_Back = item; + m_Count = 1; + } + else + { + ItemTypeTraits::AccessNext(item) = m_Front; + ItemTypeTraits::AccessPrev(m_Front) = item; + m_Front = item; + ++m_Count; + } +} + +template +typename VmaIntrusiveLinkedList::ItemType* VmaIntrusiveLinkedList::PopBack() +{ + VMA_HEAVY_ASSERT(m_Count > 0); + ItemType* const backItem = m_Back; + ItemType* const prevItem = ItemTypeTraits::GetPrev(backItem); + if (prevItem != VMA_NULL) + { + ItemTypeTraits::AccessNext(prevItem) = VMA_NULL; + } + m_Back = prevItem; + --m_Count; + ItemTypeTraits::AccessPrev(backItem) = VMA_NULL; + ItemTypeTraits::AccessNext(backItem) = VMA_NULL; + return backItem; +} + +template +typename VmaIntrusiveLinkedList::ItemType* VmaIntrusiveLinkedList::PopFront() +{ + VMA_HEAVY_ASSERT(m_Count > 0); + ItemType* const frontItem = m_Front; + ItemType* const nextItem = ItemTypeTraits::GetNext(frontItem); + if (nextItem != VMA_NULL) + { + ItemTypeTraits::AccessPrev(nextItem) = VMA_NULL; + } + m_Front = nextItem; + --m_Count; + ItemTypeTraits::AccessPrev(frontItem) = VMA_NULL; + ItemTypeTraits::AccessNext(frontItem) = VMA_NULL; + return frontItem; +} + +template +void VmaIntrusiveLinkedList::InsertBefore(ItemType* existingItem, ItemType* newItem) +{ + VMA_HEAVY_ASSERT(newItem != VMA_NULL && ItemTypeTraits::GetPrev(newItem) == VMA_NULL && ItemTypeTraits::GetNext(newItem) == VMA_NULL); + if (existingItem != VMA_NULL) + { + ItemType* const prevItem = ItemTypeTraits::GetPrev(existingItem); + ItemTypeTraits::AccessPrev(newItem) = prevItem; + ItemTypeTraits::AccessNext(newItem) = existingItem; + ItemTypeTraits::AccessPrev(existingItem) = newItem; + if (prevItem != VMA_NULL) + { + ItemTypeTraits::AccessNext(prevItem) = newItem; + } + else + { + VMA_HEAVY_ASSERT(m_Front == existingItem); + m_Front = newItem; + } + ++m_Count; + } + else + PushBack(newItem); +} + +template +void VmaIntrusiveLinkedList::InsertAfter(ItemType* existingItem, ItemType* newItem) +{ + VMA_HEAVY_ASSERT(newItem != VMA_NULL && ItemTypeTraits::GetPrev(newItem) == VMA_NULL && ItemTypeTraits::GetNext(newItem) == VMA_NULL); + if (existingItem != VMA_NULL) + { + ItemType* const nextItem = ItemTypeTraits::GetNext(existingItem); + ItemTypeTraits::AccessNext(newItem) = nextItem; + ItemTypeTraits::AccessPrev(newItem) = existingItem; + ItemTypeTraits::AccessNext(existingItem) = newItem; + if (nextItem != VMA_NULL) + { + ItemTypeTraits::AccessPrev(nextItem) = newItem; + } + else + { + VMA_HEAVY_ASSERT(m_Back == existingItem); + m_Back = newItem; + } + ++m_Count; + } + else + return PushFront(newItem); +} + +template +void VmaIntrusiveLinkedList::Remove(ItemType* item) +{ + VMA_HEAVY_ASSERT(item != VMA_NULL && m_Count > 0); + if (ItemTypeTraits::GetPrev(item) != VMA_NULL) + { + ItemTypeTraits::AccessNext(ItemTypeTraits::AccessPrev(item)) = ItemTypeTraits::GetNext(item); + } + else + { + VMA_HEAVY_ASSERT(m_Front == item); + m_Front = ItemTypeTraits::GetNext(item); + } + + if (ItemTypeTraits::GetNext(item) != VMA_NULL) + { + ItemTypeTraits::AccessPrev(ItemTypeTraits::AccessNext(item)) = ItemTypeTraits::GetPrev(item); + } + else + { + VMA_HEAVY_ASSERT(m_Back == item); + m_Back = ItemTypeTraits::GetPrev(item); + } + ItemTypeTraits::AccessPrev(item) = VMA_NULL; + ItemTypeTraits::AccessNext(item) = VMA_NULL; + --m_Count; +} + +template +void VmaIntrusiveLinkedList::RemoveAll() +{ + if (!IsEmpty()) + { + ItemType* item = m_Back; + while (item != VMA_NULL) + { + ItemType* const prevItem = ItemTypeTraits::AccessPrev(item); + ItemTypeTraits::AccessPrev(item) = VMA_NULL; + ItemTypeTraits::AccessNext(item) = VMA_NULL; + item = prevItem; + } + m_Front = VMA_NULL; + m_Back = VMA_NULL; + m_Count = 0; + } +} +#endif // _VMA_INTRUSIVE_LINKED_LIST_FUNCTIONS +#endif // _VMA_INTRUSIVE_LINKED_LIST + +#if !defined(_VMA_STRING_BUILDER) && VMA_STATS_STRING_ENABLED +class VmaStringBuilder +{ +public: + VmaStringBuilder(const VkAllocationCallbacks* allocationCallbacks) : m_Data(VmaStlAllocator(allocationCallbacks)) {} + ~VmaStringBuilder() = default; + + size_t GetLength() const { return m_Data.size(); } + const char* GetData() const { return m_Data.data(); } + void AddNewLine() { Add('\n'); } + void Add(char ch) { m_Data.push_back(ch); } + + void Add(const char* pStr); + void AddNumber(uint32_t num); + void AddNumber(uint64_t num); + void AddPointer(const void* ptr); + +private: + VmaVector> m_Data; +}; + +#ifndef _VMA_STRING_BUILDER_FUNCTIONS +void VmaStringBuilder::Add(const char* pStr) +{ + const size_t strLen = strlen(pStr); + if (strLen > 0) + { + const size_t oldCount = m_Data.size(); + m_Data.resize(oldCount + strLen); + memcpy(m_Data.data() + oldCount, pStr, strLen); + } +} + +void VmaStringBuilder::AddNumber(uint32_t num) +{ + char buf[11]; + buf[10] = '\0'; + char* p = &buf[10]; + do + { + *--p = '0' + (char)(num % 10); + num /= 10; + } while (num); + Add(p); +} + +void VmaStringBuilder::AddNumber(uint64_t num) +{ + char buf[21]; + buf[20] = '\0'; + char* p = &buf[20]; + do + { + *--p = '0' + (char)(num % 10); + num /= 10; + } while (num); + Add(p); +} + +void VmaStringBuilder::AddPointer(const void* ptr) +{ + char buf[21]; + VmaPtrToStr(buf, sizeof(buf), ptr); + Add(buf); +} +#endif //_VMA_STRING_BUILDER_FUNCTIONS +#endif // _VMA_STRING_BUILDER + +#if !defined(_VMA_JSON_WRITER) && VMA_STATS_STRING_ENABLED +/* +Allows to conveniently build a correct JSON document to be written to the +VmaStringBuilder passed to the constructor. +*/ +class VmaJsonWriter +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaJsonWriter) +public: + // sb - string builder to write the document to. Must remain alive for the whole lifetime of this object. + VmaJsonWriter(const VkAllocationCallbacks* pAllocationCallbacks, VmaStringBuilder& sb); + ~VmaJsonWriter(); + + // Begins object by writing "{". + // Inside an object, you must call pairs of WriteString and a value, e.g.: + // j.BeginObject(true); j.WriteString("A"); j.WriteNumber(1); j.WriteString("B"); j.WriteNumber(2); j.EndObject(); + // Will write: { "A": 1, "B": 2 } + void BeginObject(bool singleLine = false); + // Ends object by writing "}". + void EndObject(); + + // Begins array by writing "[". + // Inside an array, you can write a sequence of any values. + void BeginArray(bool singleLine = false); + // Ends array by writing "[". + void EndArray(); + + // Writes a string value inside "". + // pStr can contain any ANSI characters, including '"', new line etc. - they will be properly escaped. + void WriteString(const char* pStr); + + // Begins writing a string value. + // Call BeginString, ContinueString, ContinueString, ..., EndString instead of + // WriteString to conveniently build the string content incrementally, made of + // parts including numbers. + void BeginString(const char* pStr = VMA_NULL); + // Posts next part of an open string. + void ContinueString(const char* pStr); + // Posts next part of an open string. The number is converted to decimal characters. + void ContinueString(uint32_t n); + void ContinueString(uint64_t n); + // Posts next part of an open string. Pointer value is converted to characters + // using "%p" formatting - shown as hexadecimal number, e.g.: 000000081276Ad00 + void ContinueString_Pointer(const void* ptr); + // Ends writing a string value by writing '"'. + void EndString(const char* pStr = VMA_NULL); + + // Writes a number value. + void WriteNumber(uint32_t n); + void WriteNumber(uint64_t n); + // Writes a boolean value - false or true. + void WriteBool(bool b); + // Writes a null value. + void WriteNull(); + +private: + enum COLLECTION_TYPE + { + COLLECTION_TYPE_OBJECT, + COLLECTION_TYPE_ARRAY, + }; + struct StackItem + { + COLLECTION_TYPE type; + uint32_t valueCount; + bool singleLineMode; + }; + + static const char* const INDENT; + + VmaStringBuilder& m_SB; + VmaVector< StackItem, VmaStlAllocator > m_Stack; + bool m_InsideString; + + void BeginValue(bool isString); + void WriteIndent(bool oneLess = false); +}; +const char* const VmaJsonWriter::INDENT = " "; + +#ifndef _VMA_JSON_WRITER_FUNCTIONS +VmaJsonWriter::VmaJsonWriter(const VkAllocationCallbacks* pAllocationCallbacks, VmaStringBuilder& sb) + : m_SB(sb), + m_Stack(VmaStlAllocator(pAllocationCallbacks)), + m_InsideString(false) {} + +VmaJsonWriter::~VmaJsonWriter() +{ + VMA_ASSERT(!m_InsideString); + VMA_ASSERT(m_Stack.empty()); +} + +void VmaJsonWriter::BeginObject(bool singleLine) +{ + VMA_ASSERT(!m_InsideString); + + BeginValue(false); + m_SB.Add('{'); + + StackItem item; + item.type = COLLECTION_TYPE_OBJECT; + item.valueCount = 0; + item.singleLineMode = singleLine; + m_Stack.push_back(item); +} + +void VmaJsonWriter::EndObject() +{ + VMA_ASSERT(!m_InsideString); + + WriteIndent(true); + m_SB.Add('}'); + + VMA_ASSERT(!m_Stack.empty() && m_Stack.back().type == COLLECTION_TYPE_OBJECT); + m_Stack.pop_back(); +} + +void VmaJsonWriter::BeginArray(bool singleLine) +{ + VMA_ASSERT(!m_InsideString); + + BeginValue(false); + m_SB.Add('['); + + StackItem item; + item.type = COLLECTION_TYPE_ARRAY; + item.valueCount = 0; + item.singleLineMode = singleLine; + m_Stack.push_back(item); +} + +void VmaJsonWriter::EndArray() +{ + VMA_ASSERT(!m_InsideString); + + WriteIndent(true); + m_SB.Add(']'); + + VMA_ASSERT(!m_Stack.empty() && m_Stack.back().type == COLLECTION_TYPE_ARRAY); + m_Stack.pop_back(); +} + +void VmaJsonWriter::WriteString(const char* pStr) +{ + BeginString(pStr); + EndString(); +} + +void VmaJsonWriter::BeginString(const char* pStr) +{ + VMA_ASSERT(!m_InsideString); + + BeginValue(true); + m_SB.Add('"'); + m_InsideString = true; + if (pStr != VMA_NULL && pStr[0] != '\0') + { + ContinueString(pStr); + } +} + +void VmaJsonWriter::ContinueString(const char* pStr) +{ + VMA_ASSERT(m_InsideString); + + const size_t strLen = strlen(pStr); + for (size_t i = 0; i < strLen; ++i) + { + char ch = pStr[i]; + if (ch == '\\') + { + m_SB.Add("\\\\"); + } + else if (ch == '"') + { + m_SB.Add("\\\""); + } + else if ((uint8_t)ch >= 32) + { + m_SB.Add(ch); + } + else switch (ch) + { + case '\b': + m_SB.Add("\\b"); + break; + case '\f': + m_SB.Add("\\f"); + break; + case '\n': + m_SB.Add("\\n"); + break; + case '\r': + m_SB.Add("\\r"); + break; + case '\t': + m_SB.Add("\\t"); + break; + default: + VMA_ASSERT(0 && "Character not currently supported."); + } + } +} + +void VmaJsonWriter::ContinueString(uint32_t n) +{ + VMA_ASSERT(m_InsideString); + m_SB.AddNumber(n); +} + +void VmaJsonWriter::ContinueString(uint64_t n) +{ + VMA_ASSERT(m_InsideString); + m_SB.AddNumber(n); +} + +void VmaJsonWriter::ContinueString_Pointer(const void* ptr) +{ + VMA_ASSERT(m_InsideString); + m_SB.AddPointer(ptr); +} + +void VmaJsonWriter::EndString(const char* pStr) +{ + VMA_ASSERT(m_InsideString); + if (pStr != VMA_NULL && pStr[0] != '\0') + { + ContinueString(pStr); + } + m_SB.Add('"'); + m_InsideString = false; +} + +void VmaJsonWriter::WriteNumber(uint32_t n) +{ + VMA_ASSERT(!m_InsideString); + BeginValue(false); + m_SB.AddNumber(n); +} + +void VmaJsonWriter::WriteNumber(uint64_t n) +{ + VMA_ASSERT(!m_InsideString); + BeginValue(false); + m_SB.AddNumber(n); +} + +void VmaJsonWriter::WriteBool(bool b) +{ + VMA_ASSERT(!m_InsideString); + BeginValue(false); + m_SB.Add(b ? "true" : "false"); +} + +void VmaJsonWriter::WriteNull() +{ + VMA_ASSERT(!m_InsideString); + BeginValue(false); + m_SB.Add("null"); +} + +void VmaJsonWriter::BeginValue(bool isString) +{ + if (!m_Stack.empty()) + { + StackItem& currItem = m_Stack.back(); + if (currItem.type == COLLECTION_TYPE_OBJECT && + currItem.valueCount % 2 == 0) + { + VMA_ASSERT(isString); + } + + if (currItem.type == COLLECTION_TYPE_OBJECT && + currItem.valueCount % 2 != 0) + { + m_SB.Add(": "); + } + else if (currItem.valueCount > 0) + { + m_SB.Add(", "); + WriteIndent(); + } + else + { + WriteIndent(); + } + ++currItem.valueCount; + } +} + +void VmaJsonWriter::WriteIndent(bool oneLess) +{ + if (!m_Stack.empty() && !m_Stack.back().singleLineMode) + { + m_SB.AddNewLine(); + + size_t count = m_Stack.size(); + if (count > 0 && oneLess) + { + --count; + } + for (size_t i = 0; i < count; ++i) + { + m_SB.Add(INDENT); + } + } +} +#endif // _VMA_JSON_WRITER_FUNCTIONS + +static void VmaPrintDetailedStatistics(VmaJsonWriter& json, const VmaDetailedStatistics& stat) +{ + json.BeginObject(); + + json.WriteString("BlockCount"); + json.WriteNumber(stat.statistics.blockCount); + json.WriteString("BlockBytes"); + json.WriteNumber(stat.statistics.blockBytes); + json.WriteString("AllocationCount"); + json.WriteNumber(stat.statistics.allocationCount); + json.WriteString("AllocationBytes"); + json.WriteNumber(stat.statistics.allocationBytes); + json.WriteString("UnusedRangeCount"); + json.WriteNumber(stat.unusedRangeCount); + + if (stat.statistics.allocationCount > 1) + { + json.WriteString("AllocationSizeMin"); + json.WriteNumber(stat.allocationSizeMin); + json.WriteString("AllocationSizeMax"); + json.WriteNumber(stat.allocationSizeMax); + } + if (stat.unusedRangeCount > 1) + { + json.WriteString("UnusedRangeSizeMin"); + json.WriteNumber(stat.unusedRangeSizeMin); + json.WriteString("UnusedRangeSizeMax"); + json.WriteNumber(stat.unusedRangeSizeMax); + } + json.EndObject(); +} +#endif // _VMA_JSON_WRITER + +#ifndef _VMA_MAPPING_HYSTERESIS + +class VmaMappingHysteresis +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaMappingHysteresis) +public: + VmaMappingHysteresis() = default; + + uint32_t GetExtraMapping() const { return m_ExtraMapping; } + + // Call when Map was called. + // Returns true if switched to extra +1 mapping reference count. + bool PostMap() + { +#if VMA_MAPPING_HYSTERESIS_ENABLED + if(m_ExtraMapping == 0) + { + ++m_MajorCounter; + if(m_MajorCounter >= COUNTER_MIN_EXTRA_MAPPING) + { + m_ExtraMapping = 1; + m_MajorCounter = 0; + m_MinorCounter = 0; + return true; + } + } + else // m_ExtraMapping == 1 + PostMinorCounter(); +#endif // #if VMA_MAPPING_HYSTERESIS_ENABLED + return false; + } + + // Call when Unmap was called. + void PostUnmap() + { +#if VMA_MAPPING_HYSTERESIS_ENABLED + if(m_ExtraMapping == 0) + ++m_MajorCounter; + else // m_ExtraMapping == 1 + PostMinorCounter(); +#endif // #if VMA_MAPPING_HYSTERESIS_ENABLED + } + + // Call when allocation was made from the memory block. + void PostAlloc() + { +#if VMA_MAPPING_HYSTERESIS_ENABLED + if(m_ExtraMapping == 1) + ++m_MajorCounter; + else // m_ExtraMapping == 0 + PostMinorCounter(); +#endif // #if VMA_MAPPING_HYSTERESIS_ENABLED + } + + // Call when allocation was freed from the memory block. + // Returns true if switched to extra -1 mapping reference count. + bool PostFree() + { +#if VMA_MAPPING_HYSTERESIS_ENABLED + if(m_ExtraMapping == 1) + { + ++m_MajorCounter; + if(m_MajorCounter >= COUNTER_MIN_EXTRA_MAPPING && + m_MajorCounter > m_MinorCounter + 1) + { + m_ExtraMapping = 0; + m_MajorCounter = 0; + m_MinorCounter = 0; + return true; + } + } + else // m_ExtraMapping == 0 + PostMinorCounter(); +#endif // #if VMA_MAPPING_HYSTERESIS_ENABLED + return false; + } + +private: + static const int32_t COUNTER_MIN_EXTRA_MAPPING = 7; + + uint32_t m_MinorCounter = 0; + uint32_t m_MajorCounter = 0; + uint32_t m_ExtraMapping = 0; // 0 or 1. + + void PostMinorCounter() + { + if(m_MinorCounter < m_MajorCounter) + { + ++m_MinorCounter; + } + else if(m_MajorCounter > 0) + { + --m_MajorCounter; + --m_MinorCounter; + } + } +}; + +#endif // _VMA_MAPPING_HYSTERESIS + +#if VMA_EXTERNAL_MEMORY_WIN32 +class VmaWin32Handle +{ +public: + VmaWin32Handle() noexcept : m_hHandle(VMA_NULL) { } + explicit VmaWin32Handle(HANDLE hHandle) noexcept : m_hHandle(hHandle) { } + ~VmaWin32Handle() noexcept { if (m_hHandle != VMA_NULL) { ::CloseHandle(m_hHandle); } } + VMA_CLASS_NO_COPY_NO_MOVE(VmaWin32Handle) + +public: + // Strengthened + VkResult GetHandle(VkDevice device, VkDeviceMemory memory, PFN_vkGetMemoryWin32HandleKHR pvkGetMemoryWin32HandleKHR, HANDLE hTargetProcess, bool useMutex, HANDLE* pHandle) noexcept + { + *pHandle = VMA_NULL; + // Try to get handle first. + if (m_hHandle != VMA_NULL) + { + *pHandle = Duplicate(hTargetProcess); + return VK_SUCCESS; + } + + VkResult res = VK_SUCCESS; + // If failed, try to create it. + { + VmaMutexLockWrite lock(m_Mutex, useMutex); + if (m_hHandle == VMA_NULL) + { + res = Create(device, memory, pvkGetMemoryWin32HandleKHR, &m_hHandle); + } + } + + *pHandle = Duplicate(hTargetProcess); + return res; + } + + operator bool() const noexcept { return m_hHandle != VMA_NULL; } +private: + // Not atomic + static VkResult Create(VkDevice device, VkDeviceMemory memory, PFN_vkGetMemoryWin32HandleKHR pvkGetMemoryWin32HandleKHR, HANDLE* pHandle) noexcept + { + VkResult res = VK_ERROR_FEATURE_NOT_PRESENT; + if (pvkGetMemoryWin32HandleKHR != VMA_NULL) + { + VkMemoryGetWin32HandleInfoKHR handleInfo{ }; + handleInfo.sType = VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR; + handleInfo.memory = memory; + handleInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT_KHR; + res = pvkGetMemoryWin32HandleKHR(device, &handleInfo, pHandle); + } + return res; + } + HANDLE Duplicate(HANDLE hTargetProcess = VMA_NULL) const noexcept + { + if (!m_hHandle) + return m_hHandle; + + HANDLE hCurrentProcess = ::GetCurrentProcess(); + HANDLE hDupHandle = VMA_NULL; + if (!::DuplicateHandle(hCurrentProcess, m_hHandle, hTargetProcess ? hTargetProcess : hCurrentProcess, &hDupHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) + { + VMA_ASSERT(0 && "Failed to duplicate handle."); + } + return hDupHandle; + } +private: + HANDLE m_hHandle; + VMA_RW_MUTEX m_Mutex; // Protects access m_Handle +}; +#else +class VmaWin32Handle +{ + // ABI compatibility + void* placeholder = VMA_NULL; + VMA_RW_MUTEX placeholder2; +}; +#endif // VMA_EXTERNAL_MEMORY_WIN32 + + +#ifndef _VMA_DEVICE_MEMORY_BLOCK +/* +Represents a single block of device memory (`VkDeviceMemory`) with all the +data about its regions (aka suballocations, #VmaAllocation), assigned and free. + +Thread-safety: +- Access to m_pMetadata must be externally synchronized. +- Map, Unmap, Bind* are synchronized internally. +*/ +class VmaDeviceMemoryBlock +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaDeviceMemoryBlock) +public: + VmaBlockMetadata* m_pMetadata; + + VmaDeviceMemoryBlock(VmaAllocator hAllocator); + ~VmaDeviceMemoryBlock(); + + // Always call after construction. + void Init( + VmaAllocator hAllocator, + VmaPool hParentPool, + uint32_t newMemoryTypeIndex, + VkDeviceMemory newMemory, + VkDeviceSize newSize, + uint32_t id, + uint32_t algorithm, + VkDeviceSize bufferImageGranularity); + // Always call before destruction. + void Destroy(VmaAllocator allocator); + + VmaPool GetParentPool() const { return m_hParentPool; } + VkDeviceMemory GetDeviceMemory() const { return m_hMemory; } + uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; } + uint32_t GetId() const { return m_Id; } + void* GetMappedData() const { return m_pMappedData; } + uint32_t GetMapRefCount() const { return m_MapCount; } + + // Call when allocation/free was made from m_pMetadata. + // Used for m_MappingHysteresis. + void PostAlloc(VmaAllocator hAllocator); + void PostFree(VmaAllocator hAllocator); + + // Validates all data structures inside this object. If not valid, returns false. + bool Validate() const; + VkResult CheckCorruption(VmaAllocator hAllocator); + + // ppData can be null. + VkResult Map(VmaAllocator hAllocator, uint32_t count, void** ppData); + void Unmap(VmaAllocator hAllocator, uint32_t count); + + VkResult WriteMagicValueAfterAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize); + VkResult ValidateMagicValueAfterAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize); + + VkResult BindBufferMemory( + const VmaAllocator hAllocator, + const VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkBuffer hBuffer, + const void* pNext); + VkResult BindImageMemory( + const VmaAllocator hAllocator, + const VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkImage hImage, + const void* pNext); +#if VMA_EXTERNAL_MEMORY_WIN32 + VkResult CreateWin32Handle( + const VmaAllocator hAllocator, + PFN_vkGetMemoryWin32HandleKHR pvkGetMemoryWin32HandleKHR, + HANDLE hTargetProcess, + HANDLE* pHandle)noexcept; +#endif // VMA_EXTERNAL_MEMORY_WIN32 +private: + VmaPool m_hParentPool; // VK_NULL_HANDLE if not belongs to custom pool. + uint32_t m_MemoryTypeIndex; + uint32_t m_Id; + VkDeviceMemory m_hMemory; + + /* + Protects access to m_hMemory so it is not used by multiple threads simultaneously, e.g. vkMapMemory, vkBindBufferMemory. + Also protects m_MapCount, m_pMappedData. + Allocations, deallocations, any change in m_pMetadata is protected by parent's VmaBlockVector::m_Mutex. + */ + VMA_MUTEX m_MapAndBindMutex; + VmaMappingHysteresis m_MappingHysteresis; + uint32_t m_MapCount; + void* m_pMappedData; + + VmaWin32Handle m_Handle; +}; +#endif // _VMA_DEVICE_MEMORY_BLOCK + +#ifndef _VMA_ALLOCATION_T +struct VmaAllocationExtraData +{ + void* m_pMappedData = VMA_NULL; // Not null means memory is mapped. + VmaWin32Handle m_Handle; +}; + +struct VmaAllocation_T +{ + friend struct VmaDedicatedAllocationListItemTraits; + + enum FLAGS + { + FLAG_PERSISTENT_MAP = 0x01, + FLAG_MAPPING_ALLOWED = 0x02, + }; + +public: + enum ALLOCATION_TYPE + { + ALLOCATION_TYPE_NONE, + ALLOCATION_TYPE_BLOCK, + ALLOCATION_TYPE_DEDICATED, + }; + + // This struct is allocated using VmaPoolAllocator. + VmaAllocation_T(bool mappingAllowed); + ~VmaAllocation_T(); + + void InitBlockAllocation( + VmaDeviceMemoryBlock* block, + VmaAllocHandle allocHandle, + VkDeviceSize alignment, + VkDeviceSize size, + uint32_t memoryTypeIndex, + VmaSuballocationType suballocationType, + bool mapped); + // pMappedData not null means allocation is created with MAPPED flag. + void InitDedicatedAllocation( + VmaAllocator allocator, + VmaPool hParentPool, + uint32_t memoryTypeIndex, + VkDeviceMemory hMemory, + VmaSuballocationType suballocationType, + void* pMappedData, + VkDeviceSize size); + void Destroy(VmaAllocator allocator); + + ALLOCATION_TYPE GetType() const { return (ALLOCATION_TYPE)m_Type; } + VkDeviceSize GetAlignment() const { return m_Alignment; } + VkDeviceSize GetSize() const { return m_Size; } + void* GetUserData() const { return m_pUserData; } + const char* GetName() const { return m_pName; } + VmaSuballocationType GetSuballocationType() const { return (VmaSuballocationType)m_SuballocationType; } + + VmaDeviceMemoryBlock* GetBlock() const { VMA_ASSERT(m_Type == ALLOCATION_TYPE_BLOCK); return m_BlockAllocation.m_Block; } + uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; } + bool IsPersistentMap() const { return (m_Flags & FLAG_PERSISTENT_MAP) != 0; } + bool IsMappingAllowed() const { return (m_Flags & FLAG_MAPPING_ALLOWED) != 0; } + + void SetUserData(VmaAllocator hAllocator, void* pUserData) { m_pUserData = pUserData; } + void SetName(VmaAllocator hAllocator, const char* pName); + void FreeName(VmaAllocator hAllocator); + uint8_t SwapBlockAllocation(VmaAllocator hAllocator, VmaAllocation allocation); + VmaAllocHandle GetAllocHandle() const; + VkDeviceSize GetOffset() const; + VmaPool GetParentPool() const; + VkDeviceMemory GetMemory() const; + void* GetMappedData() const; + + void BlockAllocMap(); + void BlockAllocUnmap(); + VkResult DedicatedAllocMap(VmaAllocator hAllocator, void** ppData); + void DedicatedAllocUnmap(VmaAllocator hAllocator); + +#if VMA_STATS_STRING_ENABLED + VmaBufferImageUsage GetBufferImageUsage() const { return m_BufferImageUsage; } + void InitBufferUsage(const VkBufferCreateInfo &createInfo, bool useKhrMaintenance5) + { + VMA_ASSERT(m_BufferImageUsage == VmaBufferImageUsage::UNKNOWN); + m_BufferImageUsage = VmaBufferImageUsage(createInfo, useKhrMaintenance5); + } + void InitImageUsage(const VkImageCreateInfo &createInfo) + { + VMA_ASSERT(m_BufferImageUsage == VmaBufferImageUsage::UNKNOWN); + m_BufferImageUsage = VmaBufferImageUsage(createInfo); + } + void PrintParameters(class VmaJsonWriter& json) const; +#endif + +#if VMA_EXTERNAL_MEMORY_WIN32 + VkResult GetWin32Handle(VmaAllocator hAllocator, HANDLE hTargetProcess, HANDLE* hHandle) noexcept; +#endif // VMA_EXTERNAL_MEMORY_WIN32 + +private: + // Allocation out of VmaDeviceMemoryBlock. + struct BlockAllocation + { + VmaDeviceMemoryBlock* m_Block; + VmaAllocHandle m_AllocHandle; + }; + // Allocation for an object that has its own private VkDeviceMemory. + struct DedicatedAllocation + { + VmaPool m_hParentPool; // VK_NULL_HANDLE if not belongs to custom pool. + VkDeviceMemory m_hMemory; + VmaAllocationExtraData* m_ExtraData; + VmaAllocation_T* m_Prev; + VmaAllocation_T* m_Next; + }; + union + { + // Allocation out of VmaDeviceMemoryBlock. + BlockAllocation m_BlockAllocation; + // Allocation for an object that has its own private VkDeviceMemory. + DedicatedAllocation m_DedicatedAllocation; + }; + + VkDeviceSize m_Alignment; + VkDeviceSize m_Size; + void* m_pUserData; + char* m_pName; + uint32_t m_MemoryTypeIndex; + uint8_t m_Type; // ALLOCATION_TYPE + uint8_t m_SuballocationType; // VmaSuballocationType + // Reference counter for vmaMapMemory()/vmaUnmapMemory(). + uint8_t m_MapCount; + uint8_t m_Flags; // enum FLAGS +#if VMA_STATS_STRING_ENABLED + VmaBufferImageUsage m_BufferImageUsage; // 0 if unknown. +#endif + + void EnsureExtraData(VmaAllocator hAllocator); +}; +#endif // _VMA_ALLOCATION_T + +#ifndef _VMA_DEDICATED_ALLOCATION_LIST_ITEM_TRAITS +struct VmaDedicatedAllocationListItemTraits +{ + typedef VmaAllocation_T ItemType; + + static ItemType* GetPrev(const ItemType* item) + { + VMA_HEAVY_ASSERT(item->GetType() == VmaAllocation_T::ALLOCATION_TYPE_DEDICATED); + return item->m_DedicatedAllocation.m_Prev; + } + static ItemType* GetNext(const ItemType* item) + { + VMA_HEAVY_ASSERT(item->GetType() == VmaAllocation_T::ALLOCATION_TYPE_DEDICATED); + return item->m_DedicatedAllocation.m_Next; + } + static ItemType*& AccessPrev(ItemType* item) + { + VMA_HEAVY_ASSERT(item->GetType() == VmaAllocation_T::ALLOCATION_TYPE_DEDICATED); + return item->m_DedicatedAllocation.m_Prev; + } + static ItemType*& AccessNext(ItemType* item) + { + VMA_HEAVY_ASSERT(item->GetType() == VmaAllocation_T::ALLOCATION_TYPE_DEDICATED); + return item->m_DedicatedAllocation.m_Next; + } +}; +#endif // _VMA_DEDICATED_ALLOCATION_LIST_ITEM_TRAITS + +#ifndef _VMA_DEDICATED_ALLOCATION_LIST +/* +Stores linked list of VmaAllocation_T objects. +Thread-safe, synchronized internally. +*/ +class VmaDedicatedAllocationList +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaDedicatedAllocationList) +public: + VmaDedicatedAllocationList() {} + ~VmaDedicatedAllocationList(); + + void Init(bool useMutex) { m_UseMutex = useMutex; } + bool Validate(); + + void AddDetailedStatistics(VmaDetailedStatistics& inoutStats); + void AddStatistics(VmaStatistics& inoutStats); +#if VMA_STATS_STRING_ENABLED + // Writes JSON array with the list of allocations. + void BuildStatsString(VmaJsonWriter& json); +#endif + + bool IsEmpty(); + void Register(VmaAllocation alloc); + void Unregister(VmaAllocation alloc); + +private: + typedef VmaIntrusiveLinkedList DedicatedAllocationLinkedList; + + bool m_UseMutex = true; + VMA_RW_MUTEX m_Mutex; + DedicatedAllocationLinkedList m_AllocationList; +}; + +#ifndef _VMA_DEDICATED_ALLOCATION_LIST_FUNCTIONS + +VmaDedicatedAllocationList::~VmaDedicatedAllocationList() +{ + VMA_HEAVY_ASSERT(Validate()); + + if (!m_AllocationList.IsEmpty()) + { + VMA_ASSERT_LEAK(false && "Unfreed dedicated allocations found!"); + } +} + +bool VmaDedicatedAllocationList::Validate() +{ + const size_t declaredCount = m_AllocationList.GetCount(); + size_t actualCount = 0; + VmaMutexLockRead lock(m_Mutex, m_UseMutex); + for (VmaAllocation alloc = m_AllocationList.Front(); + alloc != VMA_NULL; alloc = m_AllocationList.GetNext(alloc)) + { + ++actualCount; + } + VMA_VALIDATE(actualCount == declaredCount); + + return true; +} + +void VmaDedicatedAllocationList::AddDetailedStatistics(VmaDetailedStatistics& inoutStats) +{ + for(auto* item = m_AllocationList.Front(); item != VMA_NULL; item = DedicatedAllocationLinkedList::GetNext(item)) + { + const VkDeviceSize size = item->GetSize(); + inoutStats.statistics.blockCount++; + inoutStats.statistics.blockBytes += size; + VmaAddDetailedStatisticsAllocation(inoutStats, item->GetSize()); + } +} + +void VmaDedicatedAllocationList::AddStatistics(VmaStatistics& inoutStats) +{ + VmaMutexLockRead lock(m_Mutex, m_UseMutex); + + const uint32_t allocCount = (uint32_t)m_AllocationList.GetCount(); + inoutStats.blockCount += allocCount; + inoutStats.allocationCount += allocCount; + + for(auto* item = m_AllocationList.Front(); item != VMA_NULL; item = DedicatedAllocationLinkedList::GetNext(item)) + { + const VkDeviceSize size = item->GetSize(); + inoutStats.blockBytes += size; + inoutStats.allocationBytes += size; + } +} + +#if VMA_STATS_STRING_ENABLED +void VmaDedicatedAllocationList::BuildStatsString(VmaJsonWriter& json) +{ + VmaMutexLockRead lock(m_Mutex, m_UseMutex); + json.BeginArray(); + for (VmaAllocation alloc = m_AllocationList.Front(); + alloc != VMA_NULL; alloc = m_AllocationList.GetNext(alloc)) + { + json.BeginObject(true); + alloc->PrintParameters(json); + json.EndObject(); + } + json.EndArray(); +} +#endif // VMA_STATS_STRING_ENABLED + +bool VmaDedicatedAllocationList::IsEmpty() +{ + VmaMutexLockRead lock(m_Mutex, m_UseMutex); + return m_AllocationList.IsEmpty(); +} + +void VmaDedicatedAllocationList::Register(VmaAllocation alloc) +{ + VmaMutexLockWrite lock(m_Mutex, m_UseMutex); + m_AllocationList.PushBack(alloc); +} + +void VmaDedicatedAllocationList::Unregister(VmaAllocation alloc) +{ + VmaMutexLockWrite lock(m_Mutex, m_UseMutex); + m_AllocationList.Remove(alloc); +} +#endif // _VMA_DEDICATED_ALLOCATION_LIST_FUNCTIONS +#endif // _VMA_DEDICATED_ALLOCATION_LIST + +#ifndef _VMA_SUBALLOCATION +/* +Represents a region of VmaDeviceMemoryBlock that is either assigned and returned as +allocated memory block or free. +*/ +struct VmaSuballocation +{ + VkDeviceSize offset; + VkDeviceSize size; + void* userData; + VmaSuballocationType type; +}; + +// Comparator for offsets. +struct VmaSuballocationOffsetLess +{ + bool operator()(const VmaSuballocation& lhs, const VmaSuballocation& rhs) const + { + return lhs.offset < rhs.offset; + } +}; + +struct VmaSuballocationOffsetGreater +{ + bool operator()(const VmaSuballocation& lhs, const VmaSuballocation& rhs) const + { + return lhs.offset > rhs.offset; + } +}; + +struct VmaSuballocationItemSizeLess +{ + bool operator()(const VmaSuballocationList::iterator lhs, + const VmaSuballocationList::iterator rhs) const + { + return lhs->size < rhs->size; + } + + bool operator()(const VmaSuballocationList::iterator lhs, + VkDeviceSize rhsSize) const + { + return lhs->size < rhsSize; + } +}; +#endif // _VMA_SUBALLOCATION + +#ifndef _VMA_ALLOCATION_REQUEST +/* +Parameters of planned allocation inside a VmaDeviceMemoryBlock. +item points to a FREE suballocation. +*/ +struct VmaAllocationRequest +{ + VmaAllocHandle allocHandle; + VkDeviceSize size; + VmaSuballocationList::iterator item; + void* customData; + uint64_t algorithmData; + VmaAllocationRequestType type; +}; +#endif // _VMA_ALLOCATION_REQUEST + +#ifndef _VMA_BLOCK_METADATA +/* +Data structure used for bookkeeping of allocations and unused ranges of memory +in a single VkDeviceMemory block. +*/ +class VmaBlockMetadata +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaBlockMetadata) +public: + // pAllocationCallbacks, if not null, must be owned externally - alive and unchanged for the whole lifetime of this object. + VmaBlockMetadata(const VkAllocationCallbacks* pAllocationCallbacks, + VkDeviceSize bufferImageGranularity, bool isVirtual); + virtual ~VmaBlockMetadata() = default; + + virtual void Init(VkDeviceSize size) { m_Size = size; } + bool IsVirtual() const { return m_IsVirtual; } + VkDeviceSize GetSize() const { return m_Size; } + + // Validates all data structures inside this object. If not valid, returns false. + virtual bool Validate() const = 0; + virtual size_t GetAllocationCount() const = 0; + virtual size_t GetFreeRegionsCount() const = 0; + virtual VkDeviceSize GetSumFreeSize() const = 0; + // Returns true if this block is empty - contains only single free suballocation. + virtual bool IsEmpty() const = 0; + virtual void GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) = 0; + virtual VkDeviceSize GetAllocationOffset(VmaAllocHandle allocHandle) const = 0; + virtual void* GetAllocationUserData(VmaAllocHandle allocHandle) const = 0; + + virtual VmaAllocHandle GetAllocationListBegin() const = 0; + virtual VmaAllocHandle GetNextAllocation(VmaAllocHandle prevAlloc) const = 0; + virtual VkDeviceSize GetNextFreeRegionSize(VmaAllocHandle alloc) const = 0; + + // Shouldn't modify blockCount. + virtual void AddDetailedStatistics(VmaDetailedStatistics& inoutStats) const = 0; + virtual void AddStatistics(VmaStatistics& inoutStats) const = 0; + +#if VMA_STATS_STRING_ENABLED + virtual void PrintDetailedMap(class VmaJsonWriter& json) const = 0; +#endif + + // Tries to find a place for suballocation with given parameters inside this block. + // If succeeded, fills pAllocationRequest and returns true. + // If failed, returns false. + virtual bool CreateAllocationRequest( + VkDeviceSize allocSize, + VkDeviceSize allocAlignment, + bool upperAddress, + VmaSuballocationType allocType, + // Always one of VMA_ALLOCATION_CREATE_STRATEGY_* or VMA_ALLOCATION_INTERNAL_STRATEGY_* flags. + uint32_t strategy, + VmaAllocationRequest* pAllocationRequest) = 0; + + virtual VkResult CheckCorruption(const void* pBlockData) = 0; + + // Makes actual allocation based on request. Request must already be checked and valid. + virtual void Alloc( + const VmaAllocationRequest& request, + VmaSuballocationType type, + void* userData) = 0; + + // Frees suballocation assigned to given memory region. + virtual void Free(VmaAllocHandle allocHandle) = 0; + + // Frees all allocations. + // Careful! Don't call it if there are VmaAllocation objects owned by userData of cleared allocations! + virtual void Clear() = 0; + + virtual void SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) = 0; + virtual void DebugLogAllAllocations() const = 0; + +protected: + const VkAllocationCallbacks* GetAllocationCallbacks() const { return m_pAllocationCallbacks; } + VkDeviceSize GetBufferImageGranularity() const { return m_BufferImageGranularity; } + VkDeviceSize GetDebugMargin() const { return VkDeviceSize(IsVirtual() ? 0 : VMA_DEBUG_MARGIN); } + + void DebugLogAllocation(VkDeviceSize offset, VkDeviceSize size, void* userData) const; +#if VMA_STATS_STRING_ENABLED + // mapRefCount == UINT32_MAX means unspecified. + void PrintDetailedMap_Begin(class VmaJsonWriter& json, + VkDeviceSize unusedBytes, + size_t allocationCount, + size_t unusedRangeCount) const; + void PrintDetailedMap_Allocation(class VmaJsonWriter& json, + VkDeviceSize offset, VkDeviceSize size, void* userData) const; + void PrintDetailedMap_UnusedRange(class VmaJsonWriter& json, + VkDeviceSize offset, + VkDeviceSize size) const; + void PrintDetailedMap_End(class VmaJsonWriter& json) const; +#endif + +private: + VkDeviceSize m_Size; + const VkAllocationCallbacks* m_pAllocationCallbacks; + const VkDeviceSize m_BufferImageGranularity; + const bool m_IsVirtual; +}; + +#ifndef _VMA_BLOCK_METADATA_FUNCTIONS +VmaBlockMetadata::VmaBlockMetadata(const VkAllocationCallbacks* pAllocationCallbacks, + VkDeviceSize bufferImageGranularity, bool isVirtual) + : m_Size(0), + m_pAllocationCallbacks(pAllocationCallbacks), + m_BufferImageGranularity(bufferImageGranularity), + m_IsVirtual(isVirtual) {} + +void VmaBlockMetadata::DebugLogAllocation(VkDeviceSize offset, VkDeviceSize size, void* userData) const +{ + if (IsVirtual()) + { + VMA_LEAK_LOG_FORMAT("UNFREED VIRTUAL ALLOCATION; Offset: %" PRIu64 "; Size: %" PRIu64 "; UserData: %p", offset, size, userData); + } + else + { + VMA_ASSERT(userData != VMA_NULL); + VmaAllocation allocation = reinterpret_cast(userData); + + userData = allocation->GetUserData(); + const char* name = allocation->GetName(); + +#if VMA_STATS_STRING_ENABLED + VMA_LEAK_LOG_FORMAT("UNFREED ALLOCATION; Offset: %" PRIu64 "; Size: %" PRIu64 "; UserData: %p; Name: %s; Type: %s; Usage: %" PRIu64, + offset, size, userData, name ? name : "vma_empty", + VMA_SUBALLOCATION_TYPE_NAMES[allocation->GetSuballocationType()], + (uint64_t)allocation->GetBufferImageUsage().Value); +#else + VMA_LEAK_LOG_FORMAT("UNFREED ALLOCATION; Offset: %" PRIu64 "; Size: %" PRIu64 "; UserData: %p; Name: %s; Type: %u", + offset, size, userData, name ? name : "vma_empty", + (unsigned)allocation->GetSuballocationType()); +#endif // VMA_STATS_STRING_ENABLED + } + +} + +#if VMA_STATS_STRING_ENABLED +void VmaBlockMetadata::PrintDetailedMap_Begin(class VmaJsonWriter& json, + VkDeviceSize unusedBytes, size_t allocationCount, size_t unusedRangeCount) const +{ + json.WriteString("TotalBytes"); + json.WriteNumber(GetSize()); + + json.WriteString("UnusedBytes"); + json.WriteNumber(unusedBytes); + + json.WriteString("Allocations"); + json.WriteNumber((uint64_t)allocationCount); + + json.WriteString("UnusedRanges"); + json.WriteNumber((uint64_t)unusedRangeCount); + + json.WriteString("Suballocations"); + json.BeginArray(); +} + +void VmaBlockMetadata::PrintDetailedMap_Allocation(class VmaJsonWriter& json, + VkDeviceSize offset, VkDeviceSize size, void* userData) const +{ + json.BeginObject(true); + + json.WriteString("Offset"); + json.WriteNumber(offset); + + if (IsVirtual()) + { + json.WriteString("Size"); + json.WriteNumber(size); + if (userData) + { + json.WriteString("CustomData"); + json.BeginString(); + json.ContinueString_Pointer(userData); + json.EndString(); + } + } + else + { + ((VmaAllocation)userData)->PrintParameters(json); + } + + json.EndObject(); +} + +void VmaBlockMetadata::PrintDetailedMap_UnusedRange(class VmaJsonWriter& json, + VkDeviceSize offset, VkDeviceSize size) const +{ + json.BeginObject(true); + + json.WriteString("Offset"); + json.WriteNumber(offset); + + json.WriteString("Type"); + json.WriteString(VMA_SUBALLOCATION_TYPE_NAMES[VMA_SUBALLOCATION_TYPE_FREE]); + + json.WriteString("Size"); + json.WriteNumber(size); + + json.EndObject(); +} + +void VmaBlockMetadata::PrintDetailedMap_End(class VmaJsonWriter& json) const +{ + json.EndArray(); +} +#endif // VMA_STATS_STRING_ENABLED +#endif // _VMA_BLOCK_METADATA_FUNCTIONS +#endif // _VMA_BLOCK_METADATA + +#ifndef _VMA_BLOCK_BUFFER_IMAGE_GRANULARITY +// Before deleting object of this class remember to call 'Destroy()' +class VmaBlockBufferImageGranularity final +{ +public: + struct ValidationContext + { + const VkAllocationCallbacks* allocCallbacks; + uint16_t* pageAllocs; + }; + + VmaBlockBufferImageGranularity(VkDeviceSize bufferImageGranularity); + ~VmaBlockBufferImageGranularity(); + + bool IsEnabled() const { return m_BufferImageGranularity > MAX_LOW_BUFFER_IMAGE_GRANULARITY; } + + void Init(const VkAllocationCallbacks* pAllocationCallbacks, VkDeviceSize size); + // Before destroying object you must call free it's memory + void Destroy(const VkAllocationCallbacks* pAllocationCallbacks); + + void RoundupAllocRequest(VmaSuballocationType allocType, + VkDeviceSize& inOutAllocSize, + VkDeviceSize& inOutAllocAlignment) const; + + bool CheckConflictAndAlignUp(VkDeviceSize& inOutAllocOffset, + VkDeviceSize allocSize, + VkDeviceSize blockOffset, + VkDeviceSize blockSize, + VmaSuballocationType allocType) const; + + void AllocPages(uint8_t allocType, VkDeviceSize offset, VkDeviceSize size); + void FreePages(VkDeviceSize offset, VkDeviceSize size); + void Clear(); + + ValidationContext StartValidation(const VkAllocationCallbacks* pAllocationCallbacks, + bool isVirutal) const; + bool Validate(ValidationContext& ctx, VkDeviceSize offset, VkDeviceSize size) const; + bool FinishValidation(ValidationContext& ctx) const; + +private: + static const uint16_t MAX_LOW_BUFFER_IMAGE_GRANULARITY = 256; + + struct RegionInfo + { + uint8_t allocType; + uint16_t allocCount; + }; + + VkDeviceSize m_BufferImageGranularity; + uint32_t m_RegionCount; + RegionInfo* m_RegionInfo; + + uint32_t GetStartPage(VkDeviceSize offset) const { return OffsetToPageIndex(offset & ~(m_BufferImageGranularity - 1)); } + uint32_t GetEndPage(VkDeviceSize offset, VkDeviceSize size) const { return OffsetToPageIndex((offset + size - 1) & ~(m_BufferImageGranularity - 1)); } + + uint32_t OffsetToPageIndex(VkDeviceSize offset) const; + void AllocPage(RegionInfo& page, uint8_t allocType); +}; + +#ifndef _VMA_BLOCK_BUFFER_IMAGE_GRANULARITY_FUNCTIONS +VmaBlockBufferImageGranularity::VmaBlockBufferImageGranularity(VkDeviceSize bufferImageGranularity) + : m_BufferImageGranularity(bufferImageGranularity), + m_RegionCount(0), + m_RegionInfo(VMA_NULL) {} + +VmaBlockBufferImageGranularity::~VmaBlockBufferImageGranularity() +{ + VMA_ASSERT(m_RegionInfo == VMA_NULL && "Free not called before destroying object!"); +} + +void VmaBlockBufferImageGranularity::Init(const VkAllocationCallbacks* pAllocationCallbacks, VkDeviceSize size) +{ + if (IsEnabled()) + { + m_RegionCount = static_cast(VmaDivideRoundingUp(size, m_BufferImageGranularity)); + m_RegionInfo = vma_new_array(pAllocationCallbacks, RegionInfo, m_RegionCount); + memset(m_RegionInfo, 0, m_RegionCount * sizeof(RegionInfo)); + } +} + +void VmaBlockBufferImageGranularity::Destroy(const VkAllocationCallbacks* pAllocationCallbacks) +{ + if (m_RegionInfo) + { + vma_delete_array(pAllocationCallbacks, m_RegionInfo, m_RegionCount); + m_RegionInfo = VMA_NULL; + } +} + +void VmaBlockBufferImageGranularity::RoundupAllocRequest(VmaSuballocationType allocType, + VkDeviceSize& inOutAllocSize, + VkDeviceSize& inOutAllocAlignment) const +{ + if (m_BufferImageGranularity > 1 && + m_BufferImageGranularity <= MAX_LOW_BUFFER_IMAGE_GRANULARITY) + { + if (allocType == VMA_SUBALLOCATION_TYPE_UNKNOWN || + allocType == VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN || + allocType == VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL) + { + inOutAllocAlignment = VMA_MAX(inOutAllocAlignment, m_BufferImageGranularity); + inOutAllocSize = VmaAlignUp(inOutAllocSize, m_BufferImageGranularity); + } + } +} + +bool VmaBlockBufferImageGranularity::CheckConflictAndAlignUp(VkDeviceSize& inOutAllocOffset, + VkDeviceSize allocSize, + VkDeviceSize blockOffset, + VkDeviceSize blockSize, + VmaSuballocationType allocType) const +{ + if (IsEnabled()) + { + uint32_t startPage = GetStartPage(inOutAllocOffset); + if (m_RegionInfo[startPage].allocCount > 0 && + VmaIsBufferImageGranularityConflict(static_cast(m_RegionInfo[startPage].allocType), allocType)) + { + inOutAllocOffset = VmaAlignUp(inOutAllocOffset, m_BufferImageGranularity); + if (blockSize < allocSize + inOutAllocOffset - blockOffset) + return true; + ++startPage; + } + uint32_t endPage = GetEndPage(inOutAllocOffset, allocSize); + if (endPage != startPage && + m_RegionInfo[endPage].allocCount > 0 && + VmaIsBufferImageGranularityConflict(static_cast(m_RegionInfo[endPage].allocType), allocType)) + { + return true; + } + } + return false; +} + +void VmaBlockBufferImageGranularity::AllocPages(uint8_t allocType, VkDeviceSize offset, VkDeviceSize size) +{ + if (IsEnabled()) + { + uint32_t startPage = GetStartPage(offset); + AllocPage(m_RegionInfo[startPage], allocType); + + uint32_t endPage = GetEndPage(offset, size); + if (startPage != endPage) + AllocPage(m_RegionInfo[endPage], allocType); + } +} + +void VmaBlockBufferImageGranularity::FreePages(VkDeviceSize offset, VkDeviceSize size) +{ + if (IsEnabled()) + { + uint32_t startPage = GetStartPage(offset); + --m_RegionInfo[startPage].allocCount; + if (m_RegionInfo[startPage].allocCount == 0) + m_RegionInfo[startPage].allocType = VMA_SUBALLOCATION_TYPE_FREE; + uint32_t endPage = GetEndPage(offset, size); + if (startPage != endPage) + { + --m_RegionInfo[endPage].allocCount; + if (m_RegionInfo[endPage].allocCount == 0) + m_RegionInfo[endPage].allocType = VMA_SUBALLOCATION_TYPE_FREE; + } + } +} + +void VmaBlockBufferImageGranularity::Clear() +{ + if (m_RegionInfo) + memset(m_RegionInfo, 0, m_RegionCount * sizeof(RegionInfo)); +} + +VmaBlockBufferImageGranularity::ValidationContext VmaBlockBufferImageGranularity::StartValidation( + const VkAllocationCallbacks* pAllocationCallbacks, bool isVirutal) const +{ + ValidationContext ctx{ pAllocationCallbacks, VMA_NULL }; + if (!isVirutal && IsEnabled()) + { + ctx.pageAllocs = vma_new_array(pAllocationCallbacks, uint16_t, m_RegionCount); + memset(ctx.pageAllocs, 0, m_RegionCount * sizeof(uint16_t)); + } + return ctx; +} + +bool VmaBlockBufferImageGranularity::Validate(ValidationContext& ctx, + VkDeviceSize offset, VkDeviceSize size) const +{ + if (IsEnabled()) + { + uint32_t start = GetStartPage(offset); + ++ctx.pageAllocs[start]; + VMA_VALIDATE(m_RegionInfo[start].allocCount > 0); + + uint32_t end = GetEndPage(offset, size); + if (start != end) + { + ++ctx.pageAllocs[end]; + VMA_VALIDATE(m_RegionInfo[end].allocCount > 0); + } + } + return true; +} + +bool VmaBlockBufferImageGranularity::FinishValidation(ValidationContext& ctx) const +{ + // Check proper page structure + if (IsEnabled()) + { + VMA_ASSERT(ctx.pageAllocs != VMA_NULL && "Validation context not initialized!"); + + for (uint32_t page = 0; page < m_RegionCount; ++page) + { + VMA_VALIDATE(ctx.pageAllocs[page] == m_RegionInfo[page].allocCount); + } + vma_delete_array(ctx.allocCallbacks, ctx.pageAllocs, m_RegionCount); + ctx.pageAllocs = VMA_NULL; + } + return true; +} + +uint32_t VmaBlockBufferImageGranularity::OffsetToPageIndex(VkDeviceSize offset) const +{ + return static_cast(offset >> VMA_BITSCAN_MSB(m_BufferImageGranularity)); +} + +void VmaBlockBufferImageGranularity::AllocPage(RegionInfo& page, uint8_t allocType) +{ + // When current alloc type is free then it can be overridden by new type + if (page.allocCount == 0 || (page.allocCount > 0 && page.allocType == VMA_SUBALLOCATION_TYPE_FREE)) + page.allocType = allocType; + + ++page.allocCount; +} +#endif // _VMA_BLOCK_BUFFER_IMAGE_GRANULARITY_FUNCTIONS +#endif // _VMA_BLOCK_BUFFER_IMAGE_GRANULARITY + +#ifndef _VMA_BLOCK_METADATA_LINEAR +/* +Allocations and their references in internal data structure look like this: + +if(m_2ndVectorMode == SECOND_VECTOR_EMPTY): + + 0 +-------+ + | | + | | + | | + +-------+ + | Alloc | 1st[m_1stNullItemsBeginCount] + +-------+ + | Alloc | 1st[m_1stNullItemsBeginCount + 1] + +-------+ + | ... | + +-------+ + | Alloc | 1st[1st.size() - 1] + +-------+ + | | + | | + | | +GetSize() +-------+ + +if(m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER): + + 0 +-------+ + | Alloc | 2nd[0] + +-------+ + | Alloc | 2nd[1] + +-------+ + | ... | + +-------+ + | Alloc | 2nd[2nd.size() - 1] + +-------+ + | | + | | + | | + +-------+ + | Alloc | 1st[m_1stNullItemsBeginCount] + +-------+ + | Alloc | 1st[m_1stNullItemsBeginCount + 1] + +-------+ + | ... | + +-------+ + | Alloc | 1st[1st.size() - 1] + +-------+ + | | +GetSize() +-------+ + +if(m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK): + + 0 +-------+ + | | + | | + | | + +-------+ + | Alloc | 1st[m_1stNullItemsBeginCount] + +-------+ + | Alloc | 1st[m_1stNullItemsBeginCount + 1] + +-------+ + | ... | + +-------+ + | Alloc | 1st[1st.size() - 1] + +-------+ + | | + | | + | | + +-------+ + | Alloc | 2nd[2nd.size() - 1] + +-------+ + | ... | + +-------+ + | Alloc | 2nd[1] + +-------+ + | Alloc | 2nd[0] +GetSize() +-------+ + +*/ +class VmaBlockMetadata_Linear : public VmaBlockMetadata +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaBlockMetadata_Linear) +public: + VmaBlockMetadata_Linear(const VkAllocationCallbacks* pAllocationCallbacks, + VkDeviceSize bufferImageGranularity, bool isVirtual); + virtual ~VmaBlockMetadata_Linear() = default; + + VkDeviceSize GetSumFreeSize() const override { return m_SumFreeSize; } + bool IsEmpty() const override { return GetAllocationCount() == 0; } + VkDeviceSize GetAllocationOffset(VmaAllocHandle allocHandle) const override { return (VkDeviceSize)allocHandle - 1; } + + void Init(VkDeviceSize size) override; + bool Validate() const override; + size_t GetAllocationCount() const override; + size_t GetFreeRegionsCount() const override; + + void AddDetailedStatistics(VmaDetailedStatistics& inoutStats) const override; + void AddStatistics(VmaStatistics& inoutStats) const override; + +#if VMA_STATS_STRING_ENABLED + void PrintDetailedMap(class VmaJsonWriter& json) const override; +#endif + + bool CreateAllocationRequest( + VkDeviceSize allocSize, + VkDeviceSize allocAlignment, + bool upperAddress, + VmaSuballocationType allocType, + uint32_t strategy, + VmaAllocationRequest* pAllocationRequest) override; + + VkResult CheckCorruption(const void* pBlockData) override; + + void Alloc( + const VmaAllocationRequest& request, + VmaSuballocationType type, + void* userData) override; + + void Free(VmaAllocHandle allocHandle) override; + void GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) override; + void* GetAllocationUserData(VmaAllocHandle allocHandle) const override; + VmaAllocHandle GetAllocationListBegin() const override; + VmaAllocHandle GetNextAllocation(VmaAllocHandle prevAlloc) const override; + VkDeviceSize GetNextFreeRegionSize(VmaAllocHandle alloc) const override; + void Clear() override; + void SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) override; + void DebugLogAllAllocations() const override; + +private: + /* + There are two suballocation vectors, used in ping-pong way. + The one with index m_1stVectorIndex is called 1st. + The one with index (m_1stVectorIndex ^ 1) is called 2nd. + 2nd can be non-empty only when 1st is not empty. + When 2nd is not empty, m_2ndVectorMode indicates its mode of operation. + */ + typedef VmaVector> SuballocationVectorType; + + enum SECOND_VECTOR_MODE + { + SECOND_VECTOR_EMPTY, + /* + Suballocations in 2nd vector are created later than the ones in 1st, but they + all have smaller offset. + */ + SECOND_VECTOR_RING_BUFFER, + /* + Suballocations in 2nd vector are upper side of double stack. + They all have offsets higher than those in 1st vector. + Top of this stack means smaller offsets, but higher indices in this vector. + */ + SECOND_VECTOR_DOUBLE_STACK, + }; + + VkDeviceSize m_SumFreeSize; + SuballocationVectorType m_Suballocations0, m_Suballocations1; + uint32_t m_1stVectorIndex; + SECOND_VECTOR_MODE m_2ndVectorMode; + // Number of items in 1st vector with hAllocation = null at the beginning. + size_t m_1stNullItemsBeginCount; + // Number of other items in 1st vector with hAllocation = null somewhere in the middle. + size_t m_1stNullItemsMiddleCount; + // Number of items in 2nd vector with hAllocation = null. + size_t m_2ndNullItemsCount; + + SuballocationVectorType& AccessSuballocations1st() { return m_1stVectorIndex ? m_Suballocations1 : m_Suballocations0; } + SuballocationVectorType& AccessSuballocations2nd() { return m_1stVectorIndex ? m_Suballocations0 : m_Suballocations1; } + const SuballocationVectorType& AccessSuballocations1st() const { return m_1stVectorIndex ? m_Suballocations1 : m_Suballocations0; } + const SuballocationVectorType& AccessSuballocations2nd() const { return m_1stVectorIndex ? m_Suballocations0 : m_Suballocations1; } + + VmaSuballocation& FindSuballocation(VkDeviceSize offset) const; + bool ShouldCompact1st() const; + void CleanupAfterFree(); + + bool CreateAllocationRequest_LowerAddress( + VkDeviceSize allocSize, + VkDeviceSize allocAlignment, + VmaSuballocationType allocType, + uint32_t strategy, + VmaAllocationRequest* pAllocationRequest); + bool CreateAllocationRequest_UpperAddress( + VkDeviceSize allocSize, + VkDeviceSize allocAlignment, + VmaSuballocationType allocType, + uint32_t strategy, + VmaAllocationRequest* pAllocationRequest); +}; + +#ifndef _VMA_BLOCK_METADATA_LINEAR_FUNCTIONS +VmaBlockMetadata_Linear::VmaBlockMetadata_Linear(const VkAllocationCallbacks* pAllocationCallbacks, + VkDeviceSize bufferImageGranularity, bool isVirtual) + : VmaBlockMetadata(pAllocationCallbacks, bufferImageGranularity, isVirtual), + m_SumFreeSize(0), + m_Suballocations0(VmaStlAllocator(pAllocationCallbacks)), + m_Suballocations1(VmaStlAllocator(pAllocationCallbacks)), + m_1stVectorIndex(0), + m_2ndVectorMode(SECOND_VECTOR_EMPTY), + m_1stNullItemsBeginCount(0), + m_1stNullItemsMiddleCount(0), + m_2ndNullItemsCount(0) {} + +void VmaBlockMetadata_Linear::Init(VkDeviceSize size) +{ + VmaBlockMetadata::Init(size); + m_SumFreeSize = size; +} + +bool VmaBlockMetadata_Linear::Validate() const +{ + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + VMA_VALIDATE(suballocations2nd.empty() == (m_2ndVectorMode == SECOND_VECTOR_EMPTY)); + VMA_VALIDATE(!suballocations1st.empty() || + suballocations2nd.empty() || + m_2ndVectorMode != SECOND_VECTOR_RING_BUFFER); + + if (!suballocations1st.empty()) + { + // Null item at the beginning should be accounted into m_1stNullItemsBeginCount. + VMA_VALIDATE(suballocations1st[m_1stNullItemsBeginCount].type != VMA_SUBALLOCATION_TYPE_FREE); + // Null item at the end should be just pop_back(). + VMA_VALIDATE(suballocations1st.back().type != VMA_SUBALLOCATION_TYPE_FREE); + } + if (!suballocations2nd.empty()) + { + // Null item at the end should be just pop_back(). + VMA_VALIDATE(suballocations2nd.back().type != VMA_SUBALLOCATION_TYPE_FREE); + } + + VMA_VALIDATE(m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount <= suballocations1st.size()); + VMA_VALIDATE(m_2ndNullItemsCount <= suballocations2nd.size()); + + VkDeviceSize sumUsedSize = 0; + const size_t suballoc1stCount = suballocations1st.size(); + const VkDeviceSize debugMargin = GetDebugMargin(); + VkDeviceSize offset = 0; + + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + const size_t suballoc2ndCount = suballocations2nd.size(); + size_t nullItem2ndCount = 0; + for (size_t i = 0; i < suballoc2ndCount; ++i) + { + const VmaSuballocation& suballoc = suballocations2nd[i]; + const bool currFree = (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE); + + VmaAllocation const alloc = (VmaAllocation)suballoc.userData; + if (!IsVirtual()) + { + VMA_VALIDATE(currFree == (alloc == VK_NULL_HANDLE)); + } + VMA_VALIDATE(suballoc.offset >= offset); + + if (!currFree) + { + if (!IsVirtual()) + { + VMA_VALIDATE((VkDeviceSize)alloc->GetAllocHandle() == suballoc.offset + 1); + VMA_VALIDATE(alloc->GetSize() == suballoc.size); + } + sumUsedSize += suballoc.size; + } + else + { + ++nullItem2ndCount; + } + + offset = suballoc.offset + suballoc.size + debugMargin; + } + + VMA_VALIDATE(nullItem2ndCount == m_2ndNullItemsCount); + } + + for (size_t i = 0; i < m_1stNullItemsBeginCount; ++i) + { + const VmaSuballocation& suballoc = suballocations1st[i]; + VMA_VALIDATE(suballoc.type == VMA_SUBALLOCATION_TYPE_FREE && + suballoc.userData == VMA_NULL); + } + + size_t nullItem1stCount = m_1stNullItemsBeginCount; + + for (size_t i = m_1stNullItemsBeginCount; i < suballoc1stCount; ++i) + { + const VmaSuballocation& suballoc = suballocations1st[i]; + const bool currFree = (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE); + + VmaAllocation const alloc = (VmaAllocation)suballoc.userData; + if (!IsVirtual()) + { + VMA_VALIDATE(currFree == (alloc == VK_NULL_HANDLE)); + } + VMA_VALIDATE(suballoc.offset >= offset); + VMA_VALIDATE(i >= m_1stNullItemsBeginCount || currFree); + + if (!currFree) + { + if (!IsVirtual()) + { + VMA_VALIDATE((VkDeviceSize)alloc->GetAllocHandle() == suballoc.offset + 1); + VMA_VALIDATE(alloc->GetSize() == suballoc.size); + } + sumUsedSize += suballoc.size; + } + else + { + ++nullItem1stCount; + } + + offset = suballoc.offset + suballoc.size + debugMargin; + } + VMA_VALIDATE(nullItem1stCount == m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount); + + if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + const size_t suballoc2ndCount = suballocations2nd.size(); + size_t nullItem2ndCount = 0; + for (size_t i = suballoc2ndCount; i--; ) + { + const VmaSuballocation& suballoc = suballocations2nd[i]; + const bool currFree = (suballoc.type == VMA_SUBALLOCATION_TYPE_FREE); + + VmaAllocation const alloc = (VmaAllocation)suballoc.userData; + if (!IsVirtual()) + { + VMA_VALIDATE(currFree == (alloc == VK_NULL_HANDLE)); + } + VMA_VALIDATE(suballoc.offset >= offset); + + if (!currFree) + { + if (!IsVirtual()) + { + VMA_VALIDATE((VkDeviceSize)alloc->GetAllocHandle() == suballoc.offset + 1); + VMA_VALIDATE(alloc->GetSize() == suballoc.size); + } + sumUsedSize += suballoc.size; + } + else + { + ++nullItem2ndCount; + } + + offset = suballoc.offset + suballoc.size + debugMargin; + } + + VMA_VALIDATE(nullItem2ndCount == m_2ndNullItemsCount); + } + + VMA_VALIDATE(offset <= GetSize()); + VMA_VALIDATE(m_SumFreeSize == GetSize() - sumUsedSize); + + return true; +} + +size_t VmaBlockMetadata_Linear::GetAllocationCount() const +{ + return AccessSuballocations1st().size() - m_1stNullItemsBeginCount - m_1stNullItemsMiddleCount + + AccessSuballocations2nd().size() - m_2ndNullItemsCount; +} + +size_t VmaBlockMetadata_Linear::GetFreeRegionsCount() const +{ + // Function only used for defragmentation, which is disabled for this algorithm + VMA_ASSERT(0); + return SIZE_MAX; +} + +void VmaBlockMetadata_Linear::AddDetailedStatistics(VmaDetailedStatistics& inoutStats) const +{ + const VkDeviceSize size = GetSize(); + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + const size_t suballoc1stCount = suballocations1st.size(); + const size_t suballoc2ndCount = suballocations2nd.size(); + + inoutStats.statistics.blockCount++; + inoutStats.statistics.blockBytes += size; + + VkDeviceSize lastOffset = 0; + + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset; + size_t nextAlloc2ndIndex = 0; + while (lastOffset < freeSpace2ndTo1stEnd) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc2ndIndex < suballoc2ndCount && + suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL) + { + ++nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex < suballoc2ndCount) + { + const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; + VmaAddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + VmaAddDetailedStatisticsAllocation(inoutStats, suballoc.size); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc2ndIndex; + } + // We are at the end. + else + { + // There is free space from lastOffset to freeSpace2ndTo1stEnd. + if (lastOffset < freeSpace2ndTo1stEnd) + { + const VkDeviceSize unusedRangeSize = freeSpace2ndTo1stEnd - lastOffset; + VmaAddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // End of loop. + lastOffset = freeSpace2ndTo1stEnd; + } + } + } + + size_t nextAlloc1stIndex = m_1stNullItemsBeginCount; + const VkDeviceSize freeSpace1stTo2ndEnd = + m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? suballocations2nd.back().offset : size; + while (lastOffset < freeSpace1stTo2ndEnd) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc1stIndex < suballoc1stCount && + suballocations1st[nextAlloc1stIndex].userData == VMA_NULL) + { + ++nextAlloc1stIndex; + } + + // Found non-null allocation. + if (nextAlloc1stIndex < suballoc1stCount) + { + const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; + VmaAddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + VmaAddDetailedStatisticsAllocation(inoutStats, suballoc.size); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc1stIndex; + } + // We are at the end. + else + { + // There is free space from lastOffset to freeSpace1stTo2ndEnd. + if (lastOffset < freeSpace1stTo2ndEnd) + { + const VkDeviceSize unusedRangeSize = freeSpace1stTo2ndEnd - lastOffset; + VmaAddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // End of loop. + lastOffset = freeSpace1stTo2ndEnd; + } + } + + if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + size_t nextAlloc2ndIndex = suballocations2nd.size() - 1; + while (lastOffset < size) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc2ndIndex != SIZE_MAX && + suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL) + { + --nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex != SIZE_MAX) + { + const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; + VmaAddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + VmaAddDetailedStatisticsAllocation(inoutStats, suballoc.size); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + --nextAlloc2ndIndex; + } + // We are at the end. + else + { + // There is free space from lastOffset to size. + if (lastOffset < size) + { + const VkDeviceSize unusedRangeSize = size - lastOffset; + VmaAddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // End of loop. + lastOffset = size; + } + } + } +} + +void VmaBlockMetadata_Linear::AddStatistics(VmaStatistics& inoutStats) const +{ + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + const VkDeviceSize size = GetSize(); + const size_t suballoc1stCount = suballocations1st.size(); + const size_t suballoc2ndCount = suballocations2nd.size(); + + inoutStats.blockCount++; + inoutStats.blockBytes += size; + inoutStats.allocationBytes += size - m_SumFreeSize; + + VkDeviceSize lastOffset = 0; + + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset; + size_t nextAlloc2ndIndex = m_1stNullItemsBeginCount; + while (lastOffset < freeSpace2ndTo1stEnd) + { + // Find next non-null allocation or move nextAlloc2ndIndex to the end. + while (nextAlloc2ndIndex < suballoc2ndCount && + suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL) + { + ++nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex < suballoc2ndCount) + { + const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + ++inoutStats.allocationCount; + + // Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc2ndIndex; + } + // We are at the end. + else + { + // End of loop. + lastOffset = freeSpace2ndTo1stEnd; + } + } + } + + size_t nextAlloc1stIndex = m_1stNullItemsBeginCount; + const VkDeviceSize freeSpace1stTo2ndEnd = + m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? suballocations2nd.back().offset : size; + while (lastOffset < freeSpace1stTo2ndEnd) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc1stIndex < suballoc1stCount && + suballocations1st[nextAlloc1stIndex].userData == VMA_NULL) + { + ++nextAlloc1stIndex; + } + + // Found non-null allocation. + if (nextAlloc1stIndex < suballoc1stCount) + { + const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; + + // Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + ++inoutStats.allocationCount; + + // Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc1stIndex; + } + // We are at the end. + else + { + // End of loop. + lastOffset = freeSpace1stTo2ndEnd; + } + } + + if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + size_t nextAlloc2ndIndex = suballocations2nd.size() - 1; + while (lastOffset < size) + { + // Find next non-null allocation or move nextAlloc2ndIndex to the end. + while (nextAlloc2ndIndex != SIZE_MAX && + suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL) + { + --nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex != SIZE_MAX) + { + const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + ++inoutStats.allocationCount; + + // Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + --nextAlloc2ndIndex; + } + // We are at the end. + else + { + // End of loop. + lastOffset = size; + } + } + } +} + +#if VMA_STATS_STRING_ENABLED +void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const +{ + const VkDeviceSize size = GetSize(); + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + const size_t suballoc1stCount = suballocations1st.size(); + const size_t suballoc2ndCount = suballocations2nd.size(); + + // FIRST PASS + + size_t unusedRangeCount = 0; + VkDeviceSize usedBytes = 0; + + VkDeviceSize lastOffset = 0; + + size_t alloc2ndCount = 0; + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset; + size_t nextAlloc2ndIndex = 0; + while (lastOffset < freeSpace2ndTo1stEnd) + { + // Find next non-null allocation or move nextAlloc2ndIndex to the end. + while (nextAlloc2ndIndex < suballoc2ndCount && + suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL) + { + ++nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex < suballoc2ndCount) + { + const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + ++unusedRangeCount; + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + ++alloc2ndCount; + usedBytes += suballoc.size; + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc2ndIndex; + } + // We are at the end. + else + { + if (lastOffset < freeSpace2ndTo1stEnd) + { + // There is free space from lastOffset to freeSpace2ndTo1stEnd. + ++unusedRangeCount; + } + + // End of loop. + lastOffset = freeSpace2ndTo1stEnd; + } + } + } + + size_t nextAlloc1stIndex = m_1stNullItemsBeginCount; + size_t alloc1stCount = 0; + const VkDeviceSize freeSpace1stTo2ndEnd = + m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? suballocations2nd.back().offset : size; + while (lastOffset < freeSpace1stTo2ndEnd) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc1stIndex < suballoc1stCount && + suballocations1st[nextAlloc1stIndex].userData == VMA_NULL) + { + ++nextAlloc1stIndex; + } + + // Found non-null allocation. + if (nextAlloc1stIndex < suballoc1stCount) + { + const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + ++unusedRangeCount; + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + ++alloc1stCount; + usedBytes += suballoc.size; + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc1stIndex; + } + // We are at the end. + else + { + if (lastOffset < freeSpace1stTo2ndEnd) + { + // There is free space from lastOffset to freeSpace1stTo2ndEnd. + ++unusedRangeCount; + } + + // End of loop. + lastOffset = freeSpace1stTo2ndEnd; + } + } + + if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + size_t nextAlloc2ndIndex = suballocations2nd.size() - 1; + while (lastOffset < size) + { + // Find next non-null allocation or move nextAlloc2ndIndex to the end. + while (nextAlloc2ndIndex != SIZE_MAX && + suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL) + { + --nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex != SIZE_MAX) + { + const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + ++unusedRangeCount; + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + ++alloc2ndCount; + usedBytes += suballoc.size; + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + --nextAlloc2ndIndex; + } + // We are at the end. + else + { + if (lastOffset < size) + { + // There is free space from lastOffset to size. + ++unusedRangeCount; + } + + // End of loop. + lastOffset = size; + } + } + } + + const VkDeviceSize unusedBytes = size - usedBytes; + PrintDetailedMap_Begin(json, unusedBytes, alloc1stCount + alloc2ndCount, unusedRangeCount); + + // SECOND PASS + lastOffset = 0; + + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + const VkDeviceSize freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset; + size_t nextAlloc2ndIndex = 0; + while (lastOffset < freeSpace2ndTo1stEnd) + { + // Find next non-null allocation or move nextAlloc2ndIndex to the end. + while (nextAlloc2ndIndex < suballoc2ndCount && + suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL) + { + ++nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex < suballoc2ndCount) + { + const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.userData); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc2ndIndex; + } + // We are at the end. + else + { + if (lastOffset < freeSpace2ndTo1stEnd) + { + // There is free space from lastOffset to freeSpace2ndTo1stEnd. + const VkDeviceSize unusedRangeSize = freeSpace2ndTo1stEnd - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // End of loop. + lastOffset = freeSpace2ndTo1stEnd; + } + } + } + + nextAlloc1stIndex = m_1stNullItemsBeginCount; + while (lastOffset < freeSpace1stTo2ndEnd) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc1stIndex < suballoc1stCount && + suballocations1st[nextAlloc1stIndex].userData == VMA_NULL) + { + ++nextAlloc1stIndex; + } + + // Found non-null allocation. + if (nextAlloc1stIndex < suballoc1stCount) + { + const VmaSuballocation& suballoc = suballocations1st[nextAlloc1stIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.userData); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc1stIndex; + } + // We are at the end. + else + { + if (lastOffset < freeSpace1stTo2ndEnd) + { + // There is free space from lastOffset to freeSpace1stTo2ndEnd. + const VkDeviceSize unusedRangeSize = freeSpace1stTo2ndEnd - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // End of loop. + lastOffset = freeSpace1stTo2ndEnd; + } + } + + if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + size_t nextAlloc2ndIndex = suballocations2nd.size() - 1; + while (lastOffset < size) + { + // Find next non-null allocation or move nextAlloc2ndIndex to the end. + while (nextAlloc2ndIndex != SIZE_MAX && + suballocations2nd[nextAlloc2ndIndex].userData == VMA_NULL) + { + --nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex != SIZE_MAX) + { + const VmaSuballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const VkDeviceSize unusedRangeSize = suballoc.offset - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.userData); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + --nextAlloc2ndIndex; + } + // We are at the end. + else + { + if (lastOffset < size) + { + // There is free space from lastOffset to size. + const VkDeviceSize unusedRangeSize = size - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // End of loop. + lastOffset = size; + } + } + } + + PrintDetailedMap_End(json); +} +#endif // VMA_STATS_STRING_ENABLED + +bool VmaBlockMetadata_Linear::CreateAllocationRequest( + VkDeviceSize allocSize, + VkDeviceSize allocAlignment, + bool upperAddress, + VmaSuballocationType allocType, + uint32_t strategy, + VmaAllocationRequest* pAllocationRequest) +{ + VMA_ASSERT(allocSize > 0); + VMA_ASSERT(allocType != VMA_SUBALLOCATION_TYPE_FREE); + VMA_ASSERT(pAllocationRequest != VMA_NULL); + VMA_HEAVY_ASSERT(Validate()); + + if(allocSize > GetSize()) + return false; + + pAllocationRequest->size = allocSize; + return upperAddress ? + CreateAllocationRequest_UpperAddress( + allocSize, allocAlignment, allocType, strategy, pAllocationRequest) : + CreateAllocationRequest_LowerAddress( + allocSize, allocAlignment, allocType, strategy, pAllocationRequest); +} + +VkResult VmaBlockMetadata_Linear::CheckCorruption(const void* pBlockData) +{ + VMA_ASSERT(!IsVirtual()); + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + for (size_t i = m_1stNullItemsBeginCount, count = suballocations1st.size(); i < count; ++i) + { + const VmaSuballocation& suballoc = suballocations1st[i]; + if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE) + { + if (!VmaValidateMagicValue(pBlockData, suballoc.offset + suballoc.size)) + { + VMA_ASSERT(0 && "MEMORY CORRUPTION DETECTED AFTER VALIDATED ALLOCATION!"); + return VK_ERROR_UNKNOWN_COPY; + } + } + } + + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + for (size_t i = 0, count = suballocations2nd.size(); i < count; ++i) + { + const VmaSuballocation& suballoc = suballocations2nd[i]; + if (suballoc.type != VMA_SUBALLOCATION_TYPE_FREE) + { + if (!VmaValidateMagicValue(pBlockData, suballoc.offset + suballoc.size)) + { + VMA_ASSERT(0 && "MEMORY CORRUPTION DETECTED AFTER VALIDATED ALLOCATION!"); + return VK_ERROR_UNKNOWN_COPY; + } + } + } + + return VK_SUCCESS; +} + +void VmaBlockMetadata_Linear::Alloc( + const VmaAllocationRequest& request, + VmaSuballocationType type, + void* userData) +{ + const VkDeviceSize offset = (VkDeviceSize)request.allocHandle - 1; + const VmaSuballocation newSuballoc = { offset, request.size, userData, type }; + + switch (request.type) + { + case VmaAllocationRequestType::UpperAddress: + { + VMA_ASSERT(m_2ndVectorMode != SECOND_VECTOR_RING_BUFFER && + "CRITICAL ERROR: Trying to use linear allocator as double stack while it was already used as ring buffer."); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + suballocations2nd.push_back(newSuballoc); + m_2ndVectorMode = SECOND_VECTOR_DOUBLE_STACK; + } + break; + case VmaAllocationRequestType::EndOf1st: + { + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + + VMA_ASSERT(suballocations1st.empty() || + offset >= suballocations1st.back().offset + suballocations1st.back().size); + // Check if it fits before the end of the block. + VMA_ASSERT(offset + request.size <= GetSize()); + + suballocations1st.push_back(newSuballoc); + } + break; + case VmaAllocationRequestType::EndOf2nd: + { + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + // New allocation at the end of 2-part ring buffer, so before first allocation from 1st vector. + VMA_ASSERT(!suballocations1st.empty() && + offset + request.size <= suballocations1st[m_1stNullItemsBeginCount].offset); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + switch (m_2ndVectorMode) + { + case SECOND_VECTOR_EMPTY: + // First allocation from second part ring buffer. + VMA_ASSERT(suballocations2nd.empty()); + m_2ndVectorMode = SECOND_VECTOR_RING_BUFFER; + break; + case SECOND_VECTOR_RING_BUFFER: + // 2-part ring buffer is already started. + VMA_ASSERT(!suballocations2nd.empty()); + break; + case SECOND_VECTOR_DOUBLE_STACK: + VMA_ASSERT(0 && "CRITICAL ERROR: Trying to use linear allocator as ring buffer while it was already used as double stack."); + break; + default: + VMA_ASSERT(0); + } + + suballocations2nd.push_back(newSuballoc); + } + break; + default: + VMA_ASSERT(0 && "CRITICAL INTERNAL ERROR."); + } + + m_SumFreeSize -= newSuballoc.size; +} + +void VmaBlockMetadata_Linear::Free(VmaAllocHandle allocHandle) +{ + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + VkDeviceSize offset = (VkDeviceSize)allocHandle - 1; + + if (!suballocations1st.empty()) + { + // First allocation: Mark it as next empty at the beginning. + VmaSuballocation& firstSuballoc = suballocations1st[m_1stNullItemsBeginCount]; + if (firstSuballoc.offset == offset) + { + firstSuballoc.type = VMA_SUBALLOCATION_TYPE_FREE; + firstSuballoc.userData = VMA_NULL; + m_SumFreeSize += firstSuballoc.size; + ++m_1stNullItemsBeginCount; + CleanupAfterFree(); + return; + } + } + + // Last allocation in 2-part ring buffer or top of upper stack (same logic). + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER || + m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + VmaSuballocation& lastSuballoc = suballocations2nd.back(); + if (lastSuballoc.offset == offset) + { + m_SumFreeSize += lastSuballoc.size; + suballocations2nd.pop_back(); + CleanupAfterFree(); + return; + } + } + // Last allocation in 1st vector. + else if (m_2ndVectorMode == SECOND_VECTOR_EMPTY) + { + VmaSuballocation& lastSuballoc = suballocations1st.back(); + if (lastSuballoc.offset == offset) + { + m_SumFreeSize += lastSuballoc.size; + suballocations1st.pop_back(); + CleanupAfterFree(); + return; + } + } + + VmaSuballocation refSuballoc; + refSuballoc.offset = offset; + // Rest of members stays uninitialized intentionally for better performance. + + // Item from the middle of 1st vector. + { + const SuballocationVectorType::iterator it = VmaBinaryFindSorted( + suballocations1st.begin() + m_1stNullItemsBeginCount, + suballocations1st.end(), + refSuballoc, + VmaSuballocationOffsetLess()); + if (it != suballocations1st.end()) + { + it->type = VMA_SUBALLOCATION_TYPE_FREE; + it->userData = VMA_NULL; + ++m_1stNullItemsMiddleCount; + m_SumFreeSize += it->size; + CleanupAfterFree(); + return; + } + } + + if (m_2ndVectorMode != SECOND_VECTOR_EMPTY) + { + // Item from the middle of 2nd vector. + const SuballocationVectorType::iterator it = m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER ? + VmaBinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, VmaSuballocationOffsetLess()) : + VmaBinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, VmaSuballocationOffsetGreater()); + if (it != suballocations2nd.end()) + { + it->type = VMA_SUBALLOCATION_TYPE_FREE; + it->userData = VMA_NULL; + ++m_2ndNullItemsCount; + m_SumFreeSize += it->size; + CleanupAfterFree(); + return; + } + } + + VMA_ASSERT(0 && "Allocation to free not found in linear allocator!"); +} + +void VmaBlockMetadata_Linear::GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) +{ + outInfo.offset = (VkDeviceSize)allocHandle - 1; + VmaSuballocation& suballoc = FindSuballocation(outInfo.offset); + outInfo.size = suballoc.size; + outInfo.pUserData = suballoc.userData; +} + +void* VmaBlockMetadata_Linear::GetAllocationUserData(VmaAllocHandle allocHandle) const +{ + return FindSuballocation((VkDeviceSize)allocHandle - 1).userData; +} + +VmaAllocHandle VmaBlockMetadata_Linear::GetAllocationListBegin() const +{ + // Function only used for defragmentation, which is disabled for this algorithm + VMA_ASSERT(0); + return VK_NULL_HANDLE; +} + +VmaAllocHandle VmaBlockMetadata_Linear::GetNextAllocation(VmaAllocHandle prevAlloc) const +{ + // Function only used for defragmentation, which is disabled for this algorithm + VMA_ASSERT(0); + return VK_NULL_HANDLE; +} + +VkDeviceSize VmaBlockMetadata_Linear::GetNextFreeRegionSize(VmaAllocHandle alloc) const +{ + // Function only used for defragmentation, which is disabled for this algorithm + VMA_ASSERT(0); + return 0; +} + +void VmaBlockMetadata_Linear::Clear() +{ + m_SumFreeSize = GetSize(); + m_Suballocations0.clear(); + m_Suballocations1.clear(); + // Leaving m_1stVectorIndex unchanged - it doesn't matter. + m_2ndVectorMode = SECOND_VECTOR_EMPTY; + m_1stNullItemsBeginCount = 0; + m_1stNullItemsMiddleCount = 0; + m_2ndNullItemsCount = 0; +} + +void VmaBlockMetadata_Linear::SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) +{ + VmaSuballocation& suballoc = FindSuballocation((VkDeviceSize)allocHandle - 1); + suballoc.userData = userData; +} + +void VmaBlockMetadata_Linear::DebugLogAllAllocations() const +{ + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + for (auto it = suballocations1st.begin() + m_1stNullItemsBeginCount; it != suballocations1st.end(); ++it) + if (it->type != VMA_SUBALLOCATION_TYPE_FREE) + DebugLogAllocation(it->offset, it->size, it->userData); + + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + for (auto it = suballocations2nd.begin(); it != suballocations2nd.end(); ++it) + if (it->type != VMA_SUBALLOCATION_TYPE_FREE) + DebugLogAllocation(it->offset, it->size, it->userData); +} + +VmaSuballocation& VmaBlockMetadata_Linear::FindSuballocation(VkDeviceSize offset) const +{ + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + VmaSuballocation refSuballoc; + refSuballoc.offset = offset; + // Rest of members stays uninitialized intentionally for better performance. + + // Item from the 1st vector. + { + SuballocationVectorType::const_iterator it = VmaBinaryFindSorted( + suballocations1st.begin() + m_1stNullItemsBeginCount, + suballocations1st.end(), + refSuballoc, + VmaSuballocationOffsetLess()); + if (it != suballocations1st.end()) + { + return const_cast(*it); + } + } + + if (m_2ndVectorMode != SECOND_VECTOR_EMPTY) + { + // Rest of members stays uninitialized intentionally for better performance. + SuballocationVectorType::const_iterator it = m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER ? + VmaBinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, VmaSuballocationOffsetLess()) : + VmaBinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, VmaSuballocationOffsetGreater()); + if (it != suballocations2nd.end()) + { + return const_cast(*it); + } + } + + VMA_ASSERT(0 && "Allocation not found in linear allocator!"); + return const_cast(suballocations1st.back()); // Should never occur. +} + +bool VmaBlockMetadata_Linear::ShouldCompact1st() const +{ + const size_t nullItemCount = m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount; + const size_t suballocCount = AccessSuballocations1st().size(); + return suballocCount > 32 && nullItemCount * 2 >= (suballocCount - nullItemCount) * 3; +} + +void VmaBlockMetadata_Linear::CleanupAfterFree() +{ + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + if (IsEmpty()) + { + suballocations1st.clear(); + suballocations2nd.clear(); + m_1stNullItemsBeginCount = 0; + m_1stNullItemsMiddleCount = 0; + m_2ndNullItemsCount = 0; + m_2ndVectorMode = SECOND_VECTOR_EMPTY; + } + else + { + const size_t suballoc1stCount = suballocations1st.size(); + const size_t nullItem1stCount = m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount; + VMA_ASSERT(nullItem1stCount <= suballoc1stCount); + + // Find more null items at the beginning of 1st vector. + while (m_1stNullItemsBeginCount < suballoc1stCount && + suballocations1st[m_1stNullItemsBeginCount].type == VMA_SUBALLOCATION_TYPE_FREE) + { + ++m_1stNullItemsBeginCount; + --m_1stNullItemsMiddleCount; + } + + // Find more null items at the end of 1st vector. + while (m_1stNullItemsMiddleCount > 0 && + suballocations1st.back().type == VMA_SUBALLOCATION_TYPE_FREE) + { + --m_1stNullItemsMiddleCount; + suballocations1st.pop_back(); + } + + // Find more null items at the end of 2nd vector. + while (m_2ndNullItemsCount > 0 && + suballocations2nd.back().type == VMA_SUBALLOCATION_TYPE_FREE) + { + --m_2ndNullItemsCount; + suballocations2nd.pop_back(); + } + + // Find more null items at the beginning of 2nd vector. + while (m_2ndNullItemsCount > 0 && + suballocations2nd[0].type == VMA_SUBALLOCATION_TYPE_FREE) + { + --m_2ndNullItemsCount; + VmaVectorRemove(suballocations2nd, 0); + } + + if (ShouldCompact1st()) + { + const size_t nonNullItemCount = suballoc1stCount - nullItem1stCount; + size_t srcIndex = m_1stNullItemsBeginCount; + for (size_t dstIndex = 0; dstIndex < nonNullItemCount; ++dstIndex) + { + while (suballocations1st[srcIndex].type == VMA_SUBALLOCATION_TYPE_FREE) + { + ++srcIndex; + } + if (dstIndex != srcIndex) + { + suballocations1st[dstIndex] = suballocations1st[srcIndex]; + } + ++srcIndex; + } + suballocations1st.resize(nonNullItemCount); + m_1stNullItemsBeginCount = 0; + m_1stNullItemsMiddleCount = 0; + } + + // 2nd vector became empty. + if (suballocations2nd.empty()) + { + m_2ndVectorMode = SECOND_VECTOR_EMPTY; + } + + // 1st vector became empty. + if (suballocations1st.size() - m_1stNullItemsBeginCount == 0) + { + suballocations1st.clear(); + m_1stNullItemsBeginCount = 0; + + if (!suballocations2nd.empty() && m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + // Swap 1st with 2nd. Now 2nd is empty. + m_2ndVectorMode = SECOND_VECTOR_EMPTY; + m_1stNullItemsMiddleCount = m_2ndNullItemsCount; + while (m_1stNullItemsBeginCount < suballocations2nd.size() && + suballocations2nd[m_1stNullItemsBeginCount].type == VMA_SUBALLOCATION_TYPE_FREE) + { + ++m_1stNullItemsBeginCount; + --m_1stNullItemsMiddleCount; + } + m_2ndNullItemsCount = 0; + m_1stVectorIndex ^= 1; + } + } + } + + VMA_HEAVY_ASSERT(Validate()); +} + +bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress( + VkDeviceSize allocSize, + VkDeviceSize allocAlignment, + VmaSuballocationType allocType, + uint32_t strategy, + VmaAllocationRequest* pAllocationRequest) +{ + const VkDeviceSize blockSize = GetSize(); + const VkDeviceSize debugMargin = GetDebugMargin(); + const VkDeviceSize bufferImageGranularity = GetBufferImageGranularity(); + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + if (m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + // Try to allocate at the end of 1st vector. + + VkDeviceSize resultBaseOffset = 0; + if (!suballocations1st.empty()) + { + const VmaSuballocation& lastSuballoc = suballocations1st.back(); + resultBaseOffset = lastSuballoc.offset + lastSuballoc.size + debugMargin; + } + + // Start from offset equal to beginning of free space. + VkDeviceSize resultOffset = resultBaseOffset; + + // Apply alignment. + resultOffset = VmaAlignUp(resultOffset, allocAlignment); + + // Check previous suballocations for BufferImageGranularity conflicts. + // Make bigger alignment if necessary. + if (bufferImageGranularity > 1 && bufferImageGranularity != allocAlignment && !suballocations1st.empty()) + { + bool bufferImageGranularityConflict = false; + for (size_t prevSuballocIndex = suballocations1st.size(); prevSuballocIndex--; ) + { + const VmaSuballocation& prevSuballoc = suballocations1st[prevSuballocIndex]; + if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, resultOffset, bufferImageGranularity)) + { + if (VmaIsBufferImageGranularityConflict(prevSuballoc.type, allocType)) + { + bufferImageGranularityConflict = true; + break; + } + } + else + // Already on previous page. + break; + } + if (bufferImageGranularityConflict) + { + resultOffset = VmaAlignUp(resultOffset, bufferImageGranularity); + } + } + + const VkDeviceSize freeSpaceEnd = m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? + suballocations2nd.back().offset : blockSize; + + // There is enough free space at the end after alignment. + if (resultOffset + allocSize + debugMargin <= freeSpaceEnd) + { + // Check next suballocations for BufferImageGranularity conflicts. + // If conflict exists, allocation cannot be made here. + if ((allocSize % bufferImageGranularity || resultOffset % bufferImageGranularity) && m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + for (size_t nextSuballocIndex = suballocations2nd.size(); nextSuballocIndex--; ) + { + const VmaSuballocation& nextSuballoc = suballocations2nd[nextSuballocIndex]; + if (VmaBlocksOnSamePage(resultOffset, allocSize, nextSuballoc.offset, bufferImageGranularity)) + { + if (VmaIsBufferImageGranularityConflict(allocType, nextSuballoc.type)) + { + return false; + } + } + else + { + // Already on previous page. + break; + } + } + } + + // All tests passed: Success. + pAllocationRequest->allocHandle = (VmaAllocHandle)(resultOffset + 1); + // pAllocationRequest->item, customData unused. + pAllocationRequest->type = VmaAllocationRequestType::EndOf1st; + return true; + } + } + + // Wrap-around to end of 2nd vector. Try to allocate there, watching for the + // beginning of 1st vector as the end of free space. + if (m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + VMA_ASSERT(!suballocations1st.empty()); + + VkDeviceSize resultBaseOffset = 0; + if (!suballocations2nd.empty()) + { + const VmaSuballocation& lastSuballoc = suballocations2nd.back(); + resultBaseOffset = lastSuballoc.offset + lastSuballoc.size + debugMargin; + } + + // Start from offset equal to beginning of free space. + VkDeviceSize resultOffset = resultBaseOffset; + + // Apply alignment. + resultOffset = VmaAlignUp(resultOffset, allocAlignment); + + // Check previous suballocations for BufferImageGranularity conflicts. + // Make bigger alignment if necessary. + if (bufferImageGranularity > 1 && bufferImageGranularity != allocAlignment && !suballocations2nd.empty()) + { + bool bufferImageGranularityConflict = false; + for (size_t prevSuballocIndex = suballocations2nd.size(); prevSuballocIndex--; ) + { + const VmaSuballocation& prevSuballoc = suballocations2nd[prevSuballocIndex]; + if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, resultOffset, bufferImageGranularity)) + { + if (VmaIsBufferImageGranularityConflict(prevSuballoc.type, allocType)) + { + bufferImageGranularityConflict = true; + break; + } + } + else + // Already on previous page. + break; + } + if (bufferImageGranularityConflict) + { + resultOffset = VmaAlignUp(resultOffset, bufferImageGranularity); + } + } + + size_t index1st = m_1stNullItemsBeginCount; + + // There is enough free space at the end after alignment. + if ((index1st == suballocations1st.size() && resultOffset + allocSize + debugMargin <= blockSize) || + (index1st < suballocations1st.size() && resultOffset + allocSize + debugMargin <= suballocations1st[index1st].offset)) + { + // Check next suballocations for BufferImageGranularity conflicts. + // If conflict exists, allocation cannot be made here. + if (allocSize % bufferImageGranularity || resultOffset % bufferImageGranularity) + { + for (size_t nextSuballocIndex = index1st; + nextSuballocIndex < suballocations1st.size(); + nextSuballocIndex++) + { + const VmaSuballocation& nextSuballoc = suballocations1st[nextSuballocIndex]; + if (VmaBlocksOnSamePage(resultOffset, allocSize, nextSuballoc.offset, bufferImageGranularity)) + { + if (VmaIsBufferImageGranularityConflict(allocType, nextSuballoc.type)) + { + return false; + } + } + else + { + // Already on next page. + break; + } + } + } + + // All tests passed: Success. + pAllocationRequest->allocHandle = (VmaAllocHandle)(resultOffset + 1); + pAllocationRequest->type = VmaAllocationRequestType::EndOf2nd; + // pAllocationRequest->item, customData unused. + return true; + } + } + + return false; +} + +bool VmaBlockMetadata_Linear::CreateAllocationRequest_UpperAddress( + VkDeviceSize allocSize, + VkDeviceSize allocAlignment, + VmaSuballocationType allocType, + uint32_t strategy, + VmaAllocationRequest* pAllocationRequest) +{ + const VkDeviceSize blockSize = GetSize(); + const VkDeviceSize bufferImageGranularity = GetBufferImageGranularity(); + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + VMA_ASSERT(0 && "Trying to use pool with linear algorithm as double stack, while it is already being used as ring buffer."); + return false; + } + + // Try to allocate before 2nd.back(), or end of block if 2nd.empty(). + if (allocSize > blockSize) + { + return false; + } + VkDeviceSize resultBaseOffset = blockSize - allocSize; + if (!suballocations2nd.empty()) + { + const VmaSuballocation& lastSuballoc = suballocations2nd.back(); + resultBaseOffset = lastSuballoc.offset - allocSize; + if (allocSize > lastSuballoc.offset) + { + return false; + } + } + + // Start from offset equal to end of free space. + VkDeviceSize resultOffset = resultBaseOffset; + + const VkDeviceSize debugMargin = GetDebugMargin(); + + // Apply debugMargin at the end. + if (debugMargin > 0) + { + if (resultOffset < debugMargin) + { + return false; + } + resultOffset -= debugMargin; + } + + // Apply alignment. + resultOffset = VmaAlignDown(resultOffset, allocAlignment); + + // Check next suballocations from 2nd for BufferImageGranularity conflicts. + // Make bigger alignment if necessary. + if (bufferImageGranularity > 1 && bufferImageGranularity != allocAlignment && !suballocations2nd.empty()) + { + bool bufferImageGranularityConflict = false; + for (size_t nextSuballocIndex = suballocations2nd.size(); nextSuballocIndex--; ) + { + const VmaSuballocation& nextSuballoc = suballocations2nd[nextSuballocIndex]; + if (VmaBlocksOnSamePage(resultOffset, allocSize, nextSuballoc.offset, bufferImageGranularity)) + { + if (VmaIsBufferImageGranularityConflict(nextSuballoc.type, allocType)) + { + bufferImageGranularityConflict = true; + break; + } + } + else + // Already on previous page. + break; + } + if (bufferImageGranularityConflict) + { + resultOffset = VmaAlignDown(resultOffset, bufferImageGranularity); + } + } + + // There is enough free space. + const VkDeviceSize endOf1st = !suballocations1st.empty() ? + suballocations1st.back().offset + suballocations1st.back().size : + 0; + if (endOf1st + debugMargin <= resultOffset) + { + // Check previous suballocations for BufferImageGranularity conflicts. + // If conflict exists, allocation cannot be made here. + if (bufferImageGranularity > 1) + { + for (size_t prevSuballocIndex = suballocations1st.size(); prevSuballocIndex--; ) + { + const VmaSuballocation& prevSuballoc = suballocations1st[prevSuballocIndex]; + if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, resultOffset, bufferImageGranularity)) + { + if (VmaIsBufferImageGranularityConflict(allocType, prevSuballoc.type)) + { + return false; + } + } + else + { + // Already on next page. + break; + } + } + } + + // All tests passed: Success. + pAllocationRequest->allocHandle = (VmaAllocHandle)(resultOffset + 1); + // pAllocationRequest->item unused. + pAllocationRequest->type = VmaAllocationRequestType::UpperAddress; + return true; + } + + return false; +} +#endif // _VMA_BLOCK_METADATA_LINEAR_FUNCTIONS +#endif // _VMA_BLOCK_METADATA_LINEAR + +#ifndef _VMA_BLOCK_METADATA_TLSF +// To not search current larger region if first allocation won't succeed and skip to smaller range +// use with VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT as strategy in CreateAllocationRequest(). +// When fragmentation and reusal of previous blocks doesn't matter then use with +// VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT for fastest alloc time possible. +class VmaBlockMetadata_TLSF : public VmaBlockMetadata +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaBlockMetadata_TLSF) +public: + VmaBlockMetadata_TLSF(const VkAllocationCallbacks* pAllocationCallbacks, + VkDeviceSize bufferImageGranularity, bool isVirtual); + virtual ~VmaBlockMetadata_TLSF(); + + size_t GetAllocationCount() const override { return m_AllocCount; } + size_t GetFreeRegionsCount() const override { return m_BlocksFreeCount + 1; } + VkDeviceSize GetSumFreeSize() const override { return m_BlocksFreeSize + m_NullBlock->size; } + bool IsEmpty() const override { return m_NullBlock->offset == 0; } + VkDeviceSize GetAllocationOffset(VmaAllocHandle allocHandle) const override { return ((Block*)allocHandle)->offset; } + + void Init(VkDeviceSize size) override; + bool Validate() const override; + + void AddDetailedStatistics(VmaDetailedStatistics& inoutStats) const override; + void AddStatistics(VmaStatistics& inoutStats) const override; + +#if VMA_STATS_STRING_ENABLED + void PrintDetailedMap(class VmaJsonWriter& json) const override; +#endif + + bool CreateAllocationRequest( + VkDeviceSize allocSize, + VkDeviceSize allocAlignment, + bool upperAddress, + VmaSuballocationType allocType, + uint32_t strategy, + VmaAllocationRequest* pAllocationRequest) override; + + VkResult CheckCorruption(const void* pBlockData) override; + void Alloc( + const VmaAllocationRequest& request, + VmaSuballocationType type, + void* userData) override; + + void Free(VmaAllocHandle allocHandle) override; + void GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) override; + void* GetAllocationUserData(VmaAllocHandle allocHandle) const override; + VmaAllocHandle GetAllocationListBegin() const override; + VmaAllocHandle GetNextAllocation(VmaAllocHandle prevAlloc) const override; + VkDeviceSize GetNextFreeRegionSize(VmaAllocHandle alloc) const override; + void Clear() override; + void SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) override; + void DebugLogAllAllocations() const override; + +private: + // According to original paper it should be preferable 4 or 5: + // M. Masmano, I. Ripoll, A. Crespo, and J. Real "TLSF: a New Dynamic Memory Allocator for Real-Time Systems" + // http://www.gii.upv.es/tlsf/files/ecrts04_tlsf.pdf + static const uint8_t SECOND_LEVEL_INDEX = 5; + static const uint16_t SMALL_BUFFER_SIZE = 256; + static const uint32_t INITIAL_BLOCK_ALLOC_COUNT = 16; + static const uint8_t MEMORY_CLASS_SHIFT = 7; + static const uint8_t MAX_MEMORY_CLASSES = 65 - MEMORY_CLASS_SHIFT; + + class Block + { + public: + VkDeviceSize offset; + VkDeviceSize size; + Block* prevPhysical; + Block* nextPhysical; + + void MarkFree() { prevFree = VMA_NULL; } + void MarkTaken() { prevFree = this; } + bool IsFree() const { return prevFree != this; } + void*& UserData() { VMA_HEAVY_ASSERT(!IsFree()); return userData; } + Block*& PrevFree() { return prevFree; } + Block*& NextFree() { VMA_HEAVY_ASSERT(IsFree()); return nextFree; } + + private: + Block* prevFree; // Address of the same block here indicates that block is taken + union + { + Block* nextFree; + void* userData; + }; + }; + + size_t m_AllocCount; + // Total number of free blocks besides null block + size_t m_BlocksFreeCount; + // Total size of free blocks excluding null block + VkDeviceSize m_BlocksFreeSize; + uint32_t m_IsFreeBitmap; + uint8_t m_MemoryClasses; + uint32_t m_InnerIsFreeBitmap[MAX_MEMORY_CLASSES]; + uint32_t m_ListsCount; + /* + * 0: 0-3 lists for small buffers + * 1+: 0-(2^SLI-1) lists for normal buffers + */ + Block** m_FreeList; + VmaPoolAllocator m_BlockAllocator; + Block* m_NullBlock; + VmaBlockBufferImageGranularity m_GranularityHandler; + + uint8_t SizeToMemoryClass(VkDeviceSize size) const; + uint16_t SizeToSecondIndex(VkDeviceSize size, uint8_t memoryClass) const; + uint32_t GetListIndex(uint8_t memoryClass, uint16_t secondIndex) const; + uint32_t GetListIndex(VkDeviceSize size) const; + + void RemoveFreeBlock(Block* block); + void InsertFreeBlock(Block* block); + void MergeBlock(Block* block, Block* prev); + + Block* FindFreeBlock(VkDeviceSize size, uint32_t& listIndex) const; + bool CheckBlock( + Block& block, + uint32_t listIndex, + VkDeviceSize allocSize, + VkDeviceSize allocAlignment, + VmaSuballocationType allocType, + VmaAllocationRequest* pAllocationRequest); +}; + +#ifndef _VMA_BLOCK_METADATA_TLSF_FUNCTIONS +VmaBlockMetadata_TLSF::VmaBlockMetadata_TLSF(const VkAllocationCallbacks* pAllocationCallbacks, + VkDeviceSize bufferImageGranularity, bool isVirtual) + : VmaBlockMetadata(pAllocationCallbacks, bufferImageGranularity, isVirtual), + m_AllocCount(0), + m_BlocksFreeCount(0), + m_BlocksFreeSize(0), + m_IsFreeBitmap(0), + m_MemoryClasses(0), + m_ListsCount(0), + m_FreeList(VMA_NULL), + m_BlockAllocator(pAllocationCallbacks, INITIAL_BLOCK_ALLOC_COUNT), + m_NullBlock(VMA_NULL), + m_GranularityHandler(bufferImageGranularity) {} + +VmaBlockMetadata_TLSF::~VmaBlockMetadata_TLSF() +{ + if (m_FreeList) + vma_delete_array(GetAllocationCallbacks(), m_FreeList, m_ListsCount); + m_GranularityHandler.Destroy(GetAllocationCallbacks()); +} + +void VmaBlockMetadata_TLSF::Init(VkDeviceSize size) +{ + VmaBlockMetadata::Init(size); + + if (!IsVirtual()) + m_GranularityHandler.Init(GetAllocationCallbacks(), size); + + m_NullBlock = m_BlockAllocator.Alloc(); + m_NullBlock->size = size; + m_NullBlock->offset = 0; + m_NullBlock->prevPhysical = VMA_NULL; + m_NullBlock->nextPhysical = VMA_NULL; + m_NullBlock->MarkFree(); + m_NullBlock->NextFree() = VMA_NULL; + m_NullBlock->PrevFree() = VMA_NULL; + uint8_t memoryClass = SizeToMemoryClass(size); + uint16_t sli = SizeToSecondIndex(size, memoryClass); + m_ListsCount = (memoryClass == 0 ? 0 : (memoryClass - 1) * (1UL << SECOND_LEVEL_INDEX) + sli) + 1; + if (IsVirtual()) + m_ListsCount += 1UL << SECOND_LEVEL_INDEX; + else + m_ListsCount += 4; + + m_MemoryClasses = memoryClass + uint8_t(2); + memset(m_InnerIsFreeBitmap, 0, MAX_MEMORY_CLASSES * sizeof(uint32_t)); + + m_FreeList = vma_new_array(GetAllocationCallbacks(), Block*, m_ListsCount); + memset(m_FreeList, 0, m_ListsCount * sizeof(Block*)); +} + +bool VmaBlockMetadata_TLSF::Validate() const +{ + VMA_VALIDATE(GetSumFreeSize() <= GetSize()); + + VkDeviceSize calculatedSize = m_NullBlock->size; + VkDeviceSize calculatedFreeSize = m_NullBlock->size; + size_t allocCount = 0; + size_t freeCount = 0; + + // Check integrity of free lists + for (uint32_t list = 0; list < m_ListsCount; ++list) + { + Block* block = m_FreeList[list]; + if (block != VMA_NULL) + { + VMA_VALIDATE(block->IsFree()); + VMA_VALIDATE(block->PrevFree() == VMA_NULL); + while (block->NextFree()) + { + VMA_VALIDATE(block->NextFree()->IsFree()); + VMA_VALIDATE(block->NextFree()->PrevFree() == block); + block = block->NextFree(); + } + } + } + + VkDeviceSize nextOffset = m_NullBlock->offset; + auto validateCtx = m_GranularityHandler.StartValidation(GetAllocationCallbacks(), IsVirtual()); + + VMA_VALIDATE(m_NullBlock->nextPhysical == VMA_NULL); + if (m_NullBlock->prevPhysical) + { + VMA_VALIDATE(m_NullBlock->prevPhysical->nextPhysical == m_NullBlock); + } + // Check all blocks + for (Block* prev = m_NullBlock->prevPhysical; prev != VMA_NULL; prev = prev->prevPhysical) + { + VMA_VALIDATE(prev->offset + prev->size == nextOffset); + nextOffset = prev->offset; + calculatedSize += prev->size; + + uint32_t listIndex = GetListIndex(prev->size); + if (prev->IsFree()) + { + ++freeCount; + // Check if free block belongs to free list + Block* freeBlock = m_FreeList[listIndex]; + VMA_VALIDATE(freeBlock != VMA_NULL); + + bool found = false; + do + { + if (freeBlock == prev) + found = true; + + freeBlock = freeBlock->NextFree(); + } while (!found && freeBlock != VMA_NULL); + + VMA_VALIDATE(found); + calculatedFreeSize += prev->size; + } + else + { + ++allocCount; + // Check if taken block is not on a free list + Block* freeBlock = m_FreeList[listIndex]; + while (freeBlock) + { + VMA_VALIDATE(freeBlock != prev); + freeBlock = freeBlock->NextFree(); + } + + if (!IsVirtual()) + { + VMA_VALIDATE(m_GranularityHandler.Validate(validateCtx, prev->offset, prev->size)); + } + } + + if (prev->prevPhysical) + { + VMA_VALIDATE(prev->prevPhysical->nextPhysical == prev); + } + } + + if (!IsVirtual()) + { + VMA_VALIDATE(m_GranularityHandler.FinishValidation(validateCtx)); + } + + VMA_VALIDATE(nextOffset == 0); + VMA_VALIDATE(calculatedSize == GetSize()); + VMA_VALIDATE(calculatedFreeSize == GetSumFreeSize()); + VMA_VALIDATE(allocCount == m_AllocCount); + VMA_VALIDATE(freeCount == m_BlocksFreeCount); + + return true; +} + +void VmaBlockMetadata_TLSF::AddDetailedStatistics(VmaDetailedStatistics& inoutStats) const +{ + inoutStats.statistics.blockCount++; + inoutStats.statistics.blockBytes += GetSize(); + if (m_NullBlock->size > 0) + VmaAddDetailedStatisticsUnusedRange(inoutStats, m_NullBlock->size); + + for (Block* block = m_NullBlock->prevPhysical; block != VMA_NULL; block = block->prevPhysical) + { + if (block->IsFree()) + VmaAddDetailedStatisticsUnusedRange(inoutStats, block->size); + else + VmaAddDetailedStatisticsAllocation(inoutStats, block->size); + } +} + +void VmaBlockMetadata_TLSF::AddStatistics(VmaStatistics& inoutStats) const +{ + inoutStats.blockCount++; + inoutStats.allocationCount += (uint32_t)m_AllocCount; + inoutStats.blockBytes += GetSize(); + inoutStats.allocationBytes += GetSize() - GetSumFreeSize(); +} + +#if VMA_STATS_STRING_ENABLED +void VmaBlockMetadata_TLSF::PrintDetailedMap(class VmaJsonWriter& json) const +{ + size_t blockCount = m_AllocCount + m_BlocksFreeCount; + VmaStlAllocator allocator(GetAllocationCallbacks()); + VmaVector> blockList(blockCount, allocator); + + size_t i = blockCount; + for (Block* block = m_NullBlock->prevPhysical; block != VMA_NULL; block = block->prevPhysical) + { + blockList[--i] = block; + } + VMA_ASSERT(i == 0); + + VmaDetailedStatistics stats; + VmaClearDetailedStatistics(stats); + AddDetailedStatistics(stats); + + PrintDetailedMap_Begin(json, + stats.statistics.blockBytes - stats.statistics.allocationBytes, + stats.statistics.allocationCount, + stats.unusedRangeCount); + + for (; i < blockCount; ++i) + { + Block* block = blockList[i]; + if (block->IsFree()) + PrintDetailedMap_UnusedRange(json, block->offset, block->size); + else + PrintDetailedMap_Allocation(json, block->offset, block->size, block->UserData()); + } + if (m_NullBlock->size > 0) + PrintDetailedMap_UnusedRange(json, m_NullBlock->offset, m_NullBlock->size); + + PrintDetailedMap_End(json); +} +#endif + +bool VmaBlockMetadata_TLSF::CreateAllocationRequest( + VkDeviceSize allocSize, + VkDeviceSize allocAlignment, + bool upperAddress, + VmaSuballocationType allocType, + uint32_t strategy, + VmaAllocationRequest* pAllocationRequest) +{ + VMA_ASSERT(allocSize > 0 && "Cannot allocate empty block!"); + VMA_ASSERT(!upperAddress && "VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT can be used only with linear algorithm."); + + // For small granularity round up + if (!IsVirtual()) + m_GranularityHandler.RoundupAllocRequest(allocType, allocSize, allocAlignment); + + allocSize += GetDebugMargin(); + // Quick check for too small pool + if (allocSize > GetSumFreeSize()) + return false; + + // If no free blocks in pool then check only null block + if (m_BlocksFreeCount == 0) + return CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, allocType, pAllocationRequest); + + // Round up to the next block + VkDeviceSize sizeForNextList = allocSize; + VkDeviceSize smallSizeStep = VkDeviceSize(SMALL_BUFFER_SIZE / (IsVirtual() ? 1 << SECOND_LEVEL_INDEX : 4)); + if (allocSize > SMALL_BUFFER_SIZE) + { + sizeForNextList += (1ULL << (VMA_BITSCAN_MSB(allocSize) - SECOND_LEVEL_INDEX)); + } + else if (allocSize > SMALL_BUFFER_SIZE - smallSizeStep) + sizeForNextList = SMALL_BUFFER_SIZE + 1; + else + sizeForNextList += smallSizeStep; + + uint32_t nextListIndex = m_ListsCount; + uint32_t prevListIndex = m_ListsCount; + Block* nextListBlock = VMA_NULL; + Block* prevListBlock = VMA_NULL; + + // Check blocks according to strategies + if (strategy & VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT) + { + // Quick check for larger block first + nextListBlock = FindFreeBlock(sizeForNextList, nextListIndex); + if (nextListBlock != VMA_NULL && CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, allocType, pAllocationRequest)) + return true; + + // If not fitted then null block + if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, allocType, pAllocationRequest)) + return true; + + // Null block failed, search larger bucket + while (nextListBlock) + { + if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, allocType, pAllocationRequest)) + return true; + nextListBlock = nextListBlock->NextFree(); + } + + // Failed again, check best fit bucket + prevListBlock = FindFreeBlock(allocSize, prevListIndex); + while (prevListBlock) + { + if (CheckBlock(*prevListBlock, prevListIndex, allocSize, allocAlignment, allocType, pAllocationRequest)) + return true; + prevListBlock = prevListBlock->NextFree(); + } + } + else if (strategy & VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT) + { + // Check best fit bucket + prevListBlock = FindFreeBlock(allocSize, prevListIndex); + while (prevListBlock) + { + if (CheckBlock(*prevListBlock, prevListIndex, allocSize, allocAlignment, allocType, pAllocationRequest)) + return true; + prevListBlock = prevListBlock->NextFree(); + } + + // If failed check null block + if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, allocType, pAllocationRequest)) + return true; + + // Check larger bucket + nextListBlock = FindFreeBlock(sizeForNextList, nextListIndex); + while (nextListBlock) + { + if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, allocType, pAllocationRequest)) + return true; + nextListBlock = nextListBlock->NextFree(); + } + } + else if (strategy & VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT ) + { + // Perform search from the start + VmaStlAllocator allocator(GetAllocationCallbacks()); + VmaVector> blockList(m_BlocksFreeCount, allocator); + + size_t i = m_BlocksFreeCount; + for (Block* block = m_NullBlock->prevPhysical; block != VMA_NULL; block = block->prevPhysical) + { + if (block->IsFree() && block->size >= allocSize) + blockList[--i] = block; + } + + for (; i < m_BlocksFreeCount; ++i) + { + Block& block = *blockList[i]; + if (CheckBlock(block, GetListIndex(block.size), allocSize, allocAlignment, allocType, pAllocationRequest)) + return true; + } + + // If failed check null block + if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, allocType, pAllocationRequest)) + return true; + + // Whole range searched, no more memory + return false; + } + else + { + // Check larger bucket + nextListBlock = FindFreeBlock(sizeForNextList, nextListIndex); + while (nextListBlock) + { + if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, allocType, pAllocationRequest)) + return true; + nextListBlock = nextListBlock->NextFree(); + } + + // If failed check null block + if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, allocType, pAllocationRequest)) + return true; + + // Check best fit bucket + prevListBlock = FindFreeBlock(allocSize, prevListIndex); + while (prevListBlock) + { + if (CheckBlock(*prevListBlock, prevListIndex, allocSize, allocAlignment, allocType, pAllocationRequest)) + return true; + prevListBlock = prevListBlock->NextFree(); + } + } + + // Worst case, full search has to be done + while (++nextListIndex < m_ListsCount) + { + nextListBlock = m_FreeList[nextListIndex]; + while (nextListBlock) + { + if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, allocType, pAllocationRequest)) + return true; + nextListBlock = nextListBlock->NextFree(); + } + } + + // No more memory sadly + return false; +} + +VkResult VmaBlockMetadata_TLSF::CheckCorruption(const void* pBlockData) +{ + for (Block* block = m_NullBlock->prevPhysical; block != VMA_NULL; block = block->prevPhysical) + { + if (!block->IsFree()) + { + if (!VmaValidateMagicValue(pBlockData, block->offset + block->size)) + { + VMA_ASSERT(0 && "MEMORY CORRUPTION DETECTED AFTER VALIDATED ALLOCATION!"); + return VK_ERROR_UNKNOWN_COPY; + } + } + } + + return VK_SUCCESS; +} + +void VmaBlockMetadata_TLSF::Alloc( + const VmaAllocationRequest& request, + VmaSuballocationType type, + void* userData) +{ + VMA_ASSERT(request.type == VmaAllocationRequestType::TLSF); + + // Get block and pop it from the free list + Block* currentBlock = (Block*)request.allocHandle; + VkDeviceSize offset = request.algorithmData; + VMA_ASSERT(currentBlock != VMA_NULL); + VMA_ASSERT(currentBlock->offset <= offset); + + if (currentBlock != m_NullBlock) + RemoveFreeBlock(currentBlock); + + VkDeviceSize debugMargin = GetDebugMargin(); + VkDeviceSize misssingAlignment = offset - currentBlock->offset; + + // Append missing alignment to prev block or create new one + if (misssingAlignment) + { + Block* prevBlock = currentBlock->prevPhysical; + VMA_ASSERT(prevBlock != VMA_NULL && "There should be no missing alignment at offset 0!"); + + if (prevBlock->IsFree() && prevBlock->size != debugMargin) + { + uint32_t oldList = GetListIndex(prevBlock->size); + prevBlock->size += misssingAlignment; + // Check if new size crosses list bucket + if (oldList != GetListIndex(prevBlock->size)) + { + prevBlock->size -= misssingAlignment; + RemoveFreeBlock(prevBlock); + prevBlock->size += misssingAlignment; + InsertFreeBlock(prevBlock); + } + else + m_BlocksFreeSize += misssingAlignment; + } + else + { + Block* newBlock = m_BlockAllocator.Alloc(); + currentBlock->prevPhysical = newBlock; + prevBlock->nextPhysical = newBlock; + newBlock->prevPhysical = prevBlock; + newBlock->nextPhysical = currentBlock; + newBlock->size = misssingAlignment; + newBlock->offset = currentBlock->offset; + newBlock->MarkTaken(); + + InsertFreeBlock(newBlock); + } + + currentBlock->size -= misssingAlignment; + currentBlock->offset += misssingAlignment; + } + + VkDeviceSize size = request.size + debugMargin; + if (currentBlock->size == size) + { + if (currentBlock == m_NullBlock) + { + // Setup new null block + m_NullBlock = m_BlockAllocator.Alloc(); + m_NullBlock->size = 0; + m_NullBlock->offset = currentBlock->offset + size; + m_NullBlock->prevPhysical = currentBlock; + m_NullBlock->nextPhysical = VMA_NULL; + m_NullBlock->MarkFree(); + m_NullBlock->PrevFree() = VMA_NULL; + m_NullBlock->NextFree() = VMA_NULL; + currentBlock->nextPhysical = m_NullBlock; + currentBlock->MarkTaken(); + } + } + else + { + VMA_ASSERT(currentBlock->size > size && "Proper block already found, shouldn't find smaller one!"); + + // Create new free block + Block* newBlock = m_BlockAllocator.Alloc(); + newBlock->size = currentBlock->size - size; + newBlock->offset = currentBlock->offset + size; + newBlock->prevPhysical = currentBlock; + newBlock->nextPhysical = currentBlock->nextPhysical; + currentBlock->nextPhysical = newBlock; + currentBlock->size = size; + + if (currentBlock == m_NullBlock) + { + m_NullBlock = newBlock; + m_NullBlock->MarkFree(); + m_NullBlock->NextFree() = VMA_NULL; + m_NullBlock->PrevFree() = VMA_NULL; + currentBlock->MarkTaken(); + } + else + { + newBlock->nextPhysical->prevPhysical = newBlock; + newBlock->MarkTaken(); + InsertFreeBlock(newBlock); + } + } + currentBlock->UserData() = userData; + + if (debugMargin > 0) + { + currentBlock->size -= debugMargin; + Block* newBlock = m_BlockAllocator.Alloc(); + newBlock->size = debugMargin; + newBlock->offset = currentBlock->offset + currentBlock->size; + newBlock->prevPhysical = currentBlock; + newBlock->nextPhysical = currentBlock->nextPhysical; + newBlock->MarkTaken(); + currentBlock->nextPhysical->prevPhysical = newBlock; + currentBlock->nextPhysical = newBlock; + InsertFreeBlock(newBlock); + } + + if (!IsVirtual()) + m_GranularityHandler.AllocPages((uint8_t)(uintptr_t)request.customData, + currentBlock->offset, currentBlock->size); + ++m_AllocCount; +} + +void VmaBlockMetadata_TLSF::Free(VmaAllocHandle allocHandle) +{ + Block* block = (Block*)allocHandle; + Block* next = block->nextPhysical; + VMA_ASSERT(!block->IsFree() && "Block is already free!"); + + if (!IsVirtual()) + m_GranularityHandler.FreePages(block->offset, block->size); + --m_AllocCount; + + VkDeviceSize debugMargin = GetDebugMargin(); + if (debugMargin > 0) + { + RemoveFreeBlock(next); + MergeBlock(next, block); + block = next; + next = next->nextPhysical; + } + + // Try merging + Block* prev = block->prevPhysical; + if (prev != VMA_NULL && prev->IsFree() && prev->size != debugMargin) + { + RemoveFreeBlock(prev); + MergeBlock(block, prev); + } + + if (!next->IsFree()) + InsertFreeBlock(block); + else if (next == m_NullBlock) + MergeBlock(m_NullBlock, block); + else + { + RemoveFreeBlock(next); + MergeBlock(next, block); + InsertFreeBlock(next); + } +} + +void VmaBlockMetadata_TLSF::GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) +{ + Block* block = (Block*)allocHandle; + VMA_ASSERT(!block->IsFree() && "Cannot get allocation info for free block!"); + outInfo.offset = block->offset; + outInfo.size = block->size; + outInfo.pUserData = block->UserData(); +} + +void* VmaBlockMetadata_TLSF::GetAllocationUserData(VmaAllocHandle allocHandle) const +{ + Block* block = (Block*)allocHandle; + VMA_ASSERT(!block->IsFree() && "Cannot get user data for free block!"); + return block->UserData(); +} + +VmaAllocHandle VmaBlockMetadata_TLSF::GetAllocationListBegin() const +{ + if (m_AllocCount == 0) + return VK_NULL_HANDLE; + + for (Block* block = m_NullBlock->prevPhysical; block; block = block->prevPhysical) + { + if (!block->IsFree()) + return (VmaAllocHandle)block; + } + VMA_ASSERT(false && "If m_AllocCount > 0 then should find any allocation!"); + return VK_NULL_HANDLE; +} + +VmaAllocHandle VmaBlockMetadata_TLSF::GetNextAllocation(VmaAllocHandle prevAlloc) const +{ + Block* startBlock = (Block*)prevAlloc; + VMA_ASSERT(!startBlock->IsFree() && "Incorrect block!"); + + for (Block* block = startBlock->prevPhysical; block; block = block->prevPhysical) + { + if (!block->IsFree()) + return (VmaAllocHandle)block; + } + return VK_NULL_HANDLE; +} + +VkDeviceSize VmaBlockMetadata_TLSF::GetNextFreeRegionSize(VmaAllocHandle alloc) const +{ + Block* block = (Block*)alloc; + VMA_ASSERT(!block->IsFree() && "Incorrect block!"); + + if (block->prevPhysical) + return block->prevPhysical->IsFree() ? block->prevPhysical->size : 0; + return 0; +} + +void VmaBlockMetadata_TLSF::Clear() +{ + m_AllocCount = 0; + m_BlocksFreeCount = 0; + m_BlocksFreeSize = 0; + m_IsFreeBitmap = 0; + m_NullBlock->offset = 0; + m_NullBlock->size = GetSize(); + Block* block = m_NullBlock->prevPhysical; + m_NullBlock->prevPhysical = VMA_NULL; + while (block) + { + Block* prev = block->prevPhysical; + m_BlockAllocator.Free(block); + block = prev; + } + memset(m_FreeList, 0, m_ListsCount * sizeof(Block*)); + memset(m_InnerIsFreeBitmap, 0, m_MemoryClasses * sizeof(uint32_t)); + m_GranularityHandler.Clear(); +} + +void VmaBlockMetadata_TLSF::SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) +{ + Block* block = (Block*)allocHandle; + VMA_ASSERT(!block->IsFree() && "Trying to set user data for not allocated block!"); + block->UserData() = userData; +} + +void VmaBlockMetadata_TLSF::DebugLogAllAllocations() const +{ + for (Block* block = m_NullBlock->prevPhysical; block != VMA_NULL; block = block->prevPhysical) + if (!block->IsFree()) + DebugLogAllocation(block->offset, block->size, block->UserData()); +} + +uint8_t VmaBlockMetadata_TLSF::SizeToMemoryClass(VkDeviceSize size) const +{ + if (size > SMALL_BUFFER_SIZE) + return uint8_t(VMA_BITSCAN_MSB(size) - MEMORY_CLASS_SHIFT); + return 0; +} + +uint16_t VmaBlockMetadata_TLSF::SizeToSecondIndex(VkDeviceSize size, uint8_t memoryClass) const +{ + if (memoryClass == 0) + { + if (IsVirtual()) + return static_cast((size - 1) / 8); + else + return static_cast((size - 1) / 64); + } + return static_cast((size >> (memoryClass + MEMORY_CLASS_SHIFT - SECOND_LEVEL_INDEX)) ^ (1U << SECOND_LEVEL_INDEX)); +} + +uint32_t VmaBlockMetadata_TLSF::GetListIndex(uint8_t memoryClass, uint16_t secondIndex) const +{ + if (memoryClass == 0) + return secondIndex; + + const uint32_t index = static_cast(memoryClass - 1) * (1 << SECOND_LEVEL_INDEX) + secondIndex; + if (IsVirtual()) + return index + (1 << SECOND_LEVEL_INDEX); + else + return index + 4; +} + +uint32_t VmaBlockMetadata_TLSF::GetListIndex(VkDeviceSize size) const +{ + uint8_t memoryClass = SizeToMemoryClass(size); + return GetListIndex(memoryClass, SizeToSecondIndex(size, memoryClass)); +} + +void VmaBlockMetadata_TLSF::RemoveFreeBlock(Block* block) +{ + VMA_ASSERT(block != m_NullBlock); + VMA_ASSERT(block->IsFree()); + + if (block->NextFree() != VMA_NULL) + block->NextFree()->PrevFree() = block->PrevFree(); + if (block->PrevFree() != VMA_NULL) + block->PrevFree()->NextFree() = block->NextFree(); + else + { + uint8_t memClass = SizeToMemoryClass(block->size); + uint16_t secondIndex = SizeToSecondIndex(block->size, memClass); + uint32_t index = GetListIndex(memClass, secondIndex); + VMA_ASSERT(m_FreeList[index] == block); + m_FreeList[index] = block->NextFree(); + if (block->NextFree() == VMA_NULL) + { + m_InnerIsFreeBitmap[memClass] &= ~(1U << secondIndex); + if (m_InnerIsFreeBitmap[memClass] == 0) + m_IsFreeBitmap &= ~(1UL << memClass); + } + } + block->MarkTaken(); + block->UserData() = VMA_NULL; + --m_BlocksFreeCount; + m_BlocksFreeSize -= block->size; +} + +void VmaBlockMetadata_TLSF::InsertFreeBlock(Block* block) +{ + VMA_ASSERT(block != m_NullBlock); + VMA_ASSERT(!block->IsFree() && "Cannot insert block twice!"); + + uint8_t memClass = SizeToMemoryClass(block->size); + uint16_t secondIndex = SizeToSecondIndex(block->size, memClass); + uint32_t index = GetListIndex(memClass, secondIndex); + VMA_ASSERT(index < m_ListsCount); + block->PrevFree() = VMA_NULL; + block->NextFree() = m_FreeList[index]; + m_FreeList[index] = block; + if (block->NextFree() != VMA_NULL) + block->NextFree()->PrevFree() = block; + else + { + m_InnerIsFreeBitmap[memClass] |= 1U << secondIndex; + m_IsFreeBitmap |= 1UL << memClass; + } + ++m_BlocksFreeCount; + m_BlocksFreeSize += block->size; +} + +void VmaBlockMetadata_TLSF::MergeBlock(Block* block, Block* prev) +{ + VMA_ASSERT(block->prevPhysical == prev && "Cannot merge separate physical regions!"); + VMA_ASSERT(!prev->IsFree() && "Cannot merge block that belongs to free list!"); + + block->offset = prev->offset; + block->size += prev->size; + block->prevPhysical = prev->prevPhysical; + if (block->prevPhysical) + block->prevPhysical->nextPhysical = block; + m_BlockAllocator.Free(prev); +} + +VmaBlockMetadata_TLSF::Block* VmaBlockMetadata_TLSF::FindFreeBlock(VkDeviceSize size, uint32_t& listIndex) const +{ + uint8_t memoryClass = SizeToMemoryClass(size); + uint32_t innerFreeMap = m_InnerIsFreeBitmap[memoryClass] & (~0U << SizeToSecondIndex(size, memoryClass)); + if (!innerFreeMap) + { + // Check higher levels for available blocks + uint32_t freeMap = m_IsFreeBitmap & (~0UL << (memoryClass + 1)); + if (!freeMap) + return VMA_NULL; // No more memory available + + // Find lowest free region + memoryClass = VMA_BITSCAN_LSB(freeMap); + innerFreeMap = m_InnerIsFreeBitmap[memoryClass]; + VMA_ASSERT(innerFreeMap != 0); + } + // Find lowest free subregion + listIndex = GetListIndex(memoryClass, VMA_BITSCAN_LSB(innerFreeMap)); + VMA_ASSERT(m_FreeList[listIndex]); + return m_FreeList[listIndex]; +} + +bool VmaBlockMetadata_TLSF::CheckBlock( + Block& block, + uint32_t listIndex, + VkDeviceSize allocSize, + VkDeviceSize allocAlignment, + VmaSuballocationType allocType, + VmaAllocationRequest* pAllocationRequest) +{ + VMA_ASSERT(block.IsFree() && "Block is already taken!"); + + VkDeviceSize alignedOffset = VmaAlignUp(block.offset, allocAlignment); + if (block.size < allocSize + alignedOffset - block.offset) + return false; + + // Check for granularity conflicts + if (!IsVirtual() && + m_GranularityHandler.CheckConflictAndAlignUp(alignedOffset, allocSize, block.offset, block.size, allocType)) + return false; + + // Alloc successful + pAllocationRequest->type = VmaAllocationRequestType::TLSF; + pAllocationRequest->allocHandle = (VmaAllocHandle)█ + pAllocationRequest->size = allocSize - GetDebugMargin(); + pAllocationRequest->customData = (void*)allocType; + pAllocationRequest->algorithmData = alignedOffset; + + // Place block at the start of list if it's normal block + if (listIndex != m_ListsCount && block.PrevFree()) + { + block.PrevFree()->NextFree() = block.NextFree(); + if (block.NextFree()) + block.NextFree()->PrevFree() = block.PrevFree(); + block.PrevFree() = VMA_NULL; + block.NextFree() = m_FreeList[listIndex]; + m_FreeList[listIndex] = █ + if (block.NextFree()) + block.NextFree()->PrevFree() = █ + } + + return true; +} +#endif // _VMA_BLOCK_METADATA_TLSF_FUNCTIONS +#endif // _VMA_BLOCK_METADATA_TLSF + +#ifndef _VMA_BLOCK_VECTOR +/* +Sequence of VmaDeviceMemoryBlock. Represents memory blocks allocated for a specific +Vulkan memory type. + +Synchronized internally with a mutex. +*/ +class VmaBlockVector +{ + friend struct VmaDefragmentationContext_T; + VMA_CLASS_NO_COPY_NO_MOVE(VmaBlockVector) +public: + VmaBlockVector( + VmaAllocator hAllocator, + VmaPool hParentPool, + uint32_t memoryTypeIndex, + VkDeviceSize preferredBlockSize, + size_t minBlockCount, + size_t maxBlockCount, + VkDeviceSize bufferImageGranularity, + bool explicitBlockSize, + uint32_t algorithm, + float priority, + VkDeviceSize minAllocationAlignment, + void* pMemoryAllocateNext); + ~VmaBlockVector(); + + VmaAllocator GetAllocator() const { return m_hAllocator; } + VmaPool GetParentPool() const { return m_hParentPool; } + bool IsCustomPool() const { return m_hParentPool != VMA_NULL; } + uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; } + VkDeviceSize GetPreferredBlockSize() const { return m_PreferredBlockSize; } + VkDeviceSize GetBufferImageGranularity() const { return m_BufferImageGranularity; } + uint32_t GetAlgorithm() const { return m_Algorithm; } + bool HasExplicitBlockSize() const { return m_ExplicitBlockSize; } + float GetPriority() const { return m_Priority; } + const void* GetAllocationNextPtr() const { return m_pMemoryAllocateNext; } + // To be used only while the m_Mutex is locked. Used during defragmentation. + size_t GetBlockCount() const { return m_Blocks.size(); } + // To be used only while the m_Mutex is locked. Used during defragmentation. + VmaDeviceMemoryBlock* GetBlock(size_t index) const { return m_Blocks[index]; } + VMA_RW_MUTEX &GetMutex() { return m_Mutex; } + + VkResult CreateMinBlocks(); + void AddStatistics(VmaStatistics& inoutStats); + void AddDetailedStatistics(VmaDetailedStatistics& inoutStats); + bool IsEmpty(); + bool IsCorruptionDetectionEnabled() const; + + VkResult Allocate( + VkDeviceSize size, + VkDeviceSize alignment, + const VmaAllocationCreateInfo& createInfo, + VmaSuballocationType suballocType, + size_t allocationCount, + VmaAllocation* pAllocations); + + void Free(const VmaAllocation hAllocation); + +#if VMA_STATS_STRING_ENABLED + void PrintDetailedMap(class VmaJsonWriter& json); +#endif + + VkResult CheckCorruption(); + +private: + const VmaAllocator m_hAllocator; + const VmaPool m_hParentPool; + const uint32_t m_MemoryTypeIndex; + const VkDeviceSize m_PreferredBlockSize; + const size_t m_MinBlockCount; + const size_t m_MaxBlockCount; + const VkDeviceSize m_BufferImageGranularity; + const bool m_ExplicitBlockSize; + const uint32_t m_Algorithm; + const float m_Priority; + const VkDeviceSize m_MinAllocationAlignment; + + void* const m_pMemoryAllocateNext; + VMA_RW_MUTEX m_Mutex; + // Incrementally sorted by sumFreeSize, ascending. + VmaVector> m_Blocks; + uint32_t m_NextBlockId; + bool m_IncrementalSort = true; + + void SetIncrementalSort(bool val) { m_IncrementalSort = val; } + + VkDeviceSize CalcMaxBlockSize() const; + // Finds and removes given block from vector. + void Remove(VmaDeviceMemoryBlock* pBlock); + // Performs single step in sorting m_Blocks. They may not be fully sorted + // after this call. + void IncrementallySortBlocks(); + void SortByFreeSize(); + + VkResult AllocatePage( + VkDeviceSize size, + VkDeviceSize alignment, + const VmaAllocationCreateInfo& createInfo, + VmaSuballocationType suballocType, + VmaAllocation* pAllocation); + + VkResult AllocateFromBlock( + VmaDeviceMemoryBlock* pBlock, + VkDeviceSize size, + VkDeviceSize alignment, + VmaAllocationCreateFlags allocFlags, + void* pUserData, + VmaSuballocationType suballocType, + uint32_t strategy, + VmaAllocation* pAllocation); + + VkResult CommitAllocationRequest( + VmaAllocationRequest& allocRequest, + VmaDeviceMemoryBlock* pBlock, + VkDeviceSize alignment, + VmaAllocationCreateFlags allocFlags, + void* pUserData, + VmaSuballocationType suballocType, + VmaAllocation* pAllocation); + + VkResult CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIndex); + bool HasEmptyBlock(); +}; +#endif // _VMA_BLOCK_VECTOR + +#ifndef _VMA_DEFRAGMENTATION_CONTEXT +struct VmaDefragmentationContext_T +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaDefragmentationContext_T) +public: + VmaDefragmentationContext_T( + VmaAllocator hAllocator, + const VmaDefragmentationInfo& info); + ~VmaDefragmentationContext_T(); + + void GetStats(VmaDefragmentationStats& outStats) { outStats = m_GlobalStats; } + + VkResult DefragmentPassBegin(VmaDefragmentationPassMoveInfo& moveInfo); + VkResult DefragmentPassEnd(VmaDefragmentationPassMoveInfo& moveInfo); + +private: + // Max number of allocations to ignore due to size constraints before ending single pass + static const uint8_t MAX_ALLOCS_TO_IGNORE = 16; + enum class CounterStatus { Pass, Ignore, End }; + + struct FragmentedBlock + { + uint32_t data; + VmaDeviceMemoryBlock* block; + }; + struct StateBalanced + { + VkDeviceSize avgFreeSize = 0; + VkDeviceSize avgAllocSize = UINT64_MAX; + }; + struct StateExtensive + { + enum class Operation : uint8_t + { + FindFreeBlockBuffer, FindFreeBlockTexture, FindFreeBlockAll, + MoveBuffers, MoveTextures, MoveAll, + Cleanup, Done + }; + + Operation operation = Operation::FindFreeBlockTexture; + size_t firstFreeBlock = SIZE_MAX; + }; + struct MoveAllocationData + { + VkDeviceSize size; + VkDeviceSize alignment; + VmaSuballocationType type; + VmaAllocationCreateFlags flags; + VmaDefragmentationMove move = {}; + }; + + const VkDeviceSize m_MaxPassBytes; + const uint32_t m_MaxPassAllocations; + const PFN_vmaCheckDefragmentationBreakFunction m_BreakCallback; + void* m_BreakCallbackUserData; + + VmaStlAllocator m_MoveAllocator; + VmaVector> m_Moves; + + uint8_t m_IgnoredAllocs = 0; + uint32_t m_Algorithm; + uint32_t m_BlockVectorCount; + VmaBlockVector* m_PoolBlockVector; + VmaBlockVector** m_pBlockVectors; + size_t m_ImmovableBlockCount = 0; + VmaDefragmentationStats m_GlobalStats = { 0 }; + VmaDefragmentationStats m_PassStats = { 0 }; + void* m_AlgorithmState = VMA_NULL; + + static MoveAllocationData GetMoveData(VmaAllocHandle handle, VmaBlockMetadata* metadata); + CounterStatus CheckCounters(VkDeviceSize bytes); + bool IncrementCounters(VkDeviceSize bytes); + bool ReallocWithinBlock(VmaBlockVector& vector, VmaDeviceMemoryBlock* block); + bool AllocInOtherBlock(size_t start, size_t end, MoveAllocationData& data, VmaBlockVector& vector); + + bool ComputeDefragmentation(VmaBlockVector& vector, size_t index); + bool ComputeDefragmentation_Fast(VmaBlockVector& vector); + bool ComputeDefragmentation_Balanced(VmaBlockVector& vector, size_t index, bool update); + bool ComputeDefragmentation_Full(VmaBlockVector& vector); + bool ComputeDefragmentation_Extensive(VmaBlockVector& vector, size_t index); + + void UpdateVectorStatistics(VmaBlockVector& vector, StateBalanced& state); + bool MoveDataToFreeBlocks(VmaSuballocationType currentType, + VmaBlockVector& vector, size_t firstFreeBlock, + bool& texturePresent, bool& bufferPresent, bool& otherPresent); +}; +#endif // _VMA_DEFRAGMENTATION_CONTEXT + +#ifndef _VMA_POOL_T +struct VmaPool_T +{ + friend struct VmaPoolListItemTraits; + VMA_CLASS_NO_COPY_NO_MOVE(VmaPool_T) +public: + VmaBlockVector m_BlockVector; + VmaDedicatedAllocationList m_DedicatedAllocations; + + VmaPool_T( + VmaAllocator hAllocator, + const VmaPoolCreateInfo& createInfo, + VkDeviceSize preferredBlockSize); + ~VmaPool_T(); + + uint32_t GetId() const { return m_Id; } + void SetId(uint32_t id) { VMA_ASSERT(m_Id == 0); m_Id = id; } + + const char* GetName() const { return m_Name; } + void SetName(const char* pName); + +#if VMA_STATS_STRING_ENABLED + //void PrintDetailedMap(class VmaStringBuilder& sb); +#endif + +private: + uint32_t m_Id; + char* m_Name; + VmaPool_T* m_PrevPool = VMA_NULL; + VmaPool_T* m_NextPool = VMA_NULL; +}; + +struct VmaPoolListItemTraits +{ + typedef VmaPool_T ItemType; + + static ItemType* GetPrev(const ItemType* item) { return item->m_PrevPool; } + static ItemType* GetNext(const ItemType* item) { return item->m_NextPool; } + static ItemType*& AccessPrev(ItemType* item) { return item->m_PrevPool; } + static ItemType*& AccessNext(ItemType* item) { return item->m_NextPool; } +}; +#endif // _VMA_POOL_T + +#ifndef _VMA_CURRENT_BUDGET_DATA +struct VmaCurrentBudgetData +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaCurrentBudgetData) +public: + + VMA_ATOMIC_UINT32 m_BlockCount[VK_MAX_MEMORY_HEAPS]; + VMA_ATOMIC_UINT32 m_AllocationCount[VK_MAX_MEMORY_HEAPS]; + VMA_ATOMIC_UINT64 m_BlockBytes[VK_MAX_MEMORY_HEAPS]; + VMA_ATOMIC_UINT64 m_AllocationBytes[VK_MAX_MEMORY_HEAPS]; + +#if VMA_MEMORY_BUDGET + VMA_ATOMIC_UINT32 m_OperationsSinceBudgetFetch; + VMA_RW_MUTEX m_BudgetMutex; + uint64_t m_VulkanUsage[VK_MAX_MEMORY_HEAPS]; + uint64_t m_VulkanBudget[VK_MAX_MEMORY_HEAPS]; + uint64_t m_BlockBytesAtBudgetFetch[VK_MAX_MEMORY_HEAPS]; +#endif // VMA_MEMORY_BUDGET + + VmaCurrentBudgetData(); + + void AddAllocation(uint32_t heapIndex, VkDeviceSize allocationSize); + void RemoveAllocation(uint32_t heapIndex, VkDeviceSize allocationSize); +}; + +#ifndef _VMA_CURRENT_BUDGET_DATA_FUNCTIONS +VmaCurrentBudgetData::VmaCurrentBudgetData() +{ + for (uint32_t heapIndex = 0; heapIndex < VK_MAX_MEMORY_HEAPS; ++heapIndex) + { + m_BlockCount[heapIndex] = 0; + m_AllocationCount[heapIndex] = 0; + m_BlockBytes[heapIndex] = 0; + m_AllocationBytes[heapIndex] = 0; +#if VMA_MEMORY_BUDGET + m_VulkanUsage[heapIndex] = 0; + m_VulkanBudget[heapIndex] = 0; + m_BlockBytesAtBudgetFetch[heapIndex] = 0; +#endif + } + +#if VMA_MEMORY_BUDGET + m_OperationsSinceBudgetFetch = 0; +#endif +} + +void VmaCurrentBudgetData::AddAllocation(uint32_t heapIndex, VkDeviceSize allocationSize) +{ + m_AllocationBytes[heapIndex] += allocationSize; + ++m_AllocationCount[heapIndex]; +#if VMA_MEMORY_BUDGET + ++m_OperationsSinceBudgetFetch; +#endif +} + +void VmaCurrentBudgetData::RemoveAllocation(uint32_t heapIndex, VkDeviceSize allocationSize) +{ + VMA_ASSERT(m_AllocationBytes[heapIndex] >= allocationSize); + m_AllocationBytes[heapIndex] -= allocationSize; + VMA_ASSERT(m_AllocationCount[heapIndex] > 0); + --m_AllocationCount[heapIndex]; +#if VMA_MEMORY_BUDGET + ++m_OperationsSinceBudgetFetch; +#endif +} +#endif // _VMA_CURRENT_BUDGET_DATA_FUNCTIONS +#endif // _VMA_CURRENT_BUDGET_DATA + +#ifndef _VMA_ALLOCATION_OBJECT_ALLOCATOR +/* +Thread-safe wrapper over VmaPoolAllocator free list, for allocation of VmaAllocation_T objects. +*/ +class VmaAllocationObjectAllocator +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaAllocationObjectAllocator) +public: + VmaAllocationObjectAllocator(const VkAllocationCallbacks* pAllocationCallbacks) + : m_Allocator(pAllocationCallbacks, 1024) {} + + template VmaAllocation Allocate(Types&&... args); + void Free(VmaAllocation hAlloc); + +private: + VMA_MUTEX m_Mutex; + VmaPoolAllocator m_Allocator; +}; + +template +VmaAllocation VmaAllocationObjectAllocator::Allocate(Types&&... args) +{ + VmaMutexLock mutexLock(m_Mutex); + return m_Allocator.Alloc(std::forward(args)...); +} + +void VmaAllocationObjectAllocator::Free(VmaAllocation hAlloc) +{ + VmaMutexLock mutexLock(m_Mutex); + m_Allocator.Free(hAlloc); +} +#endif // _VMA_ALLOCATION_OBJECT_ALLOCATOR + +#ifndef _VMA_VIRTUAL_BLOCK_T +struct VmaVirtualBlock_T +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaVirtualBlock_T) +public: + const bool m_AllocationCallbacksSpecified; + const VkAllocationCallbacks m_AllocationCallbacks; + + VmaVirtualBlock_T(const VmaVirtualBlockCreateInfo& createInfo); + ~VmaVirtualBlock_T(); + + VkResult Init() { return VK_SUCCESS; } + bool IsEmpty() const { return m_Metadata->IsEmpty(); } + void Free(VmaVirtualAllocation allocation) { m_Metadata->Free((VmaAllocHandle)allocation); } + void SetAllocationUserData(VmaVirtualAllocation allocation, void* userData) { m_Metadata->SetAllocationUserData((VmaAllocHandle)allocation, userData); } + void Clear() { m_Metadata->Clear(); } + + const VkAllocationCallbacks* GetAllocationCallbacks() const; + void GetAllocationInfo(VmaVirtualAllocation allocation, VmaVirtualAllocationInfo& outInfo); + VkResult Allocate(const VmaVirtualAllocationCreateInfo& createInfo, VmaVirtualAllocation& outAllocation, + VkDeviceSize* outOffset); + void GetStatistics(VmaStatistics& outStats) const; + void CalculateDetailedStatistics(VmaDetailedStatistics& outStats) const; +#if VMA_STATS_STRING_ENABLED + void BuildStatsString(bool detailedMap, VmaStringBuilder& sb) const; +#endif + +private: + VmaBlockMetadata* m_Metadata; +}; + +#ifndef _VMA_VIRTUAL_BLOCK_T_FUNCTIONS +VmaVirtualBlock_T::VmaVirtualBlock_T(const VmaVirtualBlockCreateInfo& createInfo) + : m_AllocationCallbacksSpecified(createInfo.pAllocationCallbacks != VMA_NULL), + m_AllocationCallbacks(createInfo.pAllocationCallbacks != VMA_NULL ? *createInfo.pAllocationCallbacks : VmaEmptyAllocationCallbacks) +{ + const uint32_t algorithm = createInfo.flags & VMA_VIRTUAL_BLOCK_CREATE_ALGORITHM_MASK; + switch (algorithm) + { + case 0: + m_Metadata = vma_new(GetAllocationCallbacks(), VmaBlockMetadata_TLSF)(VK_NULL_HANDLE, 1, true); + break; + case VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT: + m_Metadata = vma_new(GetAllocationCallbacks(), VmaBlockMetadata_Linear)(VK_NULL_HANDLE, 1, true); + break; + default: + VMA_ASSERT(0); + m_Metadata = vma_new(GetAllocationCallbacks(), VmaBlockMetadata_TLSF)(VK_NULL_HANDLE, 1, true); + } + + m_Metadata->Init(createInfo.size); +} + +VmaVirtualBlock_T::~VmaVirtualBlock_T() +{ + // Define macro VMA_DEBUG_LOG_FORMAT or more specialized VMA_LEAK_LOG_FORMAT + // to receive the list of the unfreed allocations. + if (!m_Metadata->IsEmpty()) + m_Metadata->DebugLogAllAllocations(); + // This is the most important assert in the entire library. + // Hitting it means you have some memory leak - unreleased virtual allocations. + VMA_ASSERT_LEAK(m_Metadata->IsEmpty() && "Some virtual allocations were not freed before destruction of this virtual block!"); + + vma_delete(GetAllocationCallbacks(), m_Metadata); +} + +const VkAllocationCallbacks* VmaVirtualBlock_T::GetAllocationCallbacks() const +{ + return m_AllocationCallbacksSpecified ? &m_AllocationCallbacks : VMA_NULL; +} + +void VmaVirtualBlock_T::GetAllocationInfo(VmaVirtualAllocation allocation, VmaVirtualAllocationInfo& outInfo) +{ + m_Metadata->GetAllocationInfo((VmaAllocHandle)allocation, outInfo); +} + +VkResult VmaVirtualBlock_T::Allocate(const VmaVirtualAllocationCreateInfo& createInfo, VmaVirtualAllocation& outAllocation, + VkDeviceSize* outOffset) +{ + VmaAllocationRequest request = {}; + if (m_Metadata->CreateAllocationRequest( + createInfo.size, // allocSize + VMA_MAX(createInfo.alignment, (VkDeviceSize)1), // allocAlignment + (createInfo.flags & VMA_VIRTUAL_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0, // upperAddress + VMA_SUBALLOCATION_TYPE_UNKNOWN, // allocType - unimportant + createInfo.flags & VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MASK, // strategy + &request)) + { + m_Metadata->Alloc(request, + VMA_SUBALLOCATION_TYPE_UNKNOWN, // type - unimportant + createInfo.pUserData); + outAllocation = (VmaVirtualAllocation)request.allocHandle; + if(outOffset) + *outOffset = m_Metadata->GetAllocationOffset(request.allocHandle); + return VK_SUCCESS; + } + outAllocation = (VmaVirtualAllocation)VK_NULL_HANDLE; + if (outOffset) + *outOffset = UINT64_MAX; + return VK_ERROR_OUT_OF_DEVICE_MEMORY; +} + +void VmaVirtualBlock_T::GetStatistics(VmaStatistics& outStats) const +{ + VmaClearStatistics(outStats); + m_Metadata->AddStatistics(outStats); +} + +void VmaVirtualBlock_T::CalculateDetailedStatistics(VmaDetailedStatistics& outStats) const +{ + VmaClearDetailedStatistics(outStats); + m_Metadata->AddDetailedStatistics(outStats); +} + +#if VMA_STATS_STRING_ENABLED +void VmaVirtualBlock_T::BuildStatsString(bool detailedMap, VmaStringBuilder& sb) const +{ + VmaJsonWriter json(GetAllocationCallbacks(), sb); + json.BeginObject(); + + VmaDetailedStatistics stats; + CalculateDetailedStatistics(stats); + + json.WriteString("Stats"); + VmaPrintDetailedStatistics(json, stats); + + if (detailedMap) + { + json.WriteString("Details"); + json.BeginObject(); + m_Metadata->PrintDetailedMap(json); + json.EndObject(); + } + + json.EndObject(); +} +#endif // VMA_STATS_STRING_ENABLED +#endif // _VMA_VIRTUAL_BLOCK_T_FUNCTIONS +#endif // _VMA_VIRTUAL_BLOCK_T + + +// Main allocator object. +struct VmaAllocator_T +{ + VMA_CLASS_NO_COPY_NO_MOVE(VmaAllocator_T) +public: + const bool m_UseMutex; + const uint32_t m_VulkanApiVersion; + bool m_UseKhrDedicatedAllocation; // Can be set only if m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0). + bool m_UseKhrBindMemory2; // Can be set only if m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0). + bool m_UseExtMemoryBudget; + bool m_UseAmdDeviceCoherentMemory; + bool m_UseKhrBufferDeviceAddress; + bool m_UseExtMemoryPriority; + bool m_UseKhrMaintenance4; + bool m_UseKhrMaintenance5; + bool m_UseKhrExternalMemoryWin32; + const VkDevice m_hDevice; + const VkInstance m_hInstance; + const bool m_AllocationCallbacksSpecified; + const VkAllocationCallbacks m_AllocationCallbacks; + VmaDeviceMemoryCallbacks m_DeviceMemoryCallbacks; + VmaAllocationObjectAllocator m_AllocationObjectAllocator; + + // Each bit (1 << i) is set if HeapSizeLimit is enabled for that heap, so cannot allocate more than the heap size. + uint32_t m_HeapSizeLimitMask; + + VkPhysicalDeviceProperties m_PhysicalDeviceProperties; + VkPhysicalDeviceMemoryProperties m_MemProps; + + // Default pools. + VmaBlockVector* m_pBlockVectors[VK_MAX_MEMORY_TYPES]; + VmaDedicatedAllocationList m_DedicatedAllocations[VK_MAX_MEMORY_TYPES]; + + VmaCurrentBudgetData m_Budget; + VMA_ATOMIC_UINT32 m_DeviceMemoryCount; // Total number of VkDeviceMemory objects. + + VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo); + VkResult Init(const VmaAllocatorCreateInfo* pCreateInfo); + ~VmaAllocator_T(); + + const VkAllocationCallbacks* GetAllocationCallbacks() const + { + return m_AllocationCallbacksSpecified ? &m_AllocationCallbacks : VMA_NULL; + } + const VmaVulkanFunctions& GetVulkanFunctions() const + { + return m_VulkanFunctions; + } + + VkPhysicalDevice GetPhysicalDevice() const { return m_PhysicalDevice; } + + VkDeviceSize GetBufferImageGranularity() const + { + return VMA_MAX( + static_cast(VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY), + m_PhysicalDeviceProperties.limits.bufferImageGranularity); + } + + uint32_t GetMemoryHeapCount() const { return m_MemProps.memoryHeapCount; } + uint32_t GetMemoryTypeCount() const { return m_MemProps.memoryTypeCount; } + + uint32_t MemoryTypeIndexToHeapIndex(uint32_t memTypeIndex) const + { + VMA_ASSERT(memTypeIndex < m_MemProps.memoryTypeCount); + return m_MemProps.memoryTypes[memTypeIndex].heapIndex; + } + // True when specific memory type is HOST_VISIBLE but not HOST_COHERENT. + bool IsMemoryTypeNonCoherent(uint32_t memTypeIndex) const + { + return (m_MemProps.memoryTypes[memTypeIndex].propertyFlags & (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) == + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; + } + // Minimum alignment for all allocations in specific memory type. + VkDeviceSize GetMemoryTypeMinAlignment(uint32_t memTypeIndex) const + { + return IsMemoryTypeNonCoherent(memTypeIndex) ? + VMA_MAX((VkDeviceSize)VMA_MIN_ALIGNMENT, m_PhysicalDeviceProperties.limits.nonCoherentAtomSize) : + (VkDeviceSize)VMA_MIN_ALIGNMENT; + } + + bool IsIntegratedGpu() const + { + return m_PhysicalDeviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; + } + + uint32_t GetGlobalMemoryTypeBits() const { return m_GlobalMemoryTypeBits; } + + void GetBufferMemoryRequirements( + VkBuffer hBuffer, + VkMemoryRequirements& memReq, + bool& requiresDedicatedAllocation, + bool& prefersDedicatedAllocation) const; + void GetImageMemoryRequirements( + VkImage hImage, + VkMemoryRequirements& memReq, + bool& requiresDedicatedAllocation, + bool& prefersDedicatedAllocation) const; + VkResult FindMemoryTypeIndex( + uint32_t memoryTypeBits, + const VmaAllocationCreateInfo* pAllocationCreateInfo, + VmaBufferImageUsage bufImgUsage, + uint32_t* pMemoryTypeIndex) const; + + // Main allocation function. + VkResult AllocateMemory( + const VkMemoryRequirements& vkMemReq, + bool requiresDedicatedAllocation, + bool prefersDedicatedAllocation, + VkBuffer dedicatedBuffer, + VkImage dedicatedImage, + VmaBufferImageUsage dedicatedBufferImageUsage, + const VmaAllocationCreateInfo& createInfo, + VmaSuballocationType suballocType, + size_t allocationCount, + VmaAllocation* pAllocations); + + // Main deallocation function. + void FreeMemory( + size_t allocationCount, + const VmaAllocation* pAllocations); + + void CalculateStatistics(VmaTotalStatistics* pStats); + + void GetHeapBudgets( + VmaBudget* outBudgets, uint32_t firstHeap, uint32_t heapCount); + +#if VMA_STATS_STRING_ENABLED + void PrintDetailedMap(class VmaJsonWriter& json); +#endif + + void GetAllocationInfo(VmaAllocation hAllocation, VmaAllocationInfo* pAllocationInfo); + void GetAllocationInfo2(VmaAllocation hAllocation, VmaAllocationInfo2* pAllocationInfo); + + VkResult CreatePool(const VmaPoolCreateInfo* pCreateInfo, VmaPool* pPool); + void DestroyPool(VmaPool pool); + void GetPoolStatistics(VmaPool pool, VmaStatistics* pPoolStats); + void CalculatePoolStatistics(VmaPool pool, VmaDetailedStatistics* pPoolStats); + + void SetCurrentFrameIndex(uint32_t frameIndex); + uint32_t GetCurrentFrameIndex() const { return m_CurrentFrameIndex.load(); } + + VkResult CheckPoolCorruption(VmaPool hPool); + VkResult CheckCorruption(uint32_t memoryTypeBits); + + // Call to Vulkan function vkAllocateMemory with accompanying bookkeeping. + VkResult AllocateVulkanMemory(const VkMemoryAllocateInfo* pAllocateInfo, VkDeviceMemory* pMemory); + // Call to Vulkan function vkFreeMemory with accompanying bookkeeping. + void FreeVulkanMemory(uint32_t memoryType, VkDeviceSize size, VkDeviceMemory hMemory); + // Call to Vulkan function vkBindBufferMemory or vkBindBufferMemory2KHR. + VkResult BindVulkanBuffer( + VkDeviceMemory memory, + VkDeviceSize memoryOffset, + VkBuffer buffer, + const void* pNext); + // Call to Vulkan function vkBindImageMemory or vkBindImageMemory2KHR. + VkResult BindVulkanImage( + VkDeviceMemory memory, + VkDeviceSize memoryOffset, + VkImage image, + const void* pNext); + + VkResult Map(VmaAllocation hAllocation, void** ppData); + void Unmap(VmaAllocation hAllocation); + + VkResult BindBufferMemory( + VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkBuffer hBuffer, + const void* pNext); + VkResult BindImageMemory( + VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkImage hImage, + const void* pNext); + + VkResult FlushOrInvalidateAllocation( + VmaAllocation hAllocation, + VkDeviceSize offset, VkDeviceSize size, + VMA_CACHE_OPERATION op); + VkResult FlushOrInvalidateAllocations( + uint32_t allocationCount, + const VmaAllocation* allocations, + const VkDeviceSize* offsets, const VkDeviceSize* sizes, + VMA_CACHE_OPERATION op); + + VkResult CopyMemoryToAllocation( + const void* pSrcHostPointer, + VmaAllocation dstAllocation, + VkDeviceSize dstAllocationLocalOffset, + VkDeviceSize size); + VkResult CopyAllocationToMemory( + VmaAllocation srcAllocation, + VkDeviceSize srcAllocationLocalOffset, + void* pDstHostPointer, + VkDeviceSize size); + + void FillAllocation(const VmaAllocation hAllocation, uint8_t pattern); + + /* + Returns bit mask of memory types that can support defragmentation on GPU as + they support creation of required buffer for copy operations. + */ + uint32_t GetGpuDefragmentationMemoryTypeBits(); + +#if VMA_EXTERNAL_MEMORY + VkExternalMemoryHandleTypeFlagsKHR GetExternalMemoryHandleTypeFlags(uint32_t memTypeIndex) const + { + return m_TypeExternalMemoryHandleTypes[memTypeIndex]; + } +#endif // #if VMA_EXTERNAL_MEMORY + +private: + VkDeviceSize m_PreferredLargeHeapBlockSize; + + VkPhysicalDevice m_PhysicalDevice; + VMA_ATOMIC_UINT32 m_CurrentFrameIndex; + VMA_ATOMIC_UINT32 m_GpuDefragmentationMemoryTypeBits; // UINT32_MAX means uninitialized. +#if VMA_EXTERNAL_MEMORY + VkExternalMemoryHandleTypeFlagsKHR m_TypeExternalMemoryHandleTypes[VK_MAX_MEMORY_TYPES]; +#endif // #if VMA_EXTERNAL_MEMORY + + VMA_RW_MUTEX m_PoolsMutex; + typedef VmaIntrusiveLinkedList PoolList; + // Protected by m_PoolsMutex. + PoolList m_Pools; + uint32_t m_NextPoolId; + + VmaVulkanFunctions m_VulkanFunctions; + + // Global bit mask AND-ed with any memoryTypeBits to disallow certain memory types. + uint32_t m_GlobalMemoryTypeBits; + + void ImportVulkanFunctions(const VmaVulkanFunctions* pVulkanFunctions); + +#if VMA_STATIC_VULKAN_FUNCTIONS == 1 + void ImportVulkanFunctions_Static(); +#endif + + void ImportVulkanFunctions_Custom(const VmaVulkanFunctions* pVulkanFunctions); + +#if VMA_DYNAMIC_VULKAN_FUNCTIONS == 1 + void ImportVulkanFunctions_Dynamic(); +#endif + + void ValidateVulkanFunctions(); + + VkDeviceSize CalcPreferredBlockSize(uint32_t memTypeIndex); + + VkResult AllocateMemoryOfType( + VmaPool pool, + VkDeviceSize size, + VkDeviceSize alignment, + bool dedicatedPreferred, + VkBuffer dedicatedBuffer, + VkImage dedicatedImage, + VmaBufferImageUsage dedicatedBufferImageUsage, + const VmaAllocationCreateInfo& createInfo, + uint32_t memTypeIndex, + VmaSuballocationType suballocType, + VmaDedicatedAllocationList& dedicatedAllocations, + VmaBlockVector& blockVector, + size_t allocationCount, + VmaAllocation* pAllocations); + + // Helper function only to be used inside AllocateDedicatedMemory. + VkResult AllocateDedicatedMemoryPage( + VmaPool pool, + VkDeviceSize size, + VmaSuballocationType suballocType, + uint32_t memTypeIndex, + const VkMemoryAllocateInfo& allocInfo, + bool map, + bool isUserDataString, + bool isMappingAllowed, + void* pUserData, + VmaAllocation* pAllocation); + + // Allocates and registers new VkDeviceMemory specifically for dedicated allocations. + VkResult AllocateDedicatedMemory( + VmaPool pool, + VkDeviceSize size, + VmaSuballocationType suballocType, + VmaDedicatedAllocationList& dedicatedAllocations, + uint32_t memTypeIndex, + bool map, + bool isUserDataString, + bool isMappingAllowed, + bool canAliasMemory, + void* pUserData, + float priority, + VkBuffer dedicatedBuffer, + VkImage dedicatedImage, + VmaBufferImageUsage dedicatedBufferImageUsage, + size_t allocationCount, + VmaAllocation* pAllocations, + const void* pNextChain = VMA_NULL); + + void FreeDedicatedMemory(const VmaAllocation allocation); + + VkResult CalcMemTypeParams( + VmaAllocationCreateInfo& outCreateInfo, + uint32_t memTypeIndex, + VkDeviceSize size, + size_t allocationCount); + VkResult CalcAllocationParams( + VmaAllocationCreateInfo& outCreateInfo, + bool dedicatedRequired, + bool dedicatedPreferred); + + /* + Calculates and returns bit mask of memory types that can support defragmentation + on GPU as they support creation of required buffer for copy operations. + */ + uint32_t CalculateGpuDefragmentationMemoryTypeBits() const; + uint32_t CalculateGlobalMemoryTypeBits() const; + + bool GetFlushOrInvalidateRange( + VmaAllocation allocation, + VkDeviceSize offset, VkDeviceSize size, + VkMappedMemoryRange& outRange) const; + +#if VMA_MEMORY_BUDGET + void UpdateVulkanBudget(); +#endif // #if VMA_MEMORY_BUDGET +}; + + +#ifndef _VMA_MEMORY_FUNCTIONS +static void* VmaMalloc(VmaAllocator hAllocator, size_t size, size_t alignment) +{ + return VmaMalloc(&hAllocator->m_AllocationCallbacks, size, alignment); +} + +static void VmaFree(VmaAllocator hAllocator, void* ptr) +{ + VmaFree(&hAllocator->m_AllocationCallbacks, ptr); +} + +template +static T* VmaAllocate(VmaAllocator hAllocator) +{ + return (T*)VmaMalloc(hAllocator, sizeof(T), VMA_ALIGN_OF(T)); +} + +template +static T* VmaAllocateArray(VmaAllocator hAllocator, size_t count) +{ + return (T*)VmaMalloc(hAllocator, sizeof(T) * count, VMA_ALIGN_OF(T)); +} + +template +static void vma_delete(VmaAllocator hAllocator, T* ptr) +{ + if(ptr != VMA_NULL) + { + ptr->~T(); + VmaFree(hAllocator, ptr); + } +} + +template +static void vma_delete_array(VmaAllocator hAllocator, T* ptr, size_t count) +{ + if(ptr != VMA_NULL) + { + for(size_t i = count; i--; ) + ptr[i].~T(); + VmaFree(hAllocator, ptr); + } +} +#endif // _VMA_MEMORY_FUNCTIONS + +#ifndef _VMA_DEVICE_MEMORY_BLOCK_FUNCTIONS +VmaDeviceMemoryBlock::VmaDeviceMemoryBlock(VmaAllocator hAllocator) + : m_pMetadata(VMA_NULL), + m_MemoryTypeIndex(UINT32_MAX), + m_Id(0), + m_hMemory(VK_NULL_HANDLE), + m_MapCount(0), + m_pMappedData(VMA_NULL){} + +VmaDeviceMemoryBlock::~VmaDeviceMemoryBlock() +{ + VMA_ASSERT_LEAK(m_MapCount == 0 && "VkDeviceMemory block is being destroyed while it is still mapped."); + VMA_ASSERT_LEAK(m_hMemory == VK_NULL_HANDLE); +} + +void VmaDeviceMemoryBlock::Init( + VmaAllocator hAllocator, + VmaPool hParentPool, + uint32_t newMemoryTypeIndex, + VkDeviceMemory newMemory, + VkDeviceSize newSize, + uint32_t id, + uint32_t algorithm, + VkDeviceSize bufferImageGranularity) +{ + VMA_ASSERT(m_hMemory == VK_NULL_HANDLE); + + m_hParentPool = hParentPool; + m_MemoryTypeIndex = newMemoryTypeIndex; + m_Id = id; + m_hMemory = newMemory; + + switch (algorithm) + { + case 0: + m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_TLSF)(hAllocator->GetAllocationCallbacks(), + bufferImageGranularity, false); // isVirtual + break; + case VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT: + m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_Linear)(hAllocator->GetAllocationCallbacks(), + bufferImageGranularity, false); // isVirtual + break; + default: + VMA_ASSERT(0); + m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_TLSF)(hAllocator->GetAllocationCallbacks(), + bufferImageGranularity, false); // isVirtual + } + m_pMetadata->Init(newSize); +} + +void VmaDeviceMemoryBlock::Destroy(VmaAllocator allocator) +{ + // Define macro VMA_DEBUG_LOG_FORMAT or more specialized VMA_LEAK_LOG_FORMAT + // to receive the list of the unfreed allocations. + if (!m_pMetadata->IsEmpty()) + m_pMetadata->DebugLogAllAllocations(); + // This is the most important assert in the entire library. + // Hitting it means you have some memory leak - unreleased VmaAllocation objects. + VMA_ASSERT_LEAK(m_pMetadata->IsEmpty() && "Some allocations were not freed before destruction of this memory block!"); + + VMA_ASSERT_LEAK(m_hMemory != VK_NULL_HANDLE); + allocator->FreeVulkanMemory(m_MemoryTypeIndex, m_pMetadata->GetSize(), m_hMemory); + m_hMemory = VK_NULL_HANDLE; + + vma_delete(allocator, m_pMetadata); + m_pMetadata = VMA_NULL; +} + +void VmaDeviceMemoryBlock::PostAlloc(VmaAllocator hAllocator) +{ + VmaMutexLock lock(m_MapAndBindMutex, hAllocator->m_UseMutex); + m_MappingHysteresis.PostAlloc(); +} + +void VmaDeviceMemoryBlock::PostFree(VmaAllocator hAllocator) +{ + VmaMutexLock lock(m_MapAndBindMutex, hAllocator->m_UseMutex); + if(m_MappingHysteresis.PostFree()) + { + VMA_ASSERT(m_MappingHysteresis.GetExtraMapping() == 0); + if (m_MapCount == 0) + { + m_pMappedData = VMA_NULL; + (*hAllocator->GetVulkanFunctions().vkUnmapMemory)(hAllocator->m_hDevice, m_hMemory); + } + } +} + +bool VmaDeviceMemoryBlock::Validate() const +{ + VMA_VALIDATE((m_hMemory != VK_NULL_HANDLE) && + (m_pMetadata->GetSize() != 0)); + + return m_pMetadata->Validate(); +} + +VkResult VmaDeviceMemoryBlock::CheckCorruption(VmaAllocator hAllocator) +{ + void* pData = VMA_NULL; + VkResult res = Map(hAllocator, 1, &pData); + if (res != VK_SUCCESS) + { + return res; + } + + res = m_pMetadata->CheckCorruption(pData); + + Unmap(hAllocator, 1); + + return res; +} + +VkResult VmaDeviceMemoryBlock::Map(VmaAllocator hAllocator, uint32_t count, void** ppData) +{ + if (count == 0) + { + return VK_SUCCESS; + } + + VmaMutexLock lock(m_MapAndBindMutex, hAllocator->m_UseMutex); + const uint32_t oldTotalMapCount = m_MapCount + m_MappingHysteresis.GetExtraMapping(); + if (oldTotalMapCount != 0) + { + VMA_ASSERT(m_pMappedData != VMA_NULL); + m_MappingHysteresis.PostMap(); + m_MapCount += count; + if (ppData != VMA_NULL) + { + *ppData = m_pMappedData; + } + return VK_SUCCESS; + } + else + { + VkResult result = (*hAllocator->GetVulkanFunctions().vkMapMemory)( + hAllocator->m_hDevice, + m_hMemory, + 0, // offset + VK_WHOLE_SIZE, + 0, // flags + &m_pMappedData); + if (result == VK_SUCCESS) + { + VMA_ASSERT(m_pMappedData != VMA_NULL); + m_MappingHysteresis.PostMap(); + m_MapCount = count; + if (ppData != VMA_NULL) + { + *ppData = m_pMappedData; + } + } + return result; + } +} + +void VmaDeviceMemoryBlock::Unmap(VmaAllocator hAllocator, uint32_t count) +{ + if (count == 0) + { + return; + } + + VmaMutexLock lock(m_MapAndBindMutex, hAllocator->m_UseMutex); + if (m_MapCount >= count) + { + m_MapCount -= count; + const uint32_t totalMapCount = m_MapCount + m_MappingHysteresis.GetExtraMapping(); + if (totalMapCount == 0) + { + m_pMappedData = VMA_NULL; + (*hAllocator->GetVulkanFunctions().vkUnmapMemory)(hAllocator->m_hDevice, m_hMemory); + } + m_MappingHysteresis.PostUnmap(); + } + else + { + VMA_ASSERT(0 && "VkDeviceMemory block is being unmapped while it was not previously mapped."); + } +} + +VkResult VmaDeviceMemoryBlock::WriteMagicValueAfterAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize) +{ + VMA_ASSERT(VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_MARGIN % 4 == 0 && VMA_DEBUG_DETECT_CORRUPTION); + + void* pData; + VkResult res = Map(hAllocator, 1, &pData); + if (res != VK_SUCCESS) + { + return res; + } + + VmaWriteMagicValue(pData, allocOffset + allocSize); + + Unmap(hAllocator, 1); + return VK_SUCCESS; +} + +VkResult VmaDeviceMemoryBlock::ValidateMagicValueAfterAllocation(VmaAllocator hAllocator, VkDeviceSize allocOffset, VkDeviceSize allocSize) +{ + VMA_ASSERT(VMA_DEBUG_MARGIN > 0 && VMA_DEBUG_MARGIN % 4 == 0 && VMA_DEBUG_DETECT_CORRUPTION); + + void* pData; + VkResult res = Map(hAllocator, 1, &pData); + if (res != VK_SUCCESS) + { + return res; + } + + if (!VmaValidateMagicValue(pData, allocOffset + allocSize)) + { + VMA_ASSERT(0 && "MEMORY CORRUPTION DETECTED AFTER FREED ALLOCATION!"); + } + + Unmap(hAllocator, 1); + return VK_SUCCESS; +} + +VkResult VmaDeviceMemoryBlock::BindBufferMemory( + const VmaAllocator hAllocator, + const VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkBuffer hBuffer, + const void* pNext) +{ + VMA_ASSERT(hAllocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_BLOCK && + hAllocation->GetBlock() == this); + VMA_ASSERT(allocationLocalOffset < hAllocation->GetSize() && + "Invalid allocationLocalOffset. Did you forget that this offset is relative to the beginning of the allocation, not the whole memory block?"); + const VkDeviceSize memoryOffset = hAllocation->GetOffset() + allocationLocalOffset; + // This lock is important so that we don't call vkBind... and/or vkMap... simultaneously on the same VkDeviceMemory from multiple threads. + VmaMutexLock lock(m_MapAndBindMutex, hAllocator->m_UseMutex); + return hAllocator->BindVulkanBuffer(m_hMemory, memoryOffset, hBuffer, pNext); +} + +VkResult VmaDeviceMemoryBlock::BindImageMemory( + const VmaAllocator hAllocator, + const VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkImage hImage, + const void* pNext) +{ + VMA_ASSERT(hAllocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_BLOCK && + hAllocation->GetBlock() == this); + VMA_ASSERT(allocationLocalOffset < hAllocation->GetSize() && + "Invalid allocationLocalOffset. Did you forget that this offset is relative to the beginning of the allocation, not the whole memory block?"); + const VkDeviceSize memoryOffset = hAllocation->GetOffset() + allocationLocalOffset; + // This lock is important so that we don't call vkBind... and/or vkMap... simultaneously on the same VkDeviceMemory from multiple threads. + VmaMutexLock lock(m_MapAndBindMutex, hAllocator->m_UseMutex); + return hAllocator->BindVulkanImage(m_hMemory, memoryOffset, hImage, pNext); +} + +#if VMA_EXTERNAL_MEMORY_WIN32 +VkResult VmaDeviceMemoryBlock::CreateWin32Handle(const VmaAllocator hAllocator, PFN_vkGetMemoryWin32HandleKHR pvkGetMemoryWin32HandleKHR, HANDLE hTargetProcess, HANDLE* pHandle) noexcept +{ + VMA_ASSERT(pHandle); + return m_Handle.GetHandle(hAllocator->m_hDevice, m_hMemory, pvkGetMemoryWin32HandleKHR, hTargetProcess, hAllocator->m_UseMutex, pHandle); +} +#endif // VMA_EXTERNAL_MEMORY_WIN32 +#endif // _VMA_DEVICE_MEMORY_BLOCK_FUNCTIONS + +#ifndef _VMA_ALLOCATION_T_FUNCTIONS +VmaAllocation_T::VmaAllocation_T(bool mappingAllowed) + : m_Alignment{ 1 }, + m_Size{ 0 }, + m_pUserData{ VMA_NULL }, + m_pName{ VMA_NULL }, + m_MemoryTypeIndex{ 0 }, + m_Type{ (uint8_t)ALLOCATION_TYPE_NONE }, + m_SuballocationType{ (uint8_t)VMA_SUBALLOCATION_TYPE_UNKNOWN }, + m_MapCount{ 0 }, + m_Flags{ 0 } +{ + if(mappingAllowed) + m_Flags |= (uint8_t)FLAG_MAPPING_ALLOWED; +} + +VmaAllocation_T::~VmaAllocation_T() +{ + VMA_ASSERT_LEAK(m_MapCount == 0 && "Allocation was not unmapped before destruction."); + + // Check if owned string was freed. + VMA_ASSERT(m_pName == VMA_NULL); +} + +void VmaAllocation_T::InitBlockAllocation( + VmaDeviceMemoryBlock* block, + VmaAllocHandle allocHandle, + VkDeviceSize alignment, + VkDeviceSize size, + uint32_t memoryTypeIndex, + VmaSuballocationType suballocationType, + bool mapped) +{ + VMA_ASSERT(m_Type == ALLOCATION_TYPE_NONE); + VMA_ASSERT(block != VMA_NULL); + m_Type = (uint8_t)ALLOCATION_TYPE_BLOCK; + m_Alignment = alignment; + m_Size = size; + m_MemoryTypeIndex = memoryTypeIndex; + if(mapped) + { + VMA_ASSERT(IsMappingAllowed() && "Mapping is not allowed on this allocation! Please use one of the new VMA_ALLOCATION_CREATE_HOST_ACCESS_* flags when creating it."); + m_Flags |= (uint8_t)FLAG_PERSISTENT_MAP; + } + m_SuballocationType = (uint8_t)suballocationType; + m_BlockAllocation.m_Block = block; + m_BlockAllocation.m_AllocHandle = allocHandle; +} + +void VmaAllocation_T::InitDedicatedAllocation( + VmaAllocator allocator, + VmaPool hParentPool, + uint32_t memoryTypeIndex, + VkDeviceMemory hMemory, + VmaSuballocationType suballocationType, + void* pMappedData, + VkDeviceSize size) +{ + VMA_ASSERT(m_Type == ALLOCATION_TYPE_NONE); + VMA_ASSERT(hMemory != VK_NULL_HANDLE); + m_Type = (uint8_t)ALLOCATION_TYPE_DEDICATED; + m_Alignment = 0; + m_Size = size; + m_MemoryTypeIndex = memoryTypeIndex; + m_SuballocationType = (uint8_t)suballocationType; + m_DedicatedAllocation.m_ExtraData = VMA_NULL; + m_DedicatedAllocation.m_hParentPool = hParentPool; + m_DedicatedAllocation.m_hMemory = hMemory; + m_DedicatedAllocation.m_Prev = VMA_NULL; + m_DedicatedAllocation.m_Next = VMA_NULL; + + if (pMappedData != VMA_NULL) + { + VMA_ASSERT(IsMappingAllowed() && "Mapping is not allowed on this allocation! Please use one of the new VMA_ALLOCATION_CREATE_HOST_ACCESS_* flags when creating it."); + m_Flags |= (uint8_t)FLAG_PERSISTENT_MAP; + EnsureExtraData(allocator); + m_DedicatedAllocation.m_ExtraData->m_pMappedData = pMappedData; + } +} + +void VmaAllocation_T::Destroy(VmaAllocator allocator) +{ + FreeName(allocator); + + if (GetType() == ALLOCATION_TYPE_DEDICATED) + { + vma_delete(allocator, m_DedicatedAllocation.m_ExtraData); + } +} + +void VmaAllocation_T::SetName(VmaAllocator hAllocator, const char* pName) +{ + VMA_ASSERT(pName == VMA_NULL || pName != m_pName); + + FreeName(hAllocator); + + if (pName != VMA_NULL) + m_pName = VmaCreateStringCopy(hAllocator->GetAllocationCallbacks(), pName); +} + +uint8_t VmaAllocation_T::SwapBlockAllocation(VmaAllocator hAllocator, VmaAllocation allocation) +{ + VMA_ASSERT(allocation != VMA_NULL); + VMA_ASSERT(m_Type == ALLOCATION_TYPE_BLOCK); + VMA_ASSERT(allocation->m_Type == ALLOCATION_TYPE_BLOCK); + + if (m_MapCount != 0) + m_BlockAllocation.m_Block->Unmap(hAllocator, m_MapCount); + + m_BlockAllocation.m_Block->m_pMetadata->SetAllocationUserData(m_BlockAllocation.m_AllocHandle, allocation); + std::swap(m_BlockAllocation, allocation->m_BlockAllocation); + m_BlockAllocation.m_Block->m_pMetadata->SetAllocationUserData(m_BlockAllocation.m_AllocHandle, this); + +#if VMA_STATS_STRING_ENABLED + std::swap(m_BufferImageUsage, allocation->m_BufferImageUsage); +#endif + return m_MapCount; +} + +VmaAllocHandle VmaAllocation_T::GetAllocHandle() const +{ + switch (m_Type) + { + case ALLOCATION_TYPE_BLOCK: + return m_BlockAllocation.m_AllocHandle; + case ALLOCATION_TYPE_DEDICATED: + return VK_NULL_HANDLE; + default: + VMA_ASSERT(0); + return VK_NULL_HANDLE; + } +} + +VkDeviceSize VmaAllocation_T::GetOffset() const +{ + switch (m_Type) + { + case ALLOCATION_TYPE_BLOCK: + return m_BlockAllocation.m_Block->m_pMetadata->GetAllocationOffset(m_BlockAllocation.m_AllocHandle); + case ALLOCATION_TYPE_DEDICATED: + return 0; + default: + VMA_ASSERT(0); + return 0; + } +} + +VmaPool VmaAllocation_T::GetParentPool() const +{ + switch (m_Type) + { + case ALLOCATION_TYPE_BLOCK: + return m_BlockAllocation.m_Block->GetParentPool(); + case ALLOCATION_TYPE_DEDICATED: + return m_DedicatedAllocation.m_hParentPool; + default: + VMA_ASSERT(0); + return VK_NULL_HANDLE; + } +} + +VkDeviceMemory VmaAllocation_T::GetMemory() const +{ + switch (m_Type) + { + case ALLOCATION_TYPE_BLOCK: + return m_BlockAllocation.m_Block->GetDeviceMemory(); + case ALLOCATION_TYPE_DEDICATED: + return m_DedicatedAllocation.m_hMemory; + default: + VMA_ASSERT(0); + return VK_NULL_HANDLE; + } +} + +void* VmaAllocation_T::GetMappedData() const +{ + switch (m_Type) + { + case ALLOCATION_TYPE_BLOCK: + if (m_MapCount != 0 || IsPersistentMap()) + { + void* pBlockData = m_BlockAllocation.m_Block->GetMappedData(); + VMA_ASSERT(pBlockData != VMA_NULL); + return (char*)pBlockData + GetOffset(); + } + else + { + return VMA_NULL; + } + break; + case ALLOCATION_TYPE_DEDICATED: + VMA_ASSERT((m_DedicatedAllocation.m_ExtraData != VMA_NULL && m_DedicatedAllocation.m_ExtraData->m_pMappedData != VMA_NULL) == + (m_MapCount != 0 || IsPersistentMap())); + return m_DedicatedAllocation.m_ExtraData != VMA_NULL ? m_DedicatedAllocation.m_ExtraData->m_pMappedData : VMA_NULL; + default: + VMA_ASSERT(0); + return VMA_NULL; + } +} + +void VmaAllocation_T::BlockAllocMap() +{ + VMA_ASSERT(GetType() == ALLOCATION_TYPE_BLOCK); + VMA_ASSERT(IsMappingAllowed() && "Mapping is not allowed on this allocation! Please use one of the new VMA_ALLOCATION_CREATE_HOST_ACCESS_* flags when creating it."); + + if (m_MapCount < 0xFF) + { + ++m_MapCount; + } + else + { + VMA_ASSERT(0 && "Allocation mapped too many times simultaneously."); + } +} + +void VmaAllocation_T::BlockAllocUnmap() +{ + VMA_ASSERT(GetType() == ALLOCATION_TYPE_BLOCK); + + if (m_MapCount > 0) + { + --m_MapCount; + } + else + { + VMA_ASSERT(0 && "Unmapping allocation not previously mapped."); + } +} + +VkResult VmaAllocation_T::DedicatedAllocMap(VmaAllocator hAllocator, void** ppData) +{ + VMA_ASSERT(GetType() == ALLOCATION_TYPE_DEDICATED); + VMA_ASSERT(IsMappingAllowed() && "Mapping is not allowed on this allocation! Please use one of the new VMA_ALLOCATION_CREATE_HOST_ACCESS_* flags when creating it."); + + EnsureExtraData(hAllocator); + + if (m_MapCount != 0 || IsPersistentMap()) + { + if (m_MapCount < 0xFF) + { + VMA_ASSERT(m_DedicatedAllocation.m_ExtraData->m_pMappedData != VMA_NULL); + *ppData = m_DedicatedAllocation.m_ExtraData->m_pMappedData; + ++m_MapCount; + return VK_SUCCESS; + } + else + { + VMA_ASSERT(0 && "Dedicated allocation mapped too many times simultaneously."); + return VK_ERROR_MEMORY_MAP_FAILED; + } + } + else + { + VkResult result = (*hAllocator->GetVulkanFunctions().vkMapMemory)( + hAllocator->m_hDevice, + m_DedicatedAllocation.m_hMemory, + 0, // offset + VK_WHOLE_SIZE, + 0, // flags + ppData); + if (result == VK_SUCCESS) + { + m_DedicatedAllocation.m_ExtraData->m_pMappedData = *ppData; + m_MapCount = 1; + } + return result; + } +} + +void VmaAllocation_T::DedicatedAllocUnmap(VmaAllocator hAllocator) +{ + VMA_ASSERT(GetType() == ALLOCATION_TYPE_DEDICATED); + + if (m_MapCount > 0) + { + --m_MapCount; + if (m_MapCount == 0 && !IsPersistentMap()) + { + VMA_ASSERT(m_DedicatedAllocation.m_ExtraData != VMA_NULL); + m_DedicatedAllocation.m_ExtraData->m_pMappedData = VMA_NULL; + (*hAllocator->GetVulkanFunctions().vkUnmapMemory)( + hAllocator->m_hDevice, + m_DedicatedAllocation.m_hMemory); + } + } + else + { + VMA_ASSERT(0 && "Unmapping dedicated allocation not previously mapped."); + } +} + +#if VMA_STATS_STRING_ENABLED +void VmaAllocation_T::PrintParameters(class VmaJsonWriter& json) const +{ + json.WriteString("Type"); + json.WriteString(VMA_SUBALLOCATION_TYPE_NAMES[m_SuballocationType]); + + json.WriteString("Size"); + json.WriteNumber(m_Size); + json.WriteString("Usage"); + json.WriteNumber(m_BufferImageUsage.Value); // It may be uint32_t or uint64_t. + + if (m_pUserData != VMA_NULL) + { + json.WriteString("CustomData"); + json.BeginString(); + json.ContinueString_Pointer(m_pUserData); + json.EndString(); + } + if (m_pName != VMA_NULL) + { + json.WriteString("Name"); + json.WriteString(m_pName); + } +} +#if VMA_EXTERNAL_MEMORY_WIN32 +VkResult VmaAllocation_T::GetWin32Handle(VmaAllocator hAllocator, HANDLE hTargetProcess, HANDLE* pHandle) noexcept +{ + auto pvkGetMemoryWin32HandleKHR = hAllocator->GetVulkanFunctions().vkGetMemoryWin32HandleKHR; + switch (m_Type) + { + case ALLOCATION_TYPE_BLOCK: + return m_BlockAllocation.m_Block->CreateWin32Handle(hAllocator, pvkGetMemoryWin32HandleKHR, hTargetProcess, pHandle); + case ALLOCATION_TYPE_DEDICATED: + EnsureExtraData(hAllocator); + return m_DedicatedAllocation.m_ExtraData->m_Handle.GetHandle(hAllocator->m_hDevice, m_DedicatedAllocation.m_hMemory, pvkGetMemoryWin32HandleKHR, hTargetProcess, hAllocator->m_UseMutex, pHandle); + default: + VMA_ASSERT(0); + return VK_ERROR_FEATURE_NOT_PRESENT; + } +} +#endif // VMA_EXTERNAL_MEMORY_WIN32 +#endif // VMA_STATS_STRING_ENABLED + +void VmaAllocation_T::EnsureExtraData(VmaAllocator hAllocator) +{ + if (m_DedicatedAllocation.m_ExtraData == VMA_NULL) + { + m_DedicatedAllocation.m_ExtraData = vma_new(hAllocator, VmaAllocationExtraData)(); + } +} + +void VmaAllocation_T::FreeName(VmaAllocator hAllocator) +{ + if(m_pName) + { + VmaFreeString(hAllocator->GetAllocationCallbacks(), m_pName); + m_pName = VMA_NULL; + } +} +#endif // _VMA_ALLOCATION_T_FUNCTIONS + +#ifndef _VMA_BLOCK_VECTOR_FUNCTIONS +VmaBlockVector::VmaBlockVector( + VmaAllocator hAllocator, + VmaPool hParentPool, + uint32_t memoryTypeIndex, + VkDeviceSize preferredBlockSize, + size_t minBlockCount, + size_t maxBlockCount, + VkDeviceSize bufferImageGranularity, + bool explicitBlockSize, + uint32_t algorithm, + float priority, + VkDeviceSize minAllocationAlignment, + void* pMemoryAllocateNext) + : m_hAllocator(hAllocator), + m_hParentPool(hParentPool), + m_MemoryTypeIndex(memoryTypeIndex), + m_PreferredBlockSize(preferredBlockSize), + m_MinBlockCount(minBlockCount), + m_MaxBlockCount(maxBlockCount), + m_BufferImageGranularity(bufferImageGranularity), + m_ExplicitBlockSize(explicitBlockSize), + m_Algorithm(algorithm), + m_Priority(priority), + m_MinAllocationAlignment(minAllocationAlignment), + m_pMemoryAllocateNext(pMemoryAllocateNext), + m_Blocks(VmaStlAllocator(hAllocator->GetAllocationCallbacks())), + m_NextBlockId(0) {} + +VmaBlockVector::~VmaBlockVector() +{ + for (size_t i = m_Blocks.size(); i--; ) + { + m_Blocks[i]->Destroy(m_hAllocator); + vma_delete(m_hAllocator, m_Blocks[i]); + } +} + +VkResult VmaBlockVector::CreateMinBlocks() +{ + for (size_t i = 0; i < m_MinBlockCount; ++i) + { + VkResult res = CreateBlock(m_PreferredBlockSize, VMA_NULL); + if (res != VK_SUCCESS) + { + return res; + } + } + return VK_SUCCESS; +} + +void VmaBlockVector::AddStatistics(VmaStatistics& inoutStats) +{ + VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex); + + const size_t blockCount = m_Blocks.size(); + for (uint32_t blockIndex = 0; blockIndex < blockCount; ++blockIndex) + { + const VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex]; + VMA_ASSERT(pBlock); + VMA_HEAVY_ASSERT(pBlock->Validate()); + pBlock->m_pMetadata->AddStatistics(inoutStats); + } +} + +void VmaBlockVector::AddDetailedStatistics(VmaDetailedStatistics& inoutStats) +{ + VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex); + + const size_t blockCount = m_Blocks.size(); + for (uint32_t blockIndex = 0; blockIndex < blockCount; ++blockIndex) + { + const VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex]; + VMA_ASSERT(pBlock); + VMA_HEAVY_ASSERT(pBlock->Validate()); + pBlock->m_pMetadata->AddDetailedStatistics(inoutStats); + } +} + +bool VmaBlockVector::IsEmpty() +{ + VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex); + return m_Blocks.empty(); +} + +bool VmaBlockVector::IsCorruptionDetectionEnabled() const +{ + const uint32_t requiredMemFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + return (VMA_DEBUG_DETECT_CORRUPTION != 0) && + (VMA_DEBUG_MARGIN > 0) && + (m_Algorithm == 0 || m_Algorithm == VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT) && + (m_hAllocator->m_MemProps.memoryTypes[m_MemoryTypeIndex].propertyFlags & requiredMemFlags) == requiredMemFlags; +} + +VkResult VmaBlockVector::Allocate( + VkDeviceSize size, + VkDeviceSize alignment, + const VmaAllocationCreateInfo& createInfo, + VmaSuballocationType suballocType, + size_t allocationCount, + VmaAllocation* pAllocations) +{ + size_t allocIndex; + VkResult res = VK_SUCCESS; + + alignment = VMA_MAX(alignment, m_MinAllocationAlignment); + + if (IsCorruptionDetectionEnabled()) + { + size = VmaAlignUp(size, sizeof(VMA_CORRUPTION_DETECTION_MAGIC_VALUE)); + alignment = VmaAlignUp(alignment, sizeof(VMA_CORRUPTION_DETECTION_MAGIC_VALUE)); + } + + { + VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex); + for (allocIndex = 0; allocIndex < allocationCount; ++allocIndex) + { + res = AllocatePage( + size, + alignment, + createInfo, + suballocType, + pAllocations + allocIndex); + if (res != VK_SUCCESS) + { + break; + } + } + } + + if (res != VK_SUCCESS) + { + // Free all already created allocations. + while (allocIndex--) + Free(pAllocations[allocIndex]); + memset(pAllocations, 0, sizeof(VmaAllocation) * allocationCount); + } + + return res; +} + +VkResult VmaBlockVector::AllocatePage( + VkDeviceSize size, + VkDeviceSize alignment, + const VmaAllocationCreateInfo& createInfo, + VmaSuballocationType suballocType, + VmaAllocation* pAllocation) +{ + const bool isUpperAddress = (createInfo.flags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0; + + VkDeviceSize freeMemory; + { + const uint32_t heapIndex = m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex); + VmaBudget heapBudget = {}; + m_hAllocator->GetHeapBudgets(&heapBudget, heapIndex, 1); + freeMemory = (heapBudget.usage < heapBudget.budget) ? (heapBudget.budget - heapBudget.usage) : 0; + } + + const bool canFallbackToDedicated = !HasExplicitBlockSize() && + (createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) == 0; + const bool canCreateNewBlock = + ((createInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) == 0) && + (m_Blocks.size() < m_MaxBlockCount) && + (freeMemory >= size || !canFallbackToDedicated); + uint32_t strategy = createInfo.flags & VMA_ALLOCATION_CREATE_STRATEGY_MASK; + + // Upper address can only be used with linear allocator and within single memory block. + if (isUpperAddress && + (m_Algorithm != VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT || m_MaxBlockCount > 1)) + { + return VK_ERROR_FEATURE_NOT_PRESENT; + } + + // Early reject: requested allocation size is larger that maximum block size for this block vector. + if (size + VMA_DEBUG_MARGIN > m_PreferredBlockSize) + { + return VK_ERROR_OUT_OF_DEVICE_MEMORY; + } + + // 1. Search existing allocations. Try to allocate. + if (m_Algorithm == VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT) + { + // Use only last block. + if (!m_Blocks.empty()) + { + VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks.back(); + VMA_ASSERT(pCurrBlock); + VkResult res = AllocateFromBlock( + pCurrBlock, size, alignment, createInfo.flags, createInfo.pUserData, suballocType, strategy, pAllocation); + if (res == VK_SUCCESS) + { + VMA_DEBUG_LOG_FORMAT(" Returned from last block #%" PRIu32, pCurrBlock->GetId()); + IncrementallySortBlocks(); + return VK_SUCCESS; + } + } + } + else + { + if (strategy != VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT) // MIN_MEMORY or default + { + const bool isHostVisible = + (m_hAllocator->m_MemProps.memoryTypes[m_MemoryTypeIndex].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0; + if(isHostVisible) + { + const bool isMappingAllowed = (createInfo.flags & + (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != 0; + /* + For non-mappable allocations, check blocks that are not mapped first. + For mappable allocations, check blocks that are already mapped first. + This way, having many blocks, we will separate mappable and non-mappable allocations, + hopefully limiting the number of blocks that are mapped, which will help tools like RenderDoc. + */ + for(size_t mappingI = 0; mappingI < 2; ++mappingI) + { + // Forward order in m_Blocks - prefer blocks with smallest amount of free space. + for (size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) + { + VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex]; + VMA_ASSERT(pCurrBlock); + const bool isBlockMapped = pCurrBlock->GetMappedData() != VMA_NULL; + if((mappingI == 0) == (isMappingAllowed == isBlockMapped)) + { + VkResult res = AllocateFromBlock( + pCurrBlock, size, alignment, createInfo.flags, createInfo.pUserData, suballocType, strategy, pAllocation); + if (res == VK_SUCCESS) + { + VMA_DEBUG_LOG_FORMAT(" Returned from existing block #%" PRIu32, pCurrBlock->GetId()); + IncrementallySortBlocks(); + return VK_SUCCESS; + } + } + } + } + } + else + { + // Forward order in m_Blocks - prefer blocks with smallest amount of free space. + for (size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) + { + VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex]; + VMA_ASSERT(pCurrBlock); + VkResult res = AllocateFromBlock( + pCurrBlock, size, alignment, createInfo.flags, createInfo.pUserData, suballocType, strategy, pAllocation); + if (res == VK_SUCCESS) + { + VMA_DEBUG_LOG_FORMAT(" Returned from existing block #%" PRIu32, pCurrBlock->GetId()); + IncrementallySortBlocks(); + return VK_SUCCESS; + } + } + } + } + else // VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT + { + // Backward order in m_Blocks - prefer blocks with largest amount of free space. + for (size_t blockIndex = m_Blocks.size(); blockIndex--; ) + { + VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex]; + VMA_ASSERT(pCurrBlock); + VkResult res = AllocateFromBlock(pCurrBlock, size, alignment, createInfo.flags, createInfo.pUserData, suballocType, strategy, pAllocation); + if (res == VK_SUCCESS) + { + VMA_DEBUG_LOG_FORMAT(" Returned from existing block #%" PRIu32, pCurrBlock->GetId()); + IncrementallySortBlocks(); + return VK_SUCCESS; + } + } + } + } + + // 2. Try to create new block. + if (canCreateNewBlock) + { + // Calculate optimal size for new block. + VkDeviceSize newBlockSize = m_PreferredBlockSize; + uint32_t newBlockSizeShift = 0; + const uint32_t NEW_BLOCK_SIZE_SHIFT_MAX = 3; + + if (!m_ExplicitBlockSize) + { + // Allocate 1/8, 1/4, 1/2 as first blocks. + const VkDeviceSize maxExistingBlockSize = CalcMaxBlockSize(); + for (uint32_t i = 0; i < NEW_BLOCK_SIZE_SHIFT_MAX; ++i) + { + const VkDeviceSize smallerNewBlockSize = newBlockSize / 2; + if (smallerNewBlockSize > maxExistingBlockSize && smallerNewBlockSize >= size * 2) + { + newBlockSize = smallerNewBlockSize; + ++newBlockSizeShift; + } + else + { + break; + } + } + } + + size_t newBlockIndex = 0; + VkResult res = (newBlockSize <= freeMemory || !canFallbackToDedicated) ? + CreateBlock(newBlockSize, &newBlockIndex) : VK_ERROR_OUT_OF_DEVICE_MEMORY; + // Allocation of this size failed? Try 1/2, 1/4, 1/8 of m_PreferredBlockSize. + if (!m_ExplicitBlockSize) + { + while (res < 0 && newBlockSizeShift < NEW_BLOCK_SIZE_SHIFT_MAX) + { + const VkDeviceSize smallerNewBlockSize = newBlockSize / 2; + if (smallerNewBlockSize >= size) + { + newBlockSize = smallerNewBlockSize; + ++newBlockSizeShift; + res = (newBlockSize <= freeMemory || !canFallbackToDedicated) ? + CreateBlock(newBlockSize, &newBlockIndex) : VK_ERROR_OUT_OF_DEVICE_MEMORY; + } + else + { + break; + } + } + } + + if (res == VK_SUCCESS) + { + VmaDeviceMemoryBlock* const pBlock = m_Blocks[newBlockIndex]; + VMA_ASSERT(pBlock->m_pMetadata->GetSize() >= size); + + res = AllocateFromBlock( + pBlock, size, alignment, createInfo.flags, createInfo.pUserData, suballocType, strategy, pAllocation); + if (res == VK_SUCCESS) + { + VMA_DEBUG_LOG_FORMAT(" Created new block #%" PRIu32 " Size=%" PRIu64, pBlock->GetId(), newBlockSize); + IncrementallySortBlocks(); + return VK_SUCCESS; + } + else + { + // Allocation from new block failed, possibly due to VMA_DEBUG_MARGIN or alignment. + return VK_ERROR_OUT_OF_DEVICE_MEMORY; + } + } + } + + return VK_ERROR_OUT_OF_DEVICE_MEMORY; +} + +void VmaBlockVector::Free(const VmaAllocation hAllocation) +{ + VmaDeviceMemoryBlock* pBlockToDelete = VMA_NULL; + + bool budgetExceeded = false; + { + const uint32_t heapIndex = m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex); + VmaBudget heapBudget = {}; + m_hAllocator->GetHeapBudgets(&heapBudget, heapIndex, 1); + budgetExceeded = heapBudget.usage >= heapBudget.budget; + } + + // Scope for lock. + { + VmaMutexLockWrite lock(m_Mutex, m_hAllocator->m_UseMutex); + + VmaDeviceMemoryBlock* pBlock = hAllocation->GetBlock(); + + if (IsCorruptionDetectionEnabled()) + { + VkResult res = pBlock->ValidateMagicValueAfterAllocation(m_hAllocator, hAllocation->GetOffset(), hAllocation->GetSize()); + VMA_ASSERT(res == VK_SUCCESS && "Couldn't map block memory to validate magic value."); + } + + if (hAllocation->IsPersistentMap()) + { + pBlock->Unmap(m_hAllocator, 1); + } + + const bool hadEmptyBlockBeforeFree = HasEmptyBlock(); + pBlock->m_pMetadata->Free(hAllocation->GetAllocHandle()); + pBlock->PostFree(m_hAllocator); + VMA_HEAVY_ASSERT(pBlock->Validate()); + + VMA_DEBUG_LOG_FORMAT(" Freed from MemoryTypeIndex=%" PRIu32, m_MemoryTypeIndex); + + const bool canDeleteBlock = m_Blocks.size() > m_MinBlockCount; + // pBlock became empty after this deallocation. + if (pBlock->m_pMetadata->IsEmpty()) + { + // Already had empty block. We don't want to have two, so delete this one. + if ((hadEmptyBlockBeforeFree || budgetExceeded) && canDeleteBlock) + { + pBlockToDelete = pBlock; + Remove(pBlock); + } + // else: We now have one empty block - leave it. A hysteresis to avoid allocating whole block back and forth. + } + // pBlock didn't become empty, but we have another empty block - find and free that one. + // (This is optional, heuristics.) + else if (hadEmptyBlockBeforeFree && canDeleteBlock) + { + VmaDeviceMemoryBlock* pLastBlock = m_Blocks.back(); + if (pLastBlock->m_pMetadata->IsEmpty()) + { + pBlockToDelete = pLastBlock; + m_Blocks.pop_back(); + } + } + + IncrementallySortBlocks(); + } + + // Destruction of a free block. Deferred until this point, outside of mutex + // lock, for performance reason. + if (pBlockToDelete != VMA_NULL) + { + VMA_DEBUG_LOG_FORMAT(" Deleted empty block #%" PRIu32, pBlockToDelete->GetId()); + pBlockToDelete->Destroy(m_hAllocator); + vma_delete(m_hAllocator, pBlockToDelete); + } + + m_hAllocator->m_Budget.RemoveAllocation(m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex), hAllocation->GetSize()); + hAllocation->Destroy(m_hAllocator); + m_hAllocator->m_AllocationObjectAllocator.Free(hAllocation); +} + +VkDeviceSize VmaBlockVector::CalcMaxBlockSize() const +{ + VkDeviceSize result = 0; + for (size_t i = m_Blocks.size(); i--; ) + { + result = VMA_MAX(result, m_Blocks[i]->m_pMetadata->GetSize()); + if (result >= m_PreferredBlockSize) + { + break; + } + } + return result; +} + +void VmaBlockVector::Remove(VmaDeviceMemoryBlock* pBlock) +{ + for (uint32_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) + { + if (m_Blocks[blockIndex] == pBlock) + { + VmaVectorRemove(m_Blocks, blockIndex); + return; + } + } + VMA_ASSERT(0); +} + +void VmaBlockVector::IncrementallySortBlocks() +{ + if (!m_IncrementalSort) + return; + if (m_Algorithm != VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT) + { + // Bubble sort only until first swap. + for (size_t i = 1; i < m_Blocks.size(); ++i) + { + if (m_Blocks[i - 1]->m_pMetadata->GetSumFreeSize() > m_Blocks[i]->m_pMetadata->GetSumFreeSize()) + { + std::swap(m_Blocks[i - 1], m_Blocks[i]); + return; + } + } + } +} + +void VmaBlockVector::SortByFreeSize() +{ + VMA_SORT(m_Blocks.begin(), m_Blocks.end(), + [](VmaDeviceMemoryBlock* b1, VmaDeviceMemoryBlock* b2) -> bool + { + return b1->m_pMetadata->GetSumFreeSize() < b2->m_pMetadata->GetSumFreeSize(); + }); +} + +VkResult VmaBlockVector::AllocateFromBlock( + VmaDeviceMemoryBlock* pBlock, + VkDeviceSize size, + VkDeviceSize alignment, + VmaAllocationCreateFlags allocFlags, + void* pUserData, + VmaSuballocationType suballocType, + uint32_t strategy, + VmaAllocation* pAllocation) +{ + const bool isUpperAddress = (allocFlags & VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0; + + VmaAllocationRequest currRequest = {}; + if (pBlock->m_pMetadata->CreateAllocationRequest( + size, + alignment, + isUpperAddress, + suballocType, + strategy, + &currRequest)) + { + return CommitAllocationRequest(currRequest, pBlock, alignment, allocFlags, pUserData, suballocType, pAllocation); + } + return VK_ERROR_OUT_OF_DEVICE_MEMORY; +} + +VkResult VmaBlockVector::CommitAllocationRequest( + VmaAllocationRequest& allocRequest, + VmaDeviceMemoryBlock* pBlock, + VkDeviceSize alignment, + VmaAllocationCreateFlags allocFlags, + void* pUserData, + VmaSuballocationType suballocType, + VmaAllocation* pAllocation) +{ + const bool mapped = (allocFlags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0; + const bool isUserDataString = (allocFlags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0; + const bool isMappingAllowed = (allocFlags & + (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != 0; + + pBlock->PostAlloc(m_hAllocator); + // Allocate from pCurrBlock. + if (mapped) + { + VkResult res = pBlock->Map(m_hAllocator, 1, VMA_NULL); + if (res != VK_SUCCESS) + { + return res; + } + } + + *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(isMappingAllowed); + pBlock->m_pMetadata->Alloc(allocRequest, suballocType, *pAllocation); + (*pAllocation)->InitBlockAllocation( + pBlock, + allocRequest.allocHandle, + alignment, + allocRequest.size, // Not size, as actual allocation size may be larger than requested! + m_MemoryTypeIndex, + suballocType, + mapped); + VMA_HEAVY_ASSERT(pBlock->Validate()); + if (isUserDataString) + (*pAllocation)->SetName(m_hAllocator, (const char*)pUserData); + else + (*pAllocation)->SetUserData(m_hAllocator, pUserData); + m_hAllocator->m_Budget.AddAllocation(m_hAllocator->MemoryTypeIndexToHeapIndex(m_MemoryTypeIndex), allocRequest.size); + if (VMA_DEBUG_INITIALIZE_ALLOCATIONS) + { + m_hAllocator->FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED); + } + if (IsCorruptionDetectionEnabled()) + { + VkResult res = pBlock->WriteMagicValueAfterAllocation(m_hAllocator, (*pAllocation)->GetOffset(), allocRequest.size); + VMA_ASSERT(res == VK_SUCCESS && "Couldn't map block memory to write magic value."); + } + return VK_SUCCESS; +} + +VkResult VmaBlockVector::CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIndex) +{ + VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + allocInfo.pNext = m_pMemoryAllocateNext; + allocInfo.memoryTypeIndex = m_MemoryTypeIndex; + allocInfo.allocationSize = blockSize; + +#if VMA_BUFFER_DEVICE_ADDRESS + // Every standalone block can potentially contain a buffer with VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT - always enable the feature. + VkMemoryAllocateFlagsInfoKHR allocFlagsInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR }; + if (m_hAllocator->m_UseKhrBufferDeviceAddress) + { + allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR; + VmaPnextChainPushFront(&allocInfo, &allocFlagsInfo); + } +#endif // VMA_BUFFER_DEVICE_ADDRESS + +#if VMA_MEMORY_PRIORITY + VkMemoryPriorityAllocateInfoEXT priorityInfo = { VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT }; + if (m_hAllocator->m_UseExtMemoryPriority) + { + VMA_ASSERT(m_Priority >= 0.f && m_Priority <= 1.f); + priorityInfo.priority = m_Priority; + VmaPnextChainPushFront(&allocInfo, &priorityInfo); + } +#endif // VMA_MEMORY_PRIORITY + +#if VMA_EXTERNAL_MEMORY + // Attach VkExportMemoryAllocateInfoKHR if necessary. + VkExportMemoryAllocateInfoKHR exportMemoryAllocInfo = { VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR }; + exportMemoryAllocInfo.handleTypes = m_hAllocator->GetExternalMemoryHandleTypeFlags(m_MemoryTypeIndex); + if (exportMemoryAllocInfo.handleTypes != 0) + { + VmaPnextChainPushFront(&allocInfo, &exportMemoryAllocInfo); + } +#endif // VMA_EXTERNAL_MEMORY + + VkDeviceMemory mem = VK_NULL_HANDLE; + VkResult res = m_hAllocator->AllocateVulkanMemory(&allocInfo, &mem); + if (res < 0) + { + return res; + } + + // New VkDeviceMemory successfully created. + + // Create new Allocation for it. + VmaDeviceMemoryBlock* const pBlock = vma_new(m_hAllocator, VmaDeviceMemoryBlock)(m_hAllocator); + pBlock->Init( + m_hAllocator, + m_hParentPool, + m_MemoryTypeIndex, + mem, + allocInfo.allocationSize, + m_NextBlockId++, + m_Algorithm, + m_BufferImageGranularity); + + m_Blocks.push_back(pBlock); + if (pNewBlockIndex != VMA_NULL) + { + *pNewBlockIndex = m_Blocks.size() - 1; + } + + return VK_SUCCESS; +} + +bool VmaBlockVector::HasEmptyBlock() +{ + for (size_t index = 0, count = m_Blocks.size(); index < count; ++index) + { + VmaDeviceMemoryBlock* const pBlock = m_Blocks[index]; + if (pBlock->m_pMetadata->IsEmpty()) + { + return true; + } + } + return false; +} + +#if VMA_STATS_STRING_ENABLED +void VmaBlockVector::PrintDetailedMap(class VmaJsonWriter& json) +{ + VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex); + + + json.BeginObject(); + for (size_t i = 0; i < m_Blocks.size(); ++i) + { + json.BeginString(); + json.ContinueString(m_Blocks[i]->GetId()); + json.EndString(); + + json.BeginObject(); + json.WriteString("MapRefCount"); + json.WriteNumber(m_Blocks[i]->GetMapRefCount()); + + m_Blocks[i]->m_pMetadata->PrintDetailedMap(json); + json.EndObject(); + } + json.EndObject(); +} +#endif // VMA_STATS_STRING_ENABLED + +VkResult VmaBlockVector::CheckCorruption() +{ + if (!IsCorruptionDetectionEnabled()) + { + return VK_ERROR_FEATURE_NOT_PRESENT; + } + + VmaMutexLockRead lock(m_Mutex, m_hAllocator->m_UseMutex); + for (uint32_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) + { + VmaDeviceMemoryBlock* const pBlock = m_Blocks[blockIndex]; + VMA_ASSERT(pBlock); + VkResult res = pBlock->CheckCorruption(m_hAllocator); + if (res != VK_SUCCESS) + { + return res; + } + } + return VK_SUCCESS; +} + +#endif // _VMA_BLOCK_VECTOR_FUNCTIONS + +#ifndef _VMA_DEFRAGMENTATION_CONTEXT_FUNCTIONS +VmaDefragmentationContext_T::VmaDefragmentationContext_T( + VmaAllocator hAllocator, + const VmaDefragmentationInfo& info) + : m_MaxPassBytes(info.maxBytesPerPass == 0 ? VK_WHOLE_SIZE : info.maxBytesPerPass), + m_MaxPassAllocations(info.maxAllocationsPerPass == 0 ? UINT32_MAX : info.maxAllocationsPerPass), + m_BreakCallback(info.pfnBreakCallback), + m_BreakCallbackUserData(info.pBreakCallbackUserData), + m_MoveAllocator(hAllocator->GetAllocationCallbacks()), + m_Moves(m_MoveAllocator) +{ + m_Algorithm = info.flags & VMA_DEFRAGMENTATION_FLAG_ALGORITHM_MASK; + + if (info.pool != VMA_NULL) + { + m_BlockVectorCount = 1; + m_PoolBlockVector = &info.pool->m_BlockVector; + m_pBlockVectors = &m_PoolBlockVector; + m_PoolBlockVector->SetIncrementalSort(false); + m_PoolBlockVector->SortByFreeSize(); + } + else + { + m_BlockVectorCount = hAllocator->GetMemoryTypeCount(); + m_PoolBlockVector = VMA_NULL; + m_pBlockVectors = hAllocator->m_pBlockVectors; + for (uint32_t i = 0; i < m_BlockVectorCount; ++i) + { + VmaBlockVector* vector = m_pBlockVectors[i]; + if (vector != VMA_NULL) + { + vector->SetIncrementalSort(false); + vector->SortByFreeSize(); + } + } + } + + switch (m_Algorithm) + { + case 0: // Default algorithm + m_Algorithm = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT; + m_AlgorithmState = vma_new_array(hAllocator, StateBalanced, m_BlockVectorCount); + break; + case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT: + m_AlgorithmState = vma_new_array(hAllocator, StateBalanced, m_BlockVectorCount); + break; + case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT: + if (hAllocator->GetBufferImageGranularity() > 1) + { + m_AlgorithmState = vma_new_array(hAllocator, StateExtensive, m_BlockVectorCount); + } + break; + } +} + +VmaDefragmentationContext_T::~VmaDefragmentationContext_T() +{ + if (m_PoolBlockVector != VMA_NULL) + { + m_PoolBlockVector->SetIncrementalSort(true); + } + else + { + for (uint32_t i = 0; i < m_BlockVectorCount; ++i) + { + VmaBlockVector* vector = m_pBlockVectors[i]; + if (vector != VMA_NULL) + vector->SetIncrementalSort(true); + } + } + + if (m_AlgorithmState) + { + switch (m_Algorithm) + { + case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT: + vma_delete_array(m_MoveAllocator.m_pCallbacks, reinterpret_cast(m_AlgorithmState), m_BlockVectorCount); + break; + case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT: + vma_delete_array(m_MoveAllocator.m_pCallbacks, reinterpret_cast(m_AlgorithmState), m_BlockVectorCount); + break; + default: + VMA_ASSERT(0); + } + } +} + +VkResult VmaDefragmentationContext_T::DefragmentPassBegin(VmaDefragmentationPassMoveInfo& moveInfo) +{ + if (m_PoolBlockVector != VMA_NULL) + { + VmaMutexLockWrite lock(m_PoolBlockVector->GetMutex(), m_PoolBlockVector->GetAllocator()->m_UseMutex); + + if (m_PoolBlockVector->GetBlockCount() > 1) + ComputeDefragmentation(*m_PoolBlockVector, 0); + else if (m_PoolBlockVector->GetBlockCount() == 1) + ReallocWithinBlock(*m_PoolBlockVector, m_PoolBlockVector->GetBlock(0)); + } + else + { + for (uint32_t i = 0; i < m_BlockVectorCount; ++i) + { + if (m_pBlockVectors[i] != VMA_NULL) + { + VmaMutexLockWrite lock(m_pBlockVectors[i]->GetMutex(), m_pBlockVectors[i]->GetAllocator()->m_UseMutex); + + if (m_pBlockVectors[i]->GetBlockCount() > 1) + { + if (ComputeDefragmentation(*m_pBlockVectors[i], i)) + break; + } + else if (m_pBlockVectors[i]->GetBlockCount() == 1) + { + if (ReallocWithinBlock(*m_pBlockVectors[i], m_pBlockVectors[i]->GetBlock(0))) + break; + } + } + } + } + + moveInfo.moveCount = static_cast(m_Moves.size()); + if (moveInfo.moveCount > 0) + { + moveInfo.pMoves = m_Moves.data(); + return VK_INCOMPLETE; + } + + moveInfo.pMoves = VMA_NULL; + return VK_SUCCESS; +} + +VkResult VmaDefragmentationContext_T::DefragmentPassEnd(VmaDefragmentationPassMoveInfo& moveInfo) +{ + VMA_ASSERT(moveInfo.moveCount > 0 ? moveInfo.pMoves != VMA_NULL : true); + + VkResult result = VK_SUCCESS; + VmaStlAllocator blockAllocator(m_MoveAllocator.m_pCallbacks); + VmaVector> immovableBlocks(blockAllocator); + VmaVector> mappedBlocks(blockAllocator); + + VmaAllocator allocator = VMA_NULL; + for (uint32_t i = 0; i < moveInfo.moveCount; ++i) + { + VmaDefragmentationMove& move = moveInfo.pMoves[i]; + size_t prevCount = 0, currentCount = 0; + VkDeviceSize freedBlockSize = 0; + + uint32_t vectorIndex; + VmaBlockVector* vector; + if (m_PoolBlockVector != VMA_NULL) + { + vectorIndex = 0; + vector = m_PoolBlockVector; + } + else + { + vectorIndex = move.srcAllocation->GetMemoryTypeIndex(); + vector = m_pBlockVectors[vectorIndex]; + VMA_ASSERT(vector != VMA_NULL); + } + + switch (move.operation) + { + case VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY: + { + uint8_t mapCount = move.srcAllocation->SwapBlockAllocation(vector->m_hAllocator, move.dstTmpAllocation); + if (mapCount > 0) + { + allocator = vector->m_hAllocator; + VmaDeviceMemoryBlock* newMapBlock = move.srcAllocation->GetBlock(); + bool notPresent = true; + for (FragmentedBlock& block : mappedBlocks) + { + if (block.block == newMapBlock) + { + notPresent = false; + block.data += mapCount; + break; + } + } + if (notPresent) + mappedBlocks.push_back({ mapCount, newMapBlock }); + } + + // Scope for locks, Free have it's own lock + { + VmaMutexLockRead lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex); + prevCount = vector->GetBlockCount(); + freedBlockSize = move.dstTmpAllocation->GetBlock()->m_pMetadata->GetSize(); + } + vector->Free(move.dstTmpAllocation); + { + VmaMutexLockRead lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex); + currentCount = vector->GetBlockCount(); + } + + result = VK_INCOMPLETE; + break; + } + case VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE: + { + m_PassStats.bytesMoved -= move.srcAllocation->GetSize(); + --m_PassStats.allocationsMoved; + vector->Free(move.dstTmpAllocation); + + VmaDeviceMemoryBlock* newBlock = move.srcAllocation->GetBlock(); + bool notPresent = true; + for (const FragmentedBlock& block : immovableBlocks) + { + if (block.block == newBlock) + { + notPresent = false; + break; + } + } + if (notPresent) + immovableBlocks.push_back({ vectorIndex, newBlock }); + break; + } + case VMA_DEFRAGMENTATION_MOVE_OPERATION_DESTROY: + { + m_PassStats.bytesMoved -= move.srcAllocation->GetSize(); + --m_PassStats.allocationsMoved; + // Scope for locks, Free have it's own lock + { + VmaMutexLockRead lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex); + prevCount = vector->GetBlockCount(); + freedBlockSize = move.srcAllocation->GetBlock()->m_pMetadata->GetSize(); + } + vector->Free(move.srcAllocation); + { + VmaMutexLockRead lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex); + currentCount = vector->GetBlockCount(); + } + freedBlockSize *= prevCount - currentCount; + + VkDeviceSize dstBlockSize; + { + VmaMutexLockRead lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex); + dstBlockSize = move.dstTmpAllocation->GetBlock()->m_pMetadata->GetSize(); + } + vector->Free(move.dstTmpAllocation); + { + VmaMutexLockRead lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex); + freedBlockSize += dstBlockSize * (currentCount - vector->GetBlockCount()); + currentCount = vector->GetBlockCount(); + } + + result = VK_INCOMPLETE; + break; + } + default: + VMA_ASSERT(0); + } + + if (prevCount > currentCount) + { + size_t freedBlocks = prevCount - currentCount; + m_PassStats.deviceMemoryBlocksFreed += static_cast(freedBlocks); + m_PassStats.bytesFreed += freedBlockSize; + } + + if(m_Algorithm == VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT && + m_AlgorithmState != VMA_NULL) + { + // Avoid unnecessary tries to allocate when new free block is available + StateExtensive& state = reinterpret_cast(m_AlgorithmState)[vectorIndex]; + if (state.firstFreeBlock != SIZE_MAX) + { + const size_t diff = prevCount - currentCount; + if (state.firstFreeBlock >= diff) + { + state.firstFreeBlock -= diff; + if (state.firstFreeBlock != 0) + state.firstFreeBlock -= vector->GetBlock(state.firstFreeBlock - 1)->m_pMetadata->IsEmpty(); + } + else + state.firstFreeBlock = 0; + } + } + } + moveInfo.moveCount = 0; + moveInfo.pMoves = VMA_NULL; + m_Moves.clear(); + + // Update stats + m_GlobalStats.allocationsMoved += m_PassStats.allocationsMoved; + m_GlobalStats.bytesFreed += m_PassStats.bytesFreed; + m_GlobalStats.bytesMoved += m_PassStats.bytesMoved; + m_GlobalStats.deviceMemoryBlocksFreed += m_PassStats.deviceMemoryBlocksFreed; + m_PassStats = { 0 }; + + // Move blocks with immovable allocations according to algorithm + if (immovableBlocks.size() > 0) + { + do + { + if(m_Algorithm == VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT) + { + if (m_AlgorithmState != VMA_NULL) + { + bool swapped = false; + // Move to the start of free blocks range + for (const FragmentedBlock& block : immovableBlocks) + { + StateExtensive& state = reinterpret_cast(m_AlgorithmState)[block.data]; + if (state.operation != StateExtensive::Operation::Cleanup) + { + VmaBlockVector* vector = m_pBlockVectors[block.data]; + VmaMutexLockWrite lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex); + + for (size_t i = 0, count = vector->GetBlockCount() - m_ImmovableBlockCount; i < count; ++i) + { + if (vector->GetBlock(i) == block.block) + { + std::swap(vector->m_Blocks[i], vector->m_Blocks[vector->GetBlockCount() - ++m_ImmovableBlockCount]); + if (state.firstFreeBlock != SIZE_MAX) + { + if (i + 1 < state.firstFreeBlock) + { + if (state.firstFreeBlock > 1) + std::swap(vector->m_Blocks[i], vector->m_Blocks[--state.firstFreeBlock]); + else + --state.firstFreeBlock; + } + } + swapped = true; + break; + } + } + } + } + if (swapped) + result = VK_INCOMPLETE; + break; + } + } + + // Move to the beginning + for (const FragmentedBlock& block : immovableBlocks) + { + VmaBlockVector* vector = m_pBlockVectors[block.data]; + VmaMutexLockWrite lock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex); + + for (size_t i = m_ImmovableBlockCount; i < vector->GetBlockCount(); ++i) + { + if (vector->GetBlock(i) == block.block) + { + std::swap(vector->m_Blocks[i], vector->m_Blocks[m_ImmovableBlockCount++]); + break; + } + } + } + } while (false); + } + + // Bulk-map destination blocks + for (const FragmentedBlock& block : mappedBlocks) + { + VkResult res = block.block->Map(allocator, block.data, VMA_NULL); + VMA_ASSERT(res == VK_SUCCESS); + } + return result; +} + +bool VmaDefragmentationContext_T::ComputeDefragmentation(VmaBlockVector& vector, size_t index) +{ + switch (m_Algorithm) + { + case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT: + return ComputeDefragmentation_Fast(vector); + case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED_BIT: + return ComputeDefragmentation_Balanced(vector, index, true); + case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FULL_BIT: + return ComputeDefragmentation_Full(vector); + case VMA_DEFRAGMENTATION_FLAG_ALGORITHM_EXTENSIVE_BIT: + return ComputeDefragmentation_Extensive(vector, index); + default: + VMA_ASSERT(0); + return ComputeDefragmentation_Balanced(vector, index, true); + } +} + +VmaDefragmentationContext_T::MoveAllocationData VmaDefragmentationContext_T::GetMoveData( + VmaAllocHandle handle, VmaBlockMetadata* metadata) +{ + MoveAllocationData moveData; + moveData.move.srcAllocation = (VmaAllocation)metadata->GetAllocationUserData(handle); + moveData.size = moveData.move.srcAllocation->GetSize(); + moveData.alignment = moveData.move.srcAllocation->GetAlignment(); + moveData.type = moveData.move.srcAllocation->GetSuballocationType(); + moveData.flags = 0; + + if (moveData.move.srcAllocation->IsPersistentMap()) + moveData.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT; + if (moveData.move.srcAllocation->IsMappingAllowed()) + moveData.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; + + return moveData; +} + +VmaDefragmentationContext_T::CounterStatus VmaDefragmentationContext_T::CheckCounters(VkDeviceSize bytes) +{ + // Check custom criteria if exists + if (m_BreakCallback && m_BreakCallback(m_BreakCallbackUserData)) + return CounterStatus::End; + + // Ignore allocation if will exceed max size for copy + if (m_PassStats.bytesMoved + bytes > m_MaxPassBytes) + { + if (++m_IgnoredAllocs < MAX_ALLOCS_TO_IGNORE) + return CounterStatus::Ignore; + else + return CounterStatus::End; + } + else + m_IgnoredAllocs = 0; + return CounterStatus::Pass; +} + +bool VmaDefragmentationContext_T::IncrementCounters(VkDeviceSize bytes) +{ + m_PassStats.bytesMoved += bytes; + // Early return when max found + if (++m_PassStats.allocationsMoved >= m_MaxPassAllocations || m_PassStats.bytesMoved >= m_MaxPassBytes) + { + VMA_ASSERT((m_PassStats.allocationsMoved == m_MaxPassAllocations || + m_PassStats.bytesMoved == m_MaxPassBytes) && "Exceeded maximal pass threshold!"); + return true; + } + return false; +} + +bool VmaDefragmentationContext_T::ReallocWithinBlock(VmaBlockVector& vector, VmaDeviceMemoryBlock* block) +{ + VmaBlockMetadata* metadata = block->m_pMetadata; + + for (VmaAllocHandle handle = metadata->GetAllocationListBegin(); + handle != VK_NULL_HANDLE; + handle = metadata->GetNextAllocation(handle)) + { + MoveAllocationData moveData = GetMoveData(handle, metadata); + // Ignore newly created allocations by defragmentation algorithm + if (moveData.move.srcAllocation->GetUserData() == this) + continue; + switch (CheckCounters(moveData.move.srcAllocation->GetSize())) + { + case CounterStatus::Ignore: + continue; + case CounterStatus::End: + return true; + case CounterStatus::Pass: + break; + default: + VMA_ASSERT(0); + } + + VkDeviceSize offset = moveData.move.srcAllocation->GetOffset(); + if (offset != 0 && metadata->GetSumFreeSize() >= moveData.size) + { + VmaAllocationRequest request = {}; + if (metadata->CreateAllocationRequest( + moveData.size, + moveData.alignment, + false, + moveData.type, + VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT, + &request)) + { + if (metadata->GetAllocationOffset(request.allocHandle) < offset) + { + if (vector.CommitAllocationRequest( + request, + block, + moveData.alignment, + moveData.flags, + this, + moveData.type, + &moveData.move.dstTmpAllocation) == VK_SUCCESS) + { + m_Moves.push_back(moveData.move); + if (IncrementCounters(moveData.size)) + return true; + } + } + } + } + } + return false; +} + +bool VmaDefragmentationContext_T::AllocInOtherBlock(size_t start, size_t end, MoveAllocationData& data, VmaBlockVector& vector) +{ + for (; start < end; ++start) + { + VmaDeviceMemoryBlock* dstBlock = vector.GetBlock(start); + if (dstBlock->m_pMetadata->GetSumFreeSize() >= data.size) + { + if (vector.AllocateFromBlock(dstBlock, + data.size, + data.alignment, + data.flags, + this, + data.type, + 0, + &data.move.dstTmpAllocation) == VK_SUCCESS) + { + m_Moves.push_back(data.move); + if (IncrementCounters(data.size)) + return true; + break; + } + } + } + return false; +} + +bool VmaDefragmentationContext_T::ComputeDefragmentation_Fast(VmaBlockVector& vector) +{ + // Move only between blocks + + // Go through allocations in last blocks and try to fit them inside first ones + for (size_t i = vector.GetBlockCount() - 1; i > m_ImmovableBlockCount; --i) + { + VmaBlockMetadata* metadata = vector.GetBlock(i)->m_pMetadata; + + for (VmaAllocHandle handle = metadata->GetAllocationListBegin(); + handle != VK_NULL_HANDLE; + handle = metadata->GetNextAllocation(handle)) + { + MoveAllocationData moveData = GetMoveData(handle, metadata); + // Ignore newly created allocations by defragmentation algorithm + if (moveData.move.srcAllocation->GetUserData() == this) + continue; + switch (CheckCounters(moveData.move.srcAllocation->GetSize())) + { + case CounterStatus::Ignore: + continue; + case CounterStatus::End: + return true; + case CounterStatus::Pass: + break; + default: + VMA_ASSERT(0); + } + + // Check all previous blocks for free space + if (AllocInOtherBlock(0, i, moveData, vector)) + return true; + } + } + return false; +} + +bool VmaDefragmentationContext_T::ComputeDefragmentation_Balanced(VmaBlockVector& vector, size_t index, bool update) +{ + // Go over every allocation and try to fit it in previous blocks at lowest offsets, + // if not possible: realloc within single block to minimize offset (exclude offset == 0), + // but only if there are noticeable gaps between them (some heuristic, ex. average size of allocation in block) + VMA_ASSERT(m_AlgorithmState != VMA_NULL); + + StateBalanced& vectorState = reinterpret_cast(m_AlgorithmState)[index]; + if (update && vectorState.avgAllocSize == UINT64_MAX) + UpdateVectorStatistics(vector, vectorState); + + const size_t startMoveCount = m_Moves.size(); + VkDeviceSize minimalFreeRegion = vectorState.avgFreeSize / 2; + for (size_t i = vector.GetBlockCount() - 1; i > m_ImmovableBlockCount; --i) + { + VmaDeviceMemoryBlock* block = vector.GetBlock(i); + VmaBlockMetadata* metadata = block->m_pMetadata; + VkDeviceSize prevFreeRegionSize = 0; + + for (VmaAllocHandle handle = metadata->GetAllocationListBegin(); + handle != VK_NULL_HANDLE; + handle = metadata->GetNextAllocation(handle)) + { + MoveAllocationData moveData = GetMoveData(handle, metadata); + // Ignore newly created allocations by defragmentation algorithm + if (moveData.move.srcAllocation->GetUserData() == this) + continue; + switch (CheckCounters(moveData.move.srcAllocation->GetSize())) + { + case CounterStatus::Ignore: + continue; + case CounterStatus::End: + return true; + case CounterStatus::Pass: + break; + default: + VMA_ASSERT(0); + } + + // Check all previous blocks for free space + const size_t prevMoveCount = m_Moves.size(); + if (AllocInOtherBlock(0, i, moveData, vector)) + return true; + + VkDeviceSize nextFreeRegionSize = metadata->GetNextFreeRegionSize(handle); + // If no room found then realloc within block for lower offset + VkDeviceSize offset = moveData.move.srcAllocation->GetOffset(); + if (prevMoveCount == m_Moves.size() && offset != 0 && metadata->GetSumFreeSize() >= moveData.size) + { + // Check if realloc will make sense + if (prevFreeRegionSize >= minimalFreeRegion || + nextFreeRegionSize >= minimalFreeRegion || + moveData.size <= vectorState.avgFreeSize || + moveData.size <= vectorState.avgAllocSize) + { + VmaAllocationRequest request = {}; + if (metadata->CreateAllocationRequest( + moveData.size, + moveData.alignment, + false, + moveData.type, + VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT, + &request)) + { + if (metadata->GetAllocationOffset(request.allocHandle) < offset) + { + if (vector.CommitAllocationRequest( + request, + block, + moveData.alignment, + moveData.flags, + this, + moveData.type, + &moveData.move.dstTmpAllocation) == VK_SUCCESS) + { + m_Moves.push_back(moveData.move); + if (IncrementCounters(moveData.size)) + return true; + } + } + } + } + } + prevFreeRegionSize = nextFreeRegionSize; + } + } + + // No moves performed, update statistics to current vector state + if (startMoveCount == m_Moves.size() && !update) + { + vectorState.avgAllocSize = UINT64_MAX; + return ComputeDefragmentation_Balanced(vector, index, false); + } + return false; +} + +bool VmaDefragmentationContext_T::ComputeDefragmentation_Full(VmaBlockVector& vector) +{ + // Go over every allocation and try to fit it in previous blocks at lowest offsets, + // if not possible: realloc within single block to minimize offset (exclude offset == 0) + + for (size_t i = vector.GetBlockCount() - 1; i > m_ImmovableBlockCount; --i) + { + VmaDeviceMemoryBlock* block = vector.GetBlock(i); + VmaBlockMetadata* metadata = block->m_pMetadata; + + for (VmaAllocHandle handle = metadata->GetAllocationListBegin(); + handle != VK_NULL_HANDLE; + handle = metadata->GetNextAllocation(handle)) + { + MoveAllocationData moveData = GetMoveData(handle, metadata); + // Ignore newly created allocations by defragmentation algorithm + if (moveData.move.srcAllocation->GetUserData() == this) + continue; + switch (CheckCounters(moveData.move.srcAllocation->GetSize())) + { + case CounterStatus::Ignore: + continue; + case CounterStatus::End: + return true; + case CounterStatus::Pass: + break; + default: + VMA_ASSERT(0); + } + + // Check all previous blocks for free space + const size_t prevMoveCount = m_Moves.size(); + if (AllocInOtherBlock(0, i, moveData, vector)) + return true; + + // If no room found then realloc within block for lower offset + VkDeviceSize offset = moveData.move.srcAllocation->GetOffset(); + if (prevMoveCount == m_Moves.size() && offset != 0 && metadata->GetSumFreeSize() >= moveData.size) + { + VmaAllocationRequest request = {}; + if (metadata->CreateAllocationRequest( + moveData.size, + moveData.alignment, + false, + moveData.type, + VMA_ALLOCATION_CREATE_STRATEGY_MIN_OFFSET_BIT, + &request)) + { + if (metadata->GetAllocationOffset(request.allocHandle) < offset) + { + if (vector.CommitAllocationRequest( + request, + block, + moveData.alignment, + moveData.flags, + this, + moveData.type, + &moveData.move.dstTmpAllocation) == VK_SUCCESS) + { + m_Moves.push_back(moveData.move); + if (IncrementCounters(moveData.size)) + return true; + } + } + } + } + } + } + return false; +} + +bool VmaDefragmentationContext_T::ComputeDefragmentation_Extensive(VmaBlockVector& vector, size_t index) +{ + // First free single block, then populate it to the brim, then free another block, and so on + + // Fallback to previous algorithm since without granularity conflicts it can achieve max packing + if (vector.m_BufferImageGranularity == 1) + return ComputeDefragmentation_Full(vector); + + VMA_ASSERT(m_AlgorithmState != VMA_NULL); + + StateExtensive& vectorState = reinterpret_cast(m_AlgorithmState)[index]; + + bool texturePresent = false, bufferPresent = false, otherPresent = false; + switch (vectorState.operation) + { + case StateExtensive::Operation::Done: // Vector defragmented + return false; + case StateExtensive::Operation::FindFreeBlockBuffer: + case StateExtensive::Operation::FindFreeBlockTexture: + case StateExtensive::Operation::FindFreeBlockAll: + { + // No more blocks to free, just perform fast realloc and move to cleanup + if (vectorState.firstFreeBlock == 0) + { + vectorState.operation = StateExtensive::Operation::Cleanup; + return ComputeDefragmentation_Fast(vector); + } + + // No free blocks, have to clear last one + size_t last = (vectorState.firstFreeBlock == SIZE_MAX ? vector.GetBlockCount() : vectorState.firstFreeBlock) - 1; + VmaBlockMetadata* freeMetadata = vector.GetBlock(last)->m_pMetadata; + + const size_t prevMoveCount = m_Moves.size(); + for (VmaAllocHandle handle = freeMetadata->GetAllocationListBegin(); + handle != VK_NULL_HANDLE; + handle = freeMetadata->GetNextAllocation(handle)) + { + MoveAllocationData moveData = GetMoveData(handle, freeMetadata); + switch (CheckCounters(moveData.move.srcAllocation->GetSize())) + { + case CounterStatus::Ignore: + continue; + case CounterStatus::End: + return true; + case CounterStatus::Pass: + break; + default: + VMA_ASSERT(0); + } + + // Check all previous blocks for free space + if (AllocInOtherBlock(0, last, moveData, vector)) + { + // Full clear performed already + if (prevMoveCount != m_Moves.size() && freeMetadata->GetNextAllocation(handle) == VK_NULL_HANDLE) + vectorState.firstFreeBlock = last; + return true; + } + } + + if (prevMoveCount == m_Moves.size()) + { + // Cannot perform full clear, have to move data in other blocks around + if (last != 0) + { + for (size_t i = last - 1; i; --i) + { + if (ReallocWithinBlock(vector, vector.GetBlock(i))) + return true; + } + } + + if (prevMoveCount == m_Moves.size()) + { + // No possible reallocs within blocks, try to move them around fast + return ComputeDefragmentation_Fast(vector); + } + } + else + { + switch (vectorState.operation) + { + case StateExtensive::Operation::FindFreeBlockBuffer: + vectorState.operation = StateExtensive::Operation::MoveBuffers; + break; + case StateExtensive::Operation::FindFreeBlockTexture: + vectorState.operation = StateExtensive::Operation::MoveTextures; + break; + case StateExtensive::Operation::FindFreeBlockAll: + vectorState.operation = StateExtensive::Operation::MoveAll; + break; + default: + VMA_ASSERT(0); + vectorState.operation = StateExtensive::Operation::MoveTextures; + } + vectorState.firstFreeBlock = last; + // Nothing done, block found without reallocations, can perform another reallocs in same pass + return ComputeDefragmentation_Extensive(vector, index); + } + break; + } + case StateExtensive::Operation::MoveTextures: + { + if (MoveDataToFreeBlocks(VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL, vector, + vectorState.firstFreeBlock, texturePresent, bufferPresent, otherPresent)) + { + if (texturePresent) + { + vectorState.operation = StateExtensive::Operation::FindFreeBlockTexture; + return ComputeDefragmentation_Extensive(vector, index); + } + + if (!bufferPresent && !otherPresent) + { + vectorState.operation = StateExtensive::Operation::Cleanup; + break; + } + + // No more textures to move, check buffers + vectorState.operation = StateExtensive::Operation::MoveBuffers; + bufferPresent = false; + otherPresent = false; + } + else + break; + VMA_FALLTHROUGH; // Fallthrough + } + case StateExtensive::Operation::MoveBuffers: + { + if (MoveDataToFreeBlocks(VMA_SUBALLOCATION_TYPE_BUFFER, vector, + vectorState.firstFreeBlock, texturePresent, bufferPresent, otherPresent)) + { + if (bufferPresent) + { + vectorState.operation = StateExtensive::Operation::FindFreeBlockBuffer; + return ComputeDefragmentation_Extensive(vector, index); + } + + if (!otherPresent) + { + vectorState.operation = StateExtensive::Operation::Cleanup; + break; + } + + // No more buffers to move, check all others + vectorState.operation = StateExtensive::Operation::MoveAll; + otherPresent = false; + } + else + break; + VMA_FALLTHROUGH; // Fallthrough + } + case StateExtensive::Operation::MoveAll: + { + if (MoveDataToFreeBlocks(VMA_SUBALLOCATION_TYPE_FREE, vector, + vectorState.firstFreeBlock, texturePresent, bufferPresent, otherPresent)) + { + if (otherPresent) + { + vectorState.operation = StateExtensive::Operation::FindFreeBlockBuffer; + return ComputeDefragmentation_Extensive(vector, index); + } + // Everything moved + vectorState.operation = StateExtensive::Operation::Cleanup; + } + break; + } + case StateExtensive::Operation::Cleanup: + // Cleanup is handled below so that other operations may reuse the cleanup code. This case is here to prevent the unhandled enum value warning (C4062). + break; + } + + if (vectorState.operation == StateExtensive::Operation::Cleanup) + { + // All other work done, pack data in blocks even tighter if possible + const size_t prevMoveCount = m_Moves.size(); + for (size_t i = 0; i < vector.GetBlockCount(); ++i) + { + if (ReallocWithinBlock(vector, vector.GetBlock(i))) + return true; + } + + if (prevMoveCount == m_Moves.size()) + vectorState.operation = StateExtensive::Operation::Done; + } + return false; +} + +void VmaDefragmentationContext_T::UpdateVectorStatistics(VmaBlockVector& vector, StateBalanced& state) +{ + size_t allocCount = 0; + size_t freeCount = 0; + state.avgFreeSize = 0; + state.avgAllocSize = 0; + + for (size_t i = 0; i < vector.GetBlockCount(); ++i) + { + VmaBlockMetadata* metadata = vector.GetBlock(i)->m_pMetadata; + + allocCount += metadata->GetAllocationCount(); + freeCount += metadata->GetFreeRegionsCount(); + state.avgFreeSize += metadata->GetSumFreeSize(); + state.avgAllocSize += metadata->GetSize(); + } + + state.avgAllocSize = (state.avgAllocSize - state.avgFreeSize) / allocCount; + state.avgFreeSize /= freeCount; +} + +bool VmaDefragmentationContext_T::MoveDataToFreeBlocks(VmaSuballocationType currentType, + VmaBlockVector& vector, size_t firstFreeBlock, + bool& texturePresent, bool& bufferPresent, bool& otherPresent) +{ + const size_t prevMoveCount = m_Moves.size(); + for (size_t i = firstFreeBlock ; i;) + { + VmaDeviceMemoryBlock* block = vector.GetBlock(--i); + VmaBlockMetadata* metadata = block->m_pMetadata; + + for (VmaAllocHandle handle = metadata->GetAllocationListBegin(); + handle != VK_NULL_HANDLE; + handle = metadata->GetNextAllocation(handle)) + { + MoveAllocationData moveData = GetMoveData(handle, metadata); + // Ignore newly created allocations by defragmentation algorithm + if (moveData.move.srcAllocation->GetUserData() == this) + continue; + switch (CheckCounters(moveData.move.srcAllocation->GetSize())) + { + case CounterStatus::Ignore: + continue; + case CounterStatus::End: + return true; + case CounterStatus::Pass: + break; + default: + VMA_ASSERT(0); + } + + // Move only single type of resources at once + if (!VmaIsBufferImageGranularityConflict(moveData.type, currentType)) + { + // Try to fit allocation into free blocks + if (AllocInOtherBlock(firstFreeBlock, vector.GetBlockCount(), moveData, vector)) + return false; + } + + if (!VmaIsBufferImageGranularityConflict(moveData.type, VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL)) + texturePresent = true; + else if (!VmaIsBufferImageGranularityConflict(moveData.type, VMA_SUBALLOCATION_TYPE_BUFFER)) + bufferPresent = true; + else + otherPresent = true; + } + } + return prevMoveCount == m_Moves.size(); +} +#endif // _VMA_DEFRAGMENTATION_CONTEXT_FUNCTIONS + +#ifndef _VMA_POOL_T_FUNCTIONS +VmaPool_T::VmaPool_T( + VmaAllocator hAllocator, + const VmaPoolCreateInfo& createInfo, + VkDeviceSize preferredBlockSize) + : m_BlockVector( + hAllocator, + this, // hParentPool + createInfo.memoryTypeIndex, + createInfo.blockSize != 0 ? createInfo.blockSize : preferredBlockSize, + createInfo.minBlockCount, + createInfo.maxBlockCount, + (createInfo.flags& VMA_POOL_CREATE_IGNORE_BUFFER_IMAGE_GRANULARITY_BIT) != 0 ? 1 : hAllocator->GetBufferImageGranularity(), + createInfo.blockSize != 0, // explicitBlockSize + createInfo.flags & VMA_POOL_CREATE_ALGORITHM_MASK, // algorithm + createInfo.priority, + VMA_MAX(hAllocator->GetMemoryTypeMinAlignment(createInfo.memoryTypeIndex), createInfo.minAllocationAlignment), + createInfo.pMemoryAllocateNext), + m_Id(0), + m_Name(VMA_NULL) {} + +VmaPool_T::~VmaPool_T() +{ + VMA_ASSERT(m_PrevPool == VMA_NULL && m_NextPool == VMA_NULL); + + const VkAllocationCallbacks* allocs = m_BlockVector.GetAllocator()->GetAllocationCallbacks(); + VmaFreeString(allocs, m_Name); +} + +void VmaPool_T::SetName(const char* pName) +{ + const VkAllocationCallbacks* allocs = m_BlockVector.GetAllocator()->GetAllocationCallbacks(); + VmaFreeString(allocs, m_Name); + + if (pName != VMA_NULL) + { + m_Name = VmaCreateStringCopy(allocs, pName); + } + else + { + m_Name = VMA_NULL; + } +} +#endif // _VMA_POOL_T_FUNCTIONS + +#ifndef _VMA_ALLOCATOR_T_FUNCTIONS +VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) : + m_UseMutex((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT) == 0), + m_VulkanApiVersion(pCreateInfo->vulkanApiVersion != 0 ? pCreateInfo->vulkanApiVersion : VK_API_VERSION_1_0), + m_UseKhrDedicatedAllocation((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT) != 0), + m_UseKhrBindMemory2((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT) != 0), + m_UseExtMemoryBudget((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT) != 0), + m_UseAmdDeviceCoherentMemory((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT) != 0), + m_UseKhrBufferDeviceAddress((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT) != 0), + m_UseExtMemoryPriority((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT) != 0), + m_UseKhrMaintenance4((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE4_BIT) != 0), + m_UseKhrMaintenance5((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT) != 0), + m_UseKhrExternalMemoryWin32((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_EXTERNAL_MEMORY_WIN32_BIT) != 0), + m_hDevice(pCreateInfo->device), + m_hInstance(pCreateInfo->instance), + m_AllocationCallbacksSpecified(pCreateInfo->pAllocationCallbacks != VMA_NULL), + m_AllocationCallbacks(pCreateInfo->pAllocationCallbacks ? + *pCreateInfo->pAllocationCallbacks : VmaEmptyAllocationCallbacks), + m_AllocationObjectAllocator(&m_AllocationCallbacks), + m_HeapSizeLimitMask(0), + m_DeviceMemoryCount(0), + m_PreferredLargeHeapBlockSize(0), + m_PhysicalDevice(pCreateInfo->physicalDevice), + m_GpuDefragmentationMemoryTypeBits(UINT32_MAX), + m_NextPoolId(0), + m_GlobalMemoryTypeBits(UINT32_MAX) +{ + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + m_UseKhrDedicatedAllocation = false; + m_UseKhrBindMemory2 = false; + } + + if(VMA_DEBUG_DETECT_CORRUPTION) + { + // Needs to be multiply of uint32_t size because we are going to write VMA_CORRUPTION_DETECTION_MAGIC_VALUE to it. + VMA_ASSERT(VMA_DEBUG_MARGIN % sizeof(uint32_t) == 0); + } + + VMA_ASSERT(pCreateInfo->physicalDevice && pCreateInfo->device && pCreateInfo->instance); + + if(m_VulkanApiVersion < VK_MAKE_VERSION(1, 1, 0)) + { +#if !(VMA_DEDICATED_ALLOCATION) + if((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT) != 0) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT set but required extensions are disabled by preprocessor macros."); + } +#endif +#if !(VMA_BIND_MEMORY2) + if((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT) != 0) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT set but required extension is disabled by preprocessor macros."); + } +#endif + } +#if !(VMA_MEMORY_BUDGET) + if((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT) != 0) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT set but required extension is disabled by preprocessor macros."); + } +#endif +#if !(VMA_BUFFER_DEVICE_ADDRESS) + if(m_UseKhrBufferDeviceAddress) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT is set but required extension or Vulkan 1.2 is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro."); + } +#endif +#if VMA_VULKAN_VERSION < 1003000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 3, 0)) + { + VMA_ASSERT(0 && "vulkanApiVersion >= VK_API_VERSION_1_3 but required Vulkan version is disabled by preprocessor macros."); + } +#endif +#if VMA_VULKAN_VERSION < 1002000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 2, 0)) + { + VMA_ASSERT(0 && "vulkanApiVersion >= VK_API_VERSION_1_2 but required Vulkan version is disabled by preprocessor macros."); + } +#endif +#if VMA_VULKAN_VERSION < 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + VMA_ASSERT(0 && "vulkanApiVersion >= VK_API_VERSION_1_1 but required Vulkan version is disabled by preprocessor macros."); + } +#endif +#if !(VMA_MEMORY_PRIORITY) + if(m_UseExtMemoryPriority) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT is set but required extension is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro."); + } +#endif +#if !(VMA_KHR_MAINTENANCE4) + if(m_UseKhrMaintenance4) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE4_BIT is set but required extension is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro."); + } +#endif +#if !(VMA_KHR_MAINTENANCE5) + if(m_UseKhrMaintenance5) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT is set but required extension is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro."); + } +#endif +#if !(VMA_KHR_MAINTENANCE5) + if(m_UseKhrMaintenance5) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT is set but required extension is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro."); + } +#endif + +#if !(VMA_EXTERNAL_MEMORY_WIN32) + if(m_UseKhrExternalMemoryWin32) + { + VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_KHR_EXTERNAL_MEMORY_WIN32_BIT is set but required extension is not available in your Vulkan header or its support in VMA has been disabled by a preprocessor macro."); + } +#endif + + memset(&m_DeviceMemoryCallbacks, 0 ,sizeof(m_DeviceMemoryCallbacks)); + memset(&m_PhysicalDeviceProperties, 0, sizeof(m_PhysicalDeviceProperties)); + memset(&m_MemProps, 0, sizeof(m_MemProps)); + + memset(&m_pBlockVectors, 0, sizeof(m_pBlockVectors)); + memset(&m_VulkanFunctions, 0, sizeof(m_VulkanFunctions)); + +#if VMA_EXTERNAL_MEMORY + memset(&m_TypeExternalMemoryHandleTypes, 0, sizeof(m_TypeExternalMemoryHandleTypes)); +#endif // #if VMA_EXTERNAL_MEMORY + + if(pCreateInfo->pDeviceMemoryCallbacks != VMA_NULL) + { + m_DeviceMemoryCallbacks.pUserData = pCreateInfo->pDeviceMemoryCallbacks->pUserData; + m_DeviceMemoryCallbacks.pfnAllocate = pCreateInfo->pDeviceMemoryCallbacks->pfnAllocate; + m_DeviceMemoryCallbacks.pfnFree = pCreateInfo->pDeviceMemoryCallbacks->pfnFree; + } + + ImportVulkanFunctions(pCreateInfo->pVulkanFunctions); + + (*m_VulkanFunctions.vkGetPhysicalDeviceProperties)(m_PhysicalDevice, &m_PhysicalDeviceProperties); + (*m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties)(m_PhysicalDevice, &m_MemProps); + + VMA_ASSERT(VmaIsPow2(VMA_MIN_ALIGNMENT)); + VMA_ASSERT(VmaIsPow2(VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY)); + VMA_ASSERT(VmaIsPow2(m_PhysicalDeviceProperties.limits.bufferImageGranularity)); + VMA_ASSERT(VmaIsPow2(m_PhysicalDeviceProperties.limits.nonCoherentAtomSize)); + + m_PreferredLargeHeapBlockSize = (pCreateInfo->preferredLargeHeapBlockSize != 0) ? + pCreateInfo->preferredLargeHeapBlockSize : static_cast(VMA_DEFAULT_LARGE_HEAP_BLOCK_SIZE); + + m_GlobalMemoryTypeBits = CalculateGlobalMemoryTypeBits(); + +#if VMA_EXTERNAL_MEMORY + if(pCreateInfo->pTypeExternalMemoryHandleTypes != VMA_NULL) + { + memcpy(m_TypeExternalMemoryHandleTypes, pCreateInfo->pTypeExternalMemoryHandleTypes, + sizeof(VkExternalMemoryHandleTypeFlagsKHR) * GetMemoryTypeCount()); + } +#endif // #if VMA_EXTERNAL_MEMORY + + if(pCreateInfo->pHeapSizeLimit != VMA_NULL) + { + for(uint32_t heapIndex = 0; heapIndex < GetMemoryHeapCount(); ++heapIndex) + { + const VkDeviceSize limit = pCreateInfo->pHeapSizeLimit[heapIndex]; + if(limit != VK_WHOLE_SIZE) + { + m_HeapSizeLimitMask |= 1u << heapIndex; + if(limit < m_MemProps.memoryHeaps[heapIndex].size) + { + m_MemProps.memoryHeaps[heapIndex].size = limit; + } + } + } + } + + for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) + { + // Create only supported types + if((m_GlobalMemoryTypeBits & (1u << memTypeIndex)) != 0) + { + const VkDeviceSize preferredBlockSize = CalcPreferredBlockSize(memTypeIndex); + m_pBlockVectors[memTypeIndex] = vma_new(this, VmaBlockVector)( + this, + VK_NULL_HANDLE, // hParentPool + memTypeIndex, + preferredBlockSize, + 0, + SIZE_MAX, + GetBufferImageGranularity(), + false, // explicitBlockSize + 0, // algorithm + 0.5f, // priority (0.5 is the default per Vulkan spec) + GetMemoryTypeMinAlignment(memTypeIndex), // minAllocationAlignment + VMA_NULL); // // pMemoryAllocateNext + // No need to call m_pBlockVectors[memTypeIndex][blockVectorTypeIndex]->CreateMinBlocks here, + // because minBlockCount is 0. + } + } +} + +VkResult VmaAllocator_T::Init(const VmaAllocatorCreateInfo* pCreateInfo) +{ + VkResult res = VK_SUCCESS; + +#if VMA_MEMORY_BUDGET + if(m_UseExtMemoryBudget) + { + UpdateVulkanBudget(); + } +#endif // #if VMA_MEMORY_BUDGET + + return res; +} + +VmaAllocator_T::~VmaAllocator_T() +{ + VMA_ASSERT(m_Pools.IsEmpty()); + + for(size_t memTypeIndex = GetMemoryTypeCount(); memTypeIndex--; ) + { + vma_delete(this, m_pBlockVectors[memTypeIndex]); + } +} + +void VmaAllocator_T::ImportVulkanFunctions(const VmaVulkanFunctions* pVulkanFunctions) +{ +#if VMA_STATIC_VULKAN_FUNCTIONS == 1 + ImportVulkanFunctions_Static(); +#endif + + if(pVulkanFunctions != VMA_NULL) + { + ImportVulkanFunctions_Custom(pVulkanFunctions); + } + +#if VMA_DYNAMIC_VULKAN_FUNCTIONS == 1 + ImportVulkanFunctions_Dynamic(); +#endif + + ValidateVulkanFunctions(); +} + +#if VMA_STATIC_VULKAN_FUNCTIONS == 1 + +void VmaAllocator_T::ImportVulkanFunctions_Static() +{ + // Vulkan 1.0 + m_VulkanFunctions.vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)vkGetInstanceProcAddr; + m_VulkanFunctions.vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetDeviceProcAddr; + m_VulkanFunctions.vkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties)vkGetPhysicalDeviceProperties; + m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties = (PFN_vkGetPhysicalDeviceMemoryProperties)vkGetPhysicalDeviceMemoryProperties; + m_VulkanFunctions.vkAllocateMemory = (PFN_vkAllocateMemory)vkAllocateMemory; + m_VulkanFunctions.vkFreeMemory = (PFN_vkFreeMemory)vkFreeMemory; + m_VulkanFunctions.vkMapMemory = (PFN_vkMapMemory)vkMapMemory; + m_VulkanFunctions.vkUnmapMemory = (PFN_vkUnmapMemory)vkUnmapMemory; + m_VulkanFunctions.vkFlushMappedMemoryRanges = (PFN_vkFlushMappedMemoryRanges)vkFlushMappedMemoryRanges; + m_VulkanFunctions.vkInvalidateMappedMemoryRanges = (PFN_vkInvalidateMappedMemoryRanges)vkInvalidateMappedMemoryRanges; + m_VulkanFunctions.vkBindBufferMemory = (PFN_vkBindBufferMemory)vkBindBufferMemory; + m_VulkanFunctions.vkBindImageMemory = (PFN_vkBindImageMemory)vkBindImageMemory; + m_VulkanFunctions.vkGetBufferMemoryRequirements = (PFN_vkGetBufferMemoryRequirements)vkGetBufferMemoryRequirements; + m_VulkanFunctions.vkGetImageMemoryRequirements = (PFN_vkGetImageMemoryRequirements)vkGetImageMemoryRequirements; + m_VulkanFunctions.vkCreateBuffer = (PFN_vkCreateBuffer)vkCreateBuffer; + m_VulkanFunctions.vkDestroyBuffer = (PFN_vkDestroyBuffer)vkDestroyBuffer; + m_VulkanFunctions.vkCreateImage = (PFN_vkCreateImage)vkCreateImage; + m_VulkanFunctions.vkDestroyImage = (PFN_vkDestroyImage)vkDestroyImage; + m_VulkanFunctions.vkCmdCopyBuffer = (PFN_vkCmdCopyBuffer)vkCmdCopyBuffer; + + // Vulkan 1.1 +#if VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR = (PFN_vkGetBufferMemoryRequirements2)vkGetBufferMemoryRequirements2; + m_VulkanFunctions.vkGetImageMemoryRequirements2KHR = (PFN_vkGetImageMemoryRequirements2)vkGetImageMemoryRequirements2; + m_VulkanFunctions.vkBindBufferMemory2KHR = (PFN_vkBindBufferMemory2)vkBindBufferMemory2; + m_VulkanFunctions.vkBindImageMemory2KHR = (PFN_vkBindImageMemory2)vkBindImageMemory2; + } +#endif + +#if VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR = (PFN_vkGetPhysicalDeviceMemoryProperties2)vkGetPhysicalDeviceMemoryProperties2; + } +#endif + +#if VMA_VULKAN_VERSION >= 1003000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 3, 0)) + { + m_VulkanFunctions.vkGetDeviceBufferMemoryRequirements = (PFN_vkGetDeviceBufferMemoryRequirements)vkGetDeviceBufferMemoryRequirements; + m_VulkanFunctions.vkGetDeviceImageMemoryRequirements = (PFN_vkGetDeviceImageMemoryRequirements)vkGetDeviceImageMemoryRequirements; + } +#endif +} + +#endif // VMA_STATIC_VULKAN_FUNCTIONS == 1 + +void VmaAllocator_T::ImportVulkanFunctions_Custom(const VmaVulkanFunctions* pVulkanFunctions) +{ + VMA_ASSERT(pVulkanFunctions != VMA_NULL); + +#define VMA_COPY_IF_NOT_NULL(funcName) \ + if(pVulkanFunctions->funcName != VMA_NULL) m_VulkanFunctions.funcName = pVulkanFunctions->funcName; + + VMA_COPY_IF_NOT_NULL(vkGetInstanceProcAddr); + VMA_COPY_IF_NOT_NULL(vkGetDeviceProcAddr); + VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceProperties); + VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties); + VMA_COPY_IF_NOT_NULL(vkAllocateMemory); + VMA_COPY_IF_NOT_NULL(vkFreeMemory); + VMA_COPY_IF_NOT_NULL(vkMapMemory); + VMA_COPY_IF_NOT_NULL(vkUnmapMemory); + VMA_COPY_IF_NOT_NULL(vkFlushMappedMemoryRanges); + VMA_COPY_IF_NOT_NULL(vkInvalidateMappedMemoryRanges); + VMA_COPY_IF_NOT_NULL(vkBindBufferMemory); + VMA_COPY_IF_NOT_NULL(vkBindImageMemory); + VMA_COPY_IF_NOT_NULL(vkGetBufferMemoryRequirements); + VMA_COPY_IF_NOT_NULL(vkGetImageMemoryRequirements); + VMA_COPY_IF_NOT_NULL(vkCreateBuffer); + VMA_COPY_IF_NOT_NULL(vkDestroyBuffer); + VMA_COPY_IF_NOT_NULL(vkCreateImage); + VMA_COPY_IF_NOT_NULL(vkDestroyImage); + VMA_COPY_IF_NOT_NULL(vkCmdCopyBuffer); + +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + VMA_COPY_IF_NOT_NULL(vkGetBufferMemoryRequirements2KHR); + VMA_COPY_IF_NOT_NULL(vkGetImageMemoryRequirements2KHR); +#endif + +#if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000 + VMA_COPY_IF_NOT_NULL(vkBindBufferMemory2KHR); + VMA_COPY_IF_NOT_NULL(vkBindImageMemory2KHR); +#endif + +#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 + VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties2KHR); +#endif + +#if VMA_KHR_MAINTENANCE4 || VMA_VULKAN_VERSION >= 1003000 + VMA_COPY_IF_NOT_NULL(vkGetDeviceBufferMemoryRequirements); + VMA_COPY_IF_NOT_NULL(vkGetDeviceImageMemoryRequirements); +#endif +#if VMA_EXTERNAL_MEMORY_WIN32 + VMA_COPY_IF_NOT_NULL(vkGetMemoryWin32HandleKHR); +#endif +#undef VMA_COPY_IF_NOT_NULL +} + +#if VMA_DYNAMIC_VULKAN_FUNCTIONS == 1 + +void VmaAllocator_T::ImportVulkanFunctions_Dynamic() +{ + VMA_ASSERT(m_VulkanFunctions.vkGetInstanceProcAddr && m_VulkanFunctions.vkGetDeviceProcAddr && + "To use VMA_DYNAMIC_VULKAN_FUNCTIONS in new versions of VMA you now have to pass " + "VmaVulkanFunctions::vkGetInstanceProcAddr and vkGetDeviceProcAddr as VmaAllocatorCreateInfo::pVulkanFunctions. " + "Other members can be null."); + +#define VMA_FETCH_INSTANCE_FUNC(memberName, functionPointerType, functionNameString) \ + if(m_VulkanFunctions.memberName == VMA_NULL) \ + m_VulkanFunctions.memberName = \ + (functionPointerType)m_VulkanFunctions.vkGetInstanceProcAddr(m_hInstance, functionNameString); +#define VMA_FETCH_DEVICE_FUNC(memberName, functionPointerType, functionNameString) \ + if(m_VulkanFunctions.memberName == VMA_NULL) \ + m_VulkanFunctions.memberName = \ + (functionPointerType)m_VulkanFunctions.vkGetDeviceProcAddr(m_hDevice, functionNameString); + + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceProperties, PFN_vkGetPhysicalDeviceProperties, "vkGetPhysicalDeviceProperties"); + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties, PFN_vkGetPhysicalDeviceMemoryProperties, "vkGetPhysicalDeviceMemoryProperties"); + VMA_FETCH_DEVICE_FUNC(vkAllocateMemory, PFN_vkAllocateMemory, "vkAllocateMemory"); + VMA_FETCH_DEVICE_FUNC(vkFreeMemory, PFN_vkFreeMemory, "vkFreeMemory"); + VMA_FETCH_DEVICE_FUNC(vkMapMemory, PFN_vkMapMemory, "vkMapMemory"); + VMA_FETCH_DEVICE_FUNC(vkUnmapMemory, PFN_vkUnmapMemory, "vkUnmapMemory"); + VMA_FETCH_DEVICE_FUNC(vkFlushMappedMemoryRanges, PFN_vkFlushMappedMemoryRanges, "vkFlushMappedMemoryRanges"); + VMA_FETCH_DEVICE_FUNC(vkInvalidateMappedMemoryRanges, PFN_vkInvalidateMappedMemoryRanges, "vkInvalidateMappedMemoryRanges"); + VMA_FETCH_DEVICE_FUNC(vkBindBufferMemory, PFN_vkBindBufferMemory, "vkBindBufferMemory"); + VMA_FETCH_DEVICE_FUNC(vkBindImageMemory, PFN_vkBindImageMemory, "vkBindImageMemory"); + VMA_FETCH_DEVICE_FUNC(vkGetBufferMemoryRequirements, PFN_vkGetBufferMemoryRequirements, "vkGetBufferMemoryRequirements"); + VMA_FETCH_DEVICE_FUNC(vkGetImageMemoryRequirements, PFN_vkGetImageMemoryRequirements, "vkGetImageMemoryRequirements"); + VMA_FETCH_DEVICE_FUNC(vkCreateBuffer, PFN_vkCreateBuffer, "vkCreateBuffer"); + VMA_FETCH_DEVICE_FUNC(vkDestroyBuffer, PFN_vkDestroyBuffer, "vkDestroyBuffer"); + VMA_FETCH_DEVICE_FUNC(vkCreateImage, PFN_vkCreateImage, "vkCreateImage"); + VMA_FETCH_DEVICE_FUNC(vkDestroyImage, PFN_vkDestroyImage, "vkDestroyImage"); + VMA_FETCH_DEVICE_FUNC(vkCmdCopyBuffer, PFN_vkCmdCopyBuffer, "vkCmdCopyBuffer"); + +#if VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + VMA_FETCH_DEVICE_FUNC(vkGetBufferMemoryRequirements2KHR, PFN_vkGetBufferMemoryRequirements2, "vkGetBufferMemoryRequirements2"); + VMA_FETCH_DEVICE_FUNC(vkGetImageMemoryRequirements2KHR, PFN_vkGetImageMemoryRequirements2, "vkGetImageMemoryRequirements2"); + VMA_FETCH_DEVICE_FUNC(vkBindBufferMemory2KHR, PFN_vkBindBufferMemory2, "vkBindBufferMemory2"); + VMA_FETCH_DEVICE_FUNC(vkBindImageMemory2KHR, PFN_vkBindImageMemory2, "vkBindImageMemory2"); + } +#endif + +#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2"); + } + else if(m_UseExtMemoryBudget) + { + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2KHR"); + } +#endif + +#if VMA_DEDICATED_ALLOCATION + if(m_UseKhrDedicatedAllocation) + { + VMA_FETCH_DEVICE_FUNC(vkGetBufferMemoryRequirements2KHR, PFN_vkGetBufferMemoryRequirements2KHR, "vkGetBufferMemoryRequirements2KHR"); + VMA_FETCH_DEVICE_FUNC(vkGetImageMemoryRequirements2KHR, PFN_vkGetImageMemoryRequirements2KHR, "vkGetImageMemoryRequirements2KHR"); + } +#endif + +#if VMA_BIND_MEMORY2 + if(m_UseKhrBindMemory2) + { + VMA_FETCH_DEVICE_FUNC(vkBindBufferMemory2KHR, PFN_vkBindBufferMemory2KHR, "vkBindBufferMemory2KHR"); + VMA_FETCH_DEVICE_FUNC(vkBindImageMemory2KHR, PFN_vkBindImageMemory2KHR, "vkBindImageMemory2KHR"); + } +#endif // #if VMA_BIND_MEMORY2 + +#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2"); + } + else if(m_UseExtMemoryBudget) + { + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2KHR"); + } +#endif // #if VMA_MEMORY_BUDGET + +#if VMA_VULKAN_VERSION >= 1003000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 3, 0)) + { + VMA_FETCH_DEVICE_FUNC(vkGetDeviceBufferMemoryRequirements, PFN_vkGetDeviceBufferMemoryRequirements, "vkGetDeviceBufferMemoryRequirements"); + VMA_FETCH_DEVICE_FUNC(vkGetDeviceImageMemoryRequirements, PFN_vkGetDeviceImageMemoryRequirements, "vkGetDeviceImageMemoryRequirements"); + } +#endif +#if VMA_KHR_MAINTENANCE4 + if(m_UseKhrMaintenance4) + { + VMA_FETCH_DEVICE_FUNC(vkGetDeviceBufferMemoryRequirements, PFN_vkGetDeviceBufferMemoryRequirementsKHR, "vkGetDeviceBufferMemoryRequirementsKHR"); + VMA_FETCH_DEVICE_FUNC(vkGetDeviceImageMemoryRequirements, PFN_vkGetDeviceImageMemoryRequirementsKHR, "vkGetDeviceImageMemoryRequirementsKHR"); + } +#endif +#if VMA_EXTERNAL_MEMORY_WIN32 + if (m_UseKhrExternalMemoryWin32) + { + VMA_FETCH_DEVICE_FUNC(vkGetMemoryWin32HandleKHR, PFN_vkGetMemoryWin32HandleKHR, "vkGetMemoryWin32HandleKHR"); + } +#endif +#undef VMA_FETCH_DEVICE_FUNC +#undef VMA_FETCH_INSTANCE_FUNC +} + +#endif // VMA_DYNAMIC_VULKAN_FUNCTIONS == 1 + +void VmaAllocator_T::ValidateVulkanFunctions() +{ + VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceProperties != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkAllocateMemory != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkFreeMemory != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkMapMemory != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkUnmapMemory != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkFlushMappedMemoryRanges != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkInvalidateMappedMemoryRanges != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkBindBufferMemory != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkBindImageMemory != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkGetBufferMemoryRequirements != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkGetImageMemoryRequirements != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkCreateBuffer != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkDestroyBuffer != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkCreateImage != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkDestroyImage != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkCmdCopyBuffer != VMA_NULL); + +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0) || m_UseKhrDedicatedAllocation) + { + VMA_ASSERT(m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkGetImageMemoryRequirements2KHR != VMA_NULL); + } +#endif + +#if VMA_BIND_MEMORY2 || VMA_VULKAN_VERSION >= 1001000 + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0) || m_UseKhrBindMemory2) + { + VMA_ASSERT(m_VulkanFunctions.vkBindBufferMemory2KHR != VMA_NULL); + VMA_ASSERT(m_VulkanFunctions.vkBindImageMemory2KHR != VMA_NULL); + } +#endif + +#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 + if(m_UseExtMemoryBudget || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR != VMA_NULL); + } +#endif +#if VMA_EXTERNAL_MEMORY_WIN32 + if (m_UseKhrExternalMemoryWin32) + { + VMA_ASSERT(m_VulkanFunctions.vkGetMemoryWin32HandleKHR != VMA_NULL); + } +#endif + + // Not validating these due to suspected driver bugs with these function + // pointers being null despite correct extension or Vulkan version is enabled. + // See issue #397. Their usage in VMA is optional anyway. + // + // VMA_ASSERT(m_VulkanFunctions.vkGetDeviceBufferMemoryRequirements != VMA_NULL); + // VMA_ASSERT(m_VulkanFunctions.vkGetDeviceImageMemoryRequirements != VMA_NULL); +} + +VkDeviceSize VmaAllocator_T::CalcPreferredBlockSize(uint32_t memTypeIndex) +{ + const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memTypeIndex); + const VkDeviceSize heapSize = m_MemProps.memoryHeaps[heapIndex].size; + const bool isSmallHeap = heapSize <= VMA_SMALL_HEAP_MAX_SIZE; + return VmaAlignUp(isSmallHeap ? (heapSize / 8) : m_PreferredLargeHeapBlockSize, (VkDeviceSize)32); +} + +VkResult VmaAllocator_T::AllocateMemoryOfType( + VmaPool pool, + VkDeviceSize size, + VkDeviceSize alignment, + bool dedicatedPreferred, + VkBuffer dedicatedBuffer, + VkImage dedicatedImage, + VmaBufferImageUsage dedicatedBufferImageUsage, + const VmaAllocationCreateInfo& createInfo, + uint32_t memTypeIndex, + VmaSuballocationType suballocType, + VmaDedicatedAllocationList& dedicatedAllocations, + VmaBlockVector& blockVector, + size_t allocationCount, + VmaAllocation* pAllocations) +{ + VMA_ASSERT(pAllocations != VMA_NULL); + VMA_DEBUG_LOG_FORMAT(" AllocateMemory: MemoryTypeIndex=%" PRIu32 ", AllocationCount=%zu, Size=%" PRIu64, memTypeIndex, allocationCount, size); + + VmaAllocationCreateInfo finalCreateInfo = createInfo; + VkResult res = CalcMemTypeParams( + finalCreateInfo, + memTypeIndex, + size, + allocationCount); + if(res != VK_SUCCESS) + return res; + + if((finalCreateInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) != 0) + { + return AllocateDedicatedMemory( + pool, + size, + suballocType, + dedicatedAllocations, + memTypeIndex, + (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0, + (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0, + (finalCreateInfo.flags & + (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != 0, + (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT) != 0, + finalCreateInfo.pUserData, + finalCreateInfo.priority, + dedicatedBuffer, + dedicatedImage, + dedicatedBufferImageUsage, + allocationCount, + pAllocations, + blockVector.GetAllocationNextPtr()); + } + else + { + const bool canAllocateDedicated = + (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) == 0 && + (pool == VK_NULL_HANDLE || !blockVector.HasExplicitBlockSize()); + + if(canAllocateDedicated) + { + // Heuristics: Allocate dedicated memory if requested size if greater than half of preferred block size. + if(size > blockVector.GetPreferredBlockSize() / 2) + { + dedicatedPreferred = true; + } + // Protection against creating each allocation as dedicated when we reach or exceed heap size/budget, + // which can quickly deplete maxMemoryAllocationCount: Don't prefer dedicated allocations when above + // 3/4 of the maximum allocation count. + if(m_PhysicalDeviceProperties.limits.maxMemoryAllocationCount < UINT32_MAX / 4 && + m_DeviceMemoryCount.load() > m_PhysicalDeviceProperties.limits.maxMemoryAllocationCount * 3 / 4) + { + dedicatedPreferred = false; + } + + if(dedicatedPreferred) + { + res = AllocateDedicatedMemory( + pool, + size, + suballocType, + dedicatedAllocations, + memTypeIndex, + (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0, + (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0, + (finalCreateInfo.flags & + (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != 0, + (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT) != 0, + finalCreateInfo.pUserData, + finalCreateInfo.priority, + dedicatedBuffer, + dedicatedImage, + dedicatedBufferImageUsage, + allocationCount, + pAllocations, + blockVector.GetAllocationNextPtr()); + if(res == VK_SUCCESS) + { + // Succeeded: AllocateDedicatedMemory function already filled pMemory, nothing more to do here. + VMA_DEBUG_LOG(" Allocated as DedicatedMemory"); + return VK_SUCCESS; + } + } + } + + res = blockVector.Allocate( + size, + alignment, + finalCreateInfo, + suballocType, + allocationCount, + pAllocations); + if(res == VK_SUCCESS) + return VK_SUCCESS; + + // Try dedicated memory. + if(canAllocateDedicated && !dedicatedPreferred) + { + res = AllocateDedicatedMemory( + pool, + size, + suballocType, + dedicatedAllocations, + memTypeIndex, + (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0, + (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0, + (finalCreateInfo.flags & + (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != 0, + (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_CAN_ALIAS_BIT) != 0, + finalCreateInfo.pUserData, + finalCreateInfo.priority, + dedicatedBuffer, + dedicatedImage, + dedicatedBufferImageUsage, + allocationCount, + pAllocations, + blockVector.GetAllocationNextPtr()); + if(res == VK_SUCCESS) + { + // Succeeded: AllocateDedicatedMemory function already filled pMemory, nothing more to do here. + VMA_DEBUG_LOG(" Allocated as DedicatedMemory"); + return VK_SUCCESS; + } + } + // Everything failed: Return error code. + VMA_DEBUG_LOG(" vkAllocateMemory FAILED"); + return res; + } +} + +VkResult VmaAllocator_T::AllocateDedicatedMemory( + VmaPool pool, + VkDeviceSize size, + VmaSuballocationType suballocType, + VmaDedicatedAllocationList& dedicatedAllocations, + uint32_t memTypeIndex, + bool map, + bool isUserDataString, + bool isMappingAllowed, + bool canAliasMemory, + void* pUserData, + float priority, + VkBuffer dedicatedBuffer, + VkImage dedicatedImage, + VmaBufferImageUsage dedicatedBufferImageUsage, + size_t allocationCount, + VmaAllocation* pAllocations, + const void* pNextChain) +{ + VMA_ASSERT(allocationCount > 0 && pAllocations); + + VkMemoryAllocateInfo allocInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; + allocInfo.memoryTypeIndex = memTypeIndex; + allocInfo.allocationSize = size; + allocInfo.pNext = pNextChain; + +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + VkMemoryDedicatedAllocateInfoKHR dedicatedAllocInfo = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR }; + if(!canAliasMemory) + { + if(m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + if(dedicatedBuffer != VK_NULL_HANDLE) + { + VMA_ASSERT(dedicatedImage == VK_NULL_HANDLE); + dedicatedAllocInfo.buffer = dedicatedBuffer; + VmaPnextChainPushFront(&allocInfo, &dedicatedAllocInfo); + } + else if(dedicatedImage != VK_NULL_HANDLE) + { + dedicatedAllocInfo.image = dedicatedImage; + VmaPnextChainPushFront(&allocInfo, &dedicatedAllocInfo); + } + } + } +#endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + +#if VMA_BUFFER_DEVICE_ADDRESS + VkMemoryAllocateFlagsInfoKHR allocFlagsInfo = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO_KHR }; + if(m_UseKhrBufferDeviceAddress) + { + bool canContainBufferWithDeviceAddress = true; + if(dedicatedBuffer != VK_NULL_HANDLE) + { + canContainBufferWithDeviceAddress = dedicatedBufferImageUsage == VmaBufferImageUsage::UNKNOWN || + dedicatedBufferImageUsage.Contains(VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_EXT); + } + else if(dedicatedImage != VK_NULL_HANDLE) + { + canContainBufferWithDeviceAddress = false; + } + if(canContainBufferWithDeviceAddress) + { + allocFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR; + VmaPnextChainPushFront(&allocInfo, &allocFlagsInfo); + } + } +#endif // #if VMA_BUFFER_DEVICE_ADDRESS + +#if VMA_MEMORY_PRIORITY + VkMemoryPriorityAllocateInfoEXT priorityInfo = { VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT }; + if(m_UseExtMemoryPriority) + { + VMA_ASSERT(priority >= 0.f && priority <= 1.f); + priorityInfo.priority = priority; + VmaPnextChainPushFront(&allocInfo, &priorityInfo); + } +#endif // #if VMA_MEMORY_PRIORITY + +#if VMA_EXTERNAL_MEMORY + // Attach VkExportMemoryAllocateInfoKHR if necessary. + VkExportMemoryAllocateInfoKHR exportMemoryAllocInfo = { VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR }; + exportMemoryAllocInfo.handleTypes = GetExternalMemoryHandleTypeFlags(memTypeIndex); + if(exportMemoryAllocInfo.handleTypes != 0) + { + VmaPnextChainPushFront(&allocInfo, &exportMemoryAllocInfo); + } +#endif // #if VMA_EXTERNAL_MEMORY + + size_t allocIndex; + VkResult res = VK_SUCCESS; + for(allocIndex = 0; allocIndex < allocationCount; ++allocIndex) + { + res = AllocateDedicatedMemoryPage( + pool, + size, + suballocType, + memTypeIndex, + allocInfo, + map, + isUserDataString, + isMappingAllowed, + pUserData, + pAllocations + allocIndex); + if(res != VK_SUCCESS) + { + break; + } + } + + if(res == VK_SUCCESS) + { + for (allocIndex = 0; allocIndex < allocationCount; ++allocIndex) + { + dedicatedAllocations.Register(pAllocations[allocIndex]); + } + VMA_DEBUG_LOG_FORMAT(" Allocated DedicatedMemory Count=%zu, MemoryTypeIndex=#%" PRIu32, allocationCount, memTypeIndex); + } + else + { + // Free all already created allocations. + while(allocIndex--) + { + VmaAllocation currAlloc = pAllocations[allocIndex]; + VkDeviceMemory hMemory = currAlloc->GetMemory(); + + /* + There is no need to call this, because Vulkan spec allows to skip vkUnmapMemory + before vkFreeMemory. + + if(currAlloc->GetMappedData() != VMA_NULL) + { + (*m_VulkanFunctions.vkUnmapMemory)(m_hDevice, hMemory); + } + */ + + FreeVulkanMemory(memTypeIndex, currAlloc->GetSize(), hMemory); + m_Budget.RemoveAllocation(MemoryTypeIndexToHeapIndex(memTypeIndex), currAlloc->GetSize()); + m_AllocationObjectAllocator.Free(currAlloc); + } + + memset(pAllocations, 0, sizeof(VmaAllocation) * allocationCount); + } + + return res; +} + +VkResult VmaAllocator_T::AllocateDedicatedMemoryPage( + VmaPool pool, + VkDeviceSize size, + VmaSuballocationType suballocType, + uint32_t memTypeIndex, + const VkMemoryAllocateInfo& allocInfo, + bool map, + bool isUserDataString, + bool isMappingAllowed, + void* pUserData, + VmaAllocation* pAllocation) +{ + VkDeviceMemory hMemory = VK_NULL_HANDLE; + VkResult res = AllocateVulkanMemory(&allocInfo, &hMemory); + if(res < 0) + { + VMA_DEBUG_LOG(" vkAllocateMemory FAILED"); + return res; + } + + void* pMappedData = VMA_NULL; + if(map) + { + res = (*m_VulkanFunctions.vkMapMemory)( + m_hDevice, + hMemory, + 0, + VK_WHOLE_SIZE, + 0, + &pMappedData); + if(res < 0) + { + VMA_DEBUG_LOG(" vkMapMemory FAILED"); + FreeVulkanMemory(memTypeIndex, size, hMemory); + return res; + } + } + + *pAllocation = m_AllocationObjectAllocator.Allocate(isMappingAllowed); + (*pAllocation)->InitDedicatedAllocation(this, pool, memTypeIndex, hMemory, suballocType, pMappedData, size); + if (isUserDataString) + (*pAllocation)->SetName(this, (const char*)pUserData); + else + (*pAllocation)->SetUserData(this, pUserData); + m_Budget.AddAllocation(MemoryTypeIndexToHeapIndex(memTypeIndex), size); + if(VMA_DEBUG_INITIALIZE_ALLOCATIONS) + { + FillAllocation(*pAllocation, VMA_ALLOCATION_FILL_PATTERN_CREATED); + } + + return VK_SUCCESS; +} + +void VmaAllocator_T::GetBufferMemoryRequirements( + VkBuffer hBuffer, + VkMemoryRequirements& memReq, + bool& requiresDedicatedAllocation, + bool& prefersDedicatedAllocation) const +{ +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + if(m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + VkBufferMemoryRequirementsInfo2KHR memReqInfo = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2_KHR }; + memReqInfo.buffer = hBuffer; + + VkMemoryDedicatedRequirementsKHR memDedicatedReq = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR }; + + VkMemoryRequirements2KHR memReq2 = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR }; + VmaPnextChainPushFront(&memReq2, &memDedicatedReq); + + (*m_VulkanFunctions.vkGetBufferMemoryRequirements2KHR)(m_hDevice, &memReqInfo, &memReq2); + + memReq = memReq2.memoryRequirements; + requiresDedicatedAllocation = (memDedicatedReq.requiresDedicatedAllocation != VK_FALSE); + prefersDedicatedAllocation = (memDedicatedReq.prefersDedicatedAllocation != VK_FALSE); + } + else +#endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + { + (*m_VulkanFunctions.vkGetBufferMemoryRequirements)(m_hDevice, hBuffer, &memReq); + requiresDedicatedAllocation = false; + prefersDedicatedAllocation = false; + } +} + +void VmaAllocator_T::GetImageMemoryRequirements( + VkImage hImage, + VkMemoryRequirements& memReq, + bool& requiresDedicatedAllocation, + bool& prefersDedicatedAllocation) const +{ +#if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + if(m_UseKhrDedicatedAllocation || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + VkImageMemoryRequirementsInfo2KHR memReqInfo = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2_KHR }; + memReqInfo.image = hImage; + + VkMemoryDedicatedRequirementsKHR memDedicatedReq = { VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR }; + + VkMemoryRequirements2KHR memReq2 = { VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR }; + VmaPnextChainPushFront(&memReq2, &memDedicatedReq); + + (*m_VulkanFunctions.vkGetImageMemoryRequirements2KHR)(m_hDevice, &memReqInfo, &memReq2); + + memReq = memReq2.memoryRequirements; + requiresDedicatedAllocation = (memDedicatedReq.requiresDedicatedAllocation != VK_FALSE); + prefersDedicatedAllocation = (memDedicatedReq.prefersDedicatedAllocation != VK_FALSE); + } + else +#endif // #if VMA_DEDICATED_ALLOCATION || VMA_VULKAN_VERSION >= 1001000 + { + (*m_VulkanFunctions.vkGetImageMemoryRequirements)(m_hDevice, hImage, &memReq); + requiresDedicatedAllocation = false; + prefersDedicatedAllocation = false; + } +} + +VkResult VmaAllocator_T::FindMemoryTypeIndex( + uint32_t memoryTypeBits, + const VmaAllocationCreateInfo* pAllocationCreateInfo, + VmaBufferImageUsage bufImgUsage, + uint32_t* pMemoryTypeIndex) const +{ + memoryTypeBits &= GetGlobalMemoryTypeBits(); + + if(pAllocationCreateInfo->memoryTypeBits != 0) + { + memoryTypeBits &= pAllocationCreateInfo->memoryTypeBits; + } + + VkMemoryPropertyFlags requiredFlags = 0, preferredFlags = 0, notPreferredFlags = 0; + if(!FindMemoryPreferences( + IsIntegratedGpu(), + *pAllocationCreateInfo, + bufImgUsage, + requiredFlags, preferredFlags, notPreferredFlags)) + { + return VK_ERROR_FEATURE_NOT_PRESENT; + } + + *pMemoryTypeIndex = UINT32_MAX; + uint32_t minCost = UINT32_MAX; + for(uint32_t memTypeIndex = 0, memTypeBit = 1; + memTypeIndex < GetMemoryTypeCount(); + ++memTypeIndex, memTypeBit <<= 1) + { + // This memory type is acceptable according to memoryTypeBits bitmask. + if((memTypeBit & memoryTypeBits) != 0) + { + const VkMemoryPropertyFlags currFlags = + m_MemProps.memoryTypes[memTypeIndex].propertyFlags; + // This memory type contains requiredFlags. + if((requiredFlags & ~currFlags) == 0) + { + // Calculate cost as number of bits from preferredFlags not present in this memory type. + uint32_t currCost = VMA_COUNT_BITS_SET(preferredFlags & ~currFlags) + + VMA_COUNT_BITS_SET(currFlags & notPreferredFlags); + // Remember memory type with lowest cost. + if(currCost < minCost) + { + *pMemoryTypeIndex = memTypeIndex; + if(currCost == 0) + { + return VK_SUCCESS; + } + minCost = currCost; + } + } + } + } + return (*pMemoryTypeIndex != UINT32_MAX) ? VK_SUCCESS : VK_ERROR_FEATURE_NOT_PRESENT; +} + +VkResult VmaAllocator_T::CalcMemTypeParams( + VmaAllocationCreateInfo& inoutCreateInfo, + uint32_t memTypeIndex, + VkDeviceSize size, + size_t allocationCount) +{ + // If memory type is not HOST_VISIBLE, disable MAPPED. + if((inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0 && + (m_MemProps.memoryTypes[memTypeIndex].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0) + { + inoutCreateInfo.flags &= ~VMA_ALLOCATION_CREATE_MAPPED_BIT; + } + + if((inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) != 0 && + (inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) != 0) + { + const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memTypeIndex); + VmaBudget heapBudget = {}; + GetHeapBudgets(&heapBudget, heapIndex, 1); + if(heapBudget.usage + size * allocationCount > heapBudget.budget) + { + return VK_ERROR_OUT_OF_DEVICE_MEMORY; + } + } + return VK_SUCCESS; +} + +VkResult VmaAllocator_T::CalcAllocationParams( + VmaAllocationCreateInfo& inoutCreateInfo, + bool dedicatedRequired, + bool dedicatedPreferred) +{ + VMA_ASSERT((inoutCreateInfo.flags & + (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != + (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT) && + "Specifying both flags VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT and VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT is incorrect."); + VMA_ASSERT((((inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT) == 0 || + (inoutCreateInfo.flags & (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != 0)) && + "Specifying VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT requires also VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT."); + if(inoutCreateInfo.usage == VMA_MEMORY_USAGE_AUTO || inoutCreateInfo.usage == VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE || inoutCreateInfo.usage == VMA_MEMORY_USAGE_AUTO_PREFER_HOST) + { + if((inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0) + { + VMA_ASSERT((inoutCreateInfo.flags & (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) != 0 && + "When using VMA_ALLOCATION_CREATE_MAPPED_BIT and usage = VMA_MEMORY_USAGE_AUTO*, you must also specify VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT."); + } + } + + // If memory is lazily allocated, it should be always dedicated. + if(dedicatedRequired || + inoutCreateInfo.usage == VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED) + { + inoutCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + } + + if(inoutCreateInfo.pool != VK_NULL_HANDLE) + { + if(inoutCreateInfo.pool->m_BlockVector.HasExplicitBlockSize() && + (inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) != 0) + { + VMA_ASSERT(0 && "Specifying VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT while current custom pool doesn't support dedicated allocations."); + return VK_ERROR_FEATURE_NOT_PRESENT; + } + inoutCreateInfo.priority = inoutCreateInfo.pool->m_BlockVector.GetPriority(); + } + + if((inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT) != 0 && + (inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) != 0) + { + VMA_ASSERT(0 && "Specifying VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT together with VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT makes no sense."); + return VK_ERROR_FEATURE_NOT_PRESENT; + } + + if(VMA_DEBUG_ALWAYS_DEDICATED_MEMORY && + (inoutCreateInfo.flags & VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT) != 0) + { + inoutCreateInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; + } + + // Non-auto USAGE values imply HOST_ACCESS flags. + // And so does VMA_MEMORY_USAGE_UNKNOWN because it is used with custom pools. + // Which specific flag is used doesn't matter. They change things only when used with VMA_MEMORY_USAGE_AUTO*. + // Otherwise they just protect from assert on mapping. + if(inoutCreateInfo.usage != VMA_MEMORY_USAGE_AUTO && + inoutCreateInfo.usage != VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE && + inoutCreateInfo.usage != VMA_MEMORY_USAGE_AUTO_PREFER_HOST) + { + if((inoutCreateInfo.flags & (VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT)) == 0) + { + inoutCreateInfo.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; + } + } + + return VK_SUCCESS; +} + +VkResult VmaAllocator_T::AllocateMemory( + const VkMemoryRequirements& vkMemReq, + bool requiresDedicatedAllocation, + bool prefersDedicatedAllocation, + VkBuffer dedicatedBuffer, + VkImage dedicatedImage, + VmaBufferImageUsage dedicatedBufferImageUsage, + const VmaAllocationCreateInfo& createInfo, + VmaSuballocationType suballocType, + size_t allocationCount, + VmaAllocation* pAllocations) +{ + memset(pAllocations, 0, sizeof(VmaAllocation) * allocationCount); + + VMA_ASSERT(VmaIsPow2(vkMemReq.alignment)); + + if(vkMemReq.size == 0) + { + return VK_ERROR_INITIALIZATION_FAILED; + } + + VmaAllocationCreateInfo createInfoFinal = createInfo; + VkResult res = CalcAllocationParams(createInfoFinal, requiresDedicatedAllocation, prefersDedicatedAllocation); + if(res != VK_SUCCESS) + return res; + + if(createInfoFinal.pool != VK_NULL_HANDLE) + { + VmaBlockVector& blockVector = createInfoFinal.pool->m_BlockVector; + return AllocateMemoryOfType( + createInfoFinal.pool, + vkMemReq.size, + vkMemReq.alignment, + prefersDedicatedAllocation, + dedicatedBuffer, + dedicatedImage, + dedicatedBufferImageUsage, + createInfoFinal, + blockVector.GetMemoryTypeIndex(), + suballocType, + createInfoFinal.pool->m_DedicatedAllocations, + blockVector, + allocationCount, + pAllocations); + } + else + { + // Bit mask of memory Vulkan types acceptable for this allocation. + uint32_t memoryTypeBits = vkMemReq.memoryTypeBits; + uint32_t memTypeIndex = UINT32_MAX; + res = FindMemoryTypeIndex(memoryTypeBits, &createInfoFinal, dedicatedBufferImageUsage, &memTypeIndex); + // Can't find any single memory type matching requirements. res is VK_ERROR_FEATURE_NOT_PRESENT. + if(res != VK_SUCCESS) + return res; + do + { + VmaBlockVector* blockVector = m_pBlockVectors[memTypeIndex]; + VMA_ASSERT(blockVector && "Trying to use unsupported memory type!"); + res = AllocateMemoryOfType( + VK_NULL_HANDLE, + vkMemReq.size, + vkMemReq.alignment, + requiresDedicatedAllocation || prefersDedicatedAllocation, + dedicatedBuffer, + dedicatedImage, + dedicatedBufferImageUsage, + createInfoFinal, + memTypeIndex, + suballocType, + m_DedicatedAllocations[memTypeIndex], + *blockVector, + allocationCount, + pAllocations); + // Allocation succeeded + if(res == VK_SUCCESS) + return VK_SUCCESS; + + // Remove old memTypeIndex from list of possibilities. + memoryTypeBits &= ~(1u << memTypeIndex); + // Find alternative memTypeIndex. + res = FindMemoryTypeIndex(memoryTypeBits, &createInfoFinal, dedicatedBufferImageUsage, &memTypeIndex); + } while(res == VK_SUCCESS); + + // No other matching memory type index could be found. + // Not returning res, which is VK_ERROR_FEATURE_NOT_PRESENT, because we already failed to allocate once. + return VK_ERROR_OUT_OF_DEVICE_MEMORY; + } +} + +void VmaAllocator_T::FreeMemory( + size_t allocationCount, + const VmaAllocation* pAllocations) +{ + VMA_ASSERT(pAllocations); + + for(size_t allocIndex = allocationCount; allocIndex--; ) + { + VmaAllocation allocation = pAllocations[allocIndex]; + + if(allocation != VK_NULL_HANDLE) + { + if(VMA_DEBUG_INITIALIZE_ALLOCATIONS) + { + FillAllocation(allocation, VMA_ALLOCATION_FILL_PATTERN_DESTROYED); + } + + switch(allocation->GetType()) + { + case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: + { + VmaBlockVector* pBlockVector = VMA_NULL; + VmaPool hPool = allocation->GetParentPool(); + if(hPool != VK_NULL_HANDLE) + { + pBlockVector = &hPool->m_BlockVector; + } + else + { + const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex(); + pBlockVector = m_pBlockVectors[memTypeIndex]; + VMA_ASSERT(pBlockVector && "Trying to free memory of unsupported type!"); + } + pBlockVector->Free(allocation); + } + break; + case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: + FreeDedicatedMemory(allocation); + break; + default: + VMA_ASSERT(0); + } + } + } +} + +void VmaAllocator_T::CalculateStatistics(VmaTotalStatistics* pStats) +{ + // Initialize. + VmaClearDetailedStatistics(pStats->total); + for(uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; ++i) + VmaClearDetailedStatistics(pStats->memoryType[i]); + for(uint32_t i = 0; i < VK_MAX_MEMORY_HEAPS; ++i) + VmaClearDetailedStatistics(pStats->memoryHeap[i]); + + // Process default pools. + for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) + { + VmaBlockVector* const pBlockVector = m_pBlockVectors[memTypeIndex]; + if (pBlockVector != VMA_NULL) + pBlockVector->AddDetailedStatistics(pStats->memoryType[memTypeIndex]); + } + + // Process custom pools. + { + VmaMutexLockRead lock(m_PoolsMutex, m_UseMutex); + for(VmaPool pool = m_Pools.Front(); pool != VMA_NULL; pool = m_Pools.GetNext(pool)) + { + VmaBlockVector& blockVector = pool->m_BlockVector; + const uint32_t memTypeIndex = blockVector.GetMemoryTypeIndex(); + blockVector.AddDetailedStatistics(pStats->memoryType[memTypeIndex]); + pool->m_DedicatedAllocations.AddDetailedStatistics(pStats->memoryType[memTypeIndex]); + } + } + + // Process dedicated allocations. + for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) + { + m_DedicatedAllocations[memTypeIndex].AddDetailedStatistics(pStats->memoryType[memTypeIndex]); + } + + // Sum from memory types to memory heaps. + for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) + { + const uint32_t memHeapIndex = m_MemProps.memoryTypes[memTypeIndex].heapIndex; + VmaAddDetailedStatistics(pStats->memoryHeap[memHeapIndex], pStats->memoryType[memTypeIndex]); + } + + // Sum from memory heaps to total. + for(uint32_t memHeapIndex = 0; memHeapIndex < GetMemoryHeapCount(); ++memHeapIndex) + VmaAddDetailedStatistics(pStats->total, pStats->memoryHeap[memHeapIndex]); + + VMA_ASSERT(pStats->total.statistics.allocationCount == 0 || + pStats->total.allocationSizeMax >= pStats->total.allocationSizeMin); + VMA_ASSERT(pStats->total.unusedRangeCount == 0 || + pStats->total.unusedRangeSizeMax >= pStats->total.unusedRangeSizeMin); +} + +void VmaAllocator_T::GetHeapBudgets(VmaBudget* outBudgets, uint32_t firstHeap, uint32_t heapCount) +{ +#if VMA_MEMORY_BUDGET + if(m_UseExtMemoryBudget) + { + if(m_Budget.m_OperationsSinceBudgetFetch < 30) + { + VmaMutexLockRead lockRead(m_Budget.m_BudgetMutex, m_UseMutex); + for(uint32_t i = 0; i < heapCount; ++i, ++outBudgets) + { + const uint32_t heapIndex = firstHeap + i; + + outBudgets->statistics.blockCount = m_Budget.m_BlockCount[heapIndex]; + outBudgets->statistics.allocationCount = m_Budget.m_AllocationCount[heapIndex]; + outBudgets->statistics.blockBytes = m_Budget.m_BlockBytes[heapIndex]; + outBudgets->statistics.allocationBytes = m_Budget.m_AllocationBytes[heapIndex]; + + if(m_Budget.m_VulkanUsage[heapIndex] + outBudgets->statistics.blockBytes > m_Budget.m_BlockBytesAtBudgetFetch[heapIndex]) + { + outBudgets->usage = m_Budget.m_VulkanUsage[heapIndex] + + outBudgets->statistics.blockBytes - m_Budget.m_BlockBytesAtBudgetFetch[heapIndex]; + } + else + { + outBudgets->usage = 0; + } + + // Have to take MIN with heap size because explicit HeapSizeLimit is included in it. + outBudgets->budget = VMA_MIN( + m_Budget.m_VulkanBudget[heapIndex], m_MemProps.memoryHeaps[heapIndex].size); + } + } + else + { + UpdateVulkanBudget(); // Outside of mutex lock + GetHeapBudgets(outBudgets, firstHeap, heapCount); // Recursion + } + } + else +#endif + { + for(uint32_t i = 0; i < heapCount; ++i, ++outBudgets) + { + const uint32_t heapIndex = firstHeap + i; + + outBudgets->statistics.blockCount = m_Budget.m_BlockCount[heapIndex]; + outBudgets->statistics.allocationCount = m_Budget.m_AllocationCount[heapIndex]; + outBudgets->statistics.blockBytes = m_Budget.m_BlockBytes[heapIndex]; + outBudgets->statistics.allocationBytes = m_Budget.m_AllocationBytes[heapIndex]; + + outBudgets->usage = outBudgets->statistics.blockBytes; + outBudgets->budget = m_MemProps.memoryHeaps[heapIndex].size * 8 / 10; // 80% heuristics. + } + } +} + +void VmaAllocator_T::GetAllocationInfo(VmaAllocation hAllocation, VmaAllocationInfo* pAllocationInfo) +{ + pAllocationInfo->memoryType = hAllocation->GetMemoryTypeIndex(); + pAllocationInfo->deviceMemory = hAllocation->GetMemory(); + pAllocationInfo->offset = hAllocation->GetOffset(); + pAllocationInfo->size = hAllocation->GetSize(); + pAllocationInfo->pMappedData = hAllocation->GetMappedData(); + pAllocationInfo->pUserData = hAllocation->GetUserData(); + pAllocationInfo->pName = hAllocation->GetName(); +} + +void VmaAllocator_T::GetAllocationInfo2(VmaAllocation hAllocation, VmaAllocationInfo2* pAllocationInfo) +{ + GetAllocationInfo(hAllocation, &pAllocationInfo->allocationInfo); + + switch (hAllocation->GetType()) + { + case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: + pAllocationInfo->blockSize = hAllocation->GetBlock()->m_pMetadata->GetSize(); + pAllocationInfo->dedicatedMemory = VK_FALSE; + break; + case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: + pAllocationInfo->blockSize = pAllocationInfo->allocationInfo.size; + pAllocationInfo->dedicatedMemory = VK_TRUE; + break; + default: + VMA_ASSERT(0); + } +} + +VkResult VmaAllocator_T::CreatePool(const VmaPoolCreateInfo* pCreateInfo, VmaPool* pPool) +{ + VMA_DEBUG_LOG_FORMAT(" CreatePool: MemoryTypeIndex=%" PRIu32 ", flags=%" PRIu32, pCreateInfo->memoryTypeIndex, pCreateInfo->flags); + + VmaPoolCreateInfo newCreateInfo = *pCreateInfo; + + // Protection against uninitialized new structure member. If garbage data are left there, this pointer dereference would crash. + if(pCreateInfo->pMemoryAllocateNext) + { + VMA_ASSERT(((const VkBaseInStructure*)pCreateInfo->pMemoryAllocateNext)->sType != 0); + } + + if(newCreateInfo.maxBlockCount == 0) + { + newCreateInfo.maxBlockCount = SIZE_MAX; + } + if(newCreateInfo.minBlockCount > newCreateInfo.maxBlockCount) + { + return VK_ERROR_INITIALIZATION_FAILED; + } + // Memory type index out of range or forbidden. + if(pCreateInfo->memoryTypeIndex >= GetMemoryTypeCount() || + ((1u << pCreateInfo->memoryTypeIndex) & m_GlobalMemoryTypeBits) == 0) + { + return VK_ERROR_FEATURE_NOT_PRESENT; + } + if(newCreateInfo.minAllocationAlignment > 0) + { + VMA_ASSERT(VmaIsPow2(newCreateInfo.minAllocationAlignment)); + } + + const VkDeviceSize preferredBlockSize = CalcPreferredBlockSize(newCreateInfo.memoryTypeIndex); + + *pPool = vma_new(this, VmaPool_T)(this, newCreateInfo, preferredBlockSize); + + VkResult res = (*pPool)->m_BlockVector.CreateMinBlocks(); + if(res != VK_SUCCESS) + { + vma_delete(this, *pPool); + *pPool = VMA_NULL; + return res; + } + + // Add to m_Pools. + { + VmaMutexLockWrite lock(m_PoolsMutex, m_UseMutex); + (*pPool)->SetId(m_NextPoolId++); + m_Pools.PushBack(*pPool); + } + + return VK_SUCCESS; +} + +void VmaAllocator_T::DestroyPool(VmaPool pool) +{ + // Remove from m_Pools. + { + VmaMutexLockWrite lock(m_PoolsMutex, m_UseMutex); + m_Pools.Remove(pool); + } + + vma_delete(this, pool); +} + +void VmaAllocator_T::GetPoolStatistics(VmaPool pool, VmaStatistics* pPoolStats) +{ + VmaClearStatistics(*pPoolStats); + pool->m_BlockVector.AddStatistics(*pPoolStats); + pool->m_DedicatedAllocations.AddStatistics(*pPoolStats); +} + +void VmaAllocator_T::CalculatePoolStatistics(VmaPool pool, VmaDetailedStatistics* pPoolStats) +{ + VmaClearDetailedStatistics(*pPoolStats); + pool->m_BlockVector.AddDetailedStatistics(*pPoolStats); + pool->m_DedicatedAllocations.AddDetailedStatistics(*pPoolStats); +} + +void VmaAllocator_T::SetCurrentFrameIndex(uint32_t frameIndex) +{ + m_CurrentFrameIndex.store(frameIndex); + +#if VMA_MEMORY_BUDGET + if(m_UseExtMemoryBudget) + { + UpdateVulkanBudget(); + } +#endif // #if VMA_MEMORY_BUDGET +} + +VkResult VmaAllocator_T::CheckPoolCorruption(VmaPool hPool) +{ + return hPool->m_BlockVector.CheckCorruption(); +} + +VkResult VmaAllocator_T::CheckCorruption(uint32_t memoryTypeBits) +{ + VkResult finalRes = VK_ERROR_FEATURE_NOT_PRESENT; + + // Process default pools. + for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) + { + VmaBlockVector* const pBlockVector = m_pBlockVectors[memTypeIndex]; + if(pBlockVector != VMA_NULL) + { + VkResult localRes = pBlockVector->CheckCorruption(); + switch(localRes) + { + case VK_ERROR_FEATURE_NOT_PRESENT: + break; + case VK_SUCCESS: + finalRes = VK_SUCCESS; + break; + default: + return localRes; + } + } + } + + // Process custom pools. + { + VmaMutexLockRead lock(m_PoolsMutex, m_UseMutex); + for(VmaPool pool = m_Pools.Front(); pool != VMA_NULL; pool = m_Pools.GetNext(pool)) + { + if(((1u << pool->m_BlockVector.GetMemoryTypeIndex()) & memoryTypeBits) != 0) + { + VkResult localRes = pool->m_BlockVector.CheckCorruption(); + switch(localRes) + { + case VK_ERROR_FEATURE_NOT_PRESENT: + break; + case VK_SUCCESS: + finalRes = VK_SUCCESS; + break; + default: + return localRes; + } + } + } + } + + return finalRes; +} + +VkResult VmaAllocator_T::AllocateVulkanMemory(const VkMemoryAllocateInfo* pAllocateInfo, VkDeviceMemory* pMemory) +{ + AtomicTransactionalIncrement deviceMemoryCountIncrement; + const uint64_t prevDeviceMemoryCount = deviceMemoryCountIncrement.Increment(&m_DeviceMemoryCount); +#if VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT + if(prevDeviceMemoryCount >= m_PhysicalDeviceProperties.limits.maxMemoryAllocationCount) + { + return VK_ERROR_TOO_MANY_OBJECTS; + } +#endif + + const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(pAllocateInfo->memoryTypeIndex); + + // HeapSizeLimit is in effect for this heap. + if((m_HeapSizeLimitMask & (1u << heapIndex)) != 0) + { + const VkDeviceSize heapSize = m_MemProps.memoryHeaps[heapIndex].size; + VkDeviceSize blockBytes = m_Budget.m_BlockBytes[heapIndex]; + for(;;) + { + const VkDeviceSize blockBytesAfterAllocation = blockBytes + pAllocateInfo->allocationSize; + if(blockBytesAfterAllocation > heapSize) + { + return VK_ERROR_OUT_OF_DEVICE_MEMORY; + } + if(m_Budget.m_BlockBytes[heapIndex].compare_exchange_strong(blockBytes, blockBytesAfterAllocation)) + { + break; + } + } + } + else + { + m_Budget.m_BlockBytes[heapIndex] += pAllocateInfo->allocationSize; + } + ++m_Budget.m_BlockCount[heapIndex]; + + // VULKAN CALL vkAllocateMemory. + VkResult res = (*m_VulkanFunctions.vkAllocateMemory)(m_hDevice, pAllocateInfo, GetAllocationCallbacks(), pMemory); + + if(res == VK_SUCCESS) + { +#if VMA_MEMORY_BUDGET + ++m_Budget.m_OperationsSinceBudgetFetch; +#endif + + // Informative callback. + if(m_DeviceMemoryCallbacks.pfnAllocate != VMA_NULL) + { + (*m_DeviceMemoryCallbacks.pfnAllocate)(this, pAllocateInfo->memoryTypeIndex, *pMemory, pAllocateInfo->allocationSize, m_DeviceMemoryCallbacks.pUserData); + } + + deviceMemoryCountIncrement.Commit(); + } + else + { + --m_Budget.m_BlockCount[heapIndex]; + m_Budget.m_BlockBytes[heapIndex] -= pAllocateInfo->allocationSize; + } + + return res; +} + +void VmaAllocator_T::FreeVulkanMemory(uint32_t memoryType, VkDeviceSize size, VkDeviceMemory hMemory) +{ + // Informative callback. + if(m_DeviceMemoryCallbacks.pfnFree != VMA_NULL) + { + (*m_DeviceMemoryCallbacks.pfnFree)(this, memoryType, hMemory, size, m_DeviceMemoryCallbacks.pUserData); + } + + // VULKAN CALL vkFreeMemory. + (*m_VulkanFunctions.vkFreeMemory)(m_hDevice, hMemory, GetAllocationCallbacks()); + + const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(memoryType); + --m_Budget.m_BlockCount[heapIndex]; + m_Budget.m_BlockBytes[heapIndex] -= size; + + --m_DeviceMemoryCount; +} + +VkResult VmaAllocator_T::BindVulkanBuffer( + VkDeviceMemory memory, + VkDeviceSize memoryOffset, + VkBuffer buffer, + const void* pNext) +{ + if(pNext != VMA_NULL) + { +#if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2 + if((m_UseKhrBindMemory2 || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) && + m_VulkanFunctions.vkBindBufferMemory2KHR != VMA_NULL) + { + VkBindBufferMemoryInfoKHR bindBufferMemoryInfo = { VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO_KHR }; + bindBufferMemoryInfo.pNext = pNext; + bindBufferMemoryInfo.buffer = buffer; + bindBufferMemoryInfo.memory = memory; + bindBufferMemoryInfo.memoryOffset = memoryOffset; + return (*m_VulkanFunctions.vkBindBufferMemory2KHR)(m_hDevice, 1, &bindBufferMemoryInfo); + } + else +#endif // #if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2 + { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + } + else + { + return (*m_VulkanFunctions.vkBindBufferMemory)(m_hDevice, buffer, memory, memoryOffset); + } +} + +VkResult VmaAllocator_T::BindVulkanImage( + VkDeviceMemory memory, + VkDeviceSize memoryOffset, + VkImage image, + const void* pNext) +{ + if(pNext != VMA_NULL) + { +#if VMA_VULKAN_VERSION >= 1001000 || VMA_BIND_MEMORY2 + if((m_UseKhrBindMemory2 || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) && + m_VulkanFunctions.vkBindImageMemory2KHR != VMA_NULL) + { + VkBindImageMemoryInfoKHR bindBufferMemoryInfo = { VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO_KHR }; + bindBufferMemoryInfo.pNext = pNext; + bindBufferMemoryInfo.image = image; + bindBufferMemoryInfo.memory = memory; + bindBufferMemoryInfo.memoryOffset = memoryOffset; + return (*m_VulkanFunctions.vkBindImageMemory2KHR)(m_hDevice, 1, &bindBufferMemoryInfo); + } + else +#endif // #if VMA_BIND_MEMORY2 + { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } + } + else + { + return (*m_VulkanFunctions.vkBindImageMemory)(m_hDevice, image, memory, memoryOffset); + } +} + +VkResult VmaAllocator_T::Map(VmaAllocation hAllocation, void** ppData) +{ + switch(hAllocation->GetType()) + { + case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: + { + VmaDeviceMemoryBlock* const pBlock = hAllocation->GetBlock(); + char *pBytes = VMA_NULL; + VkResult res = pBlock->Map(this, 1, (void**)&pBytes); + if(res == VK_SUCCESS) + { + *ppData = pBytes + (ptrdiff_t)hAllocation->GetOffset(); + hAllocation->BlockAllocMap(); + } + return res; + } + case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: + return hAllocation->DedicatedAllocMap(this, ppData); + default: + VMA_ASSERT(0); + return VK_ERROR_MEMORY_MAP_FAILED; + } +} + +void VmaAllocator_T::Unmap(VmaAllocation hAllocation) +{ + switch(hAllocation->GetType()) + { + case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: + { + VmaDeviceMemoryBlock* const pBlock = hAllocation->GetBlock(); + hAllocation->BlockAllocUnmap(); + pBlock->Unmap(this, 1); + } + break; + case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: + hAllocation->DedicatedAllocUnmap(this); + break; + default: + VMA_ASSERT(0); + } +} + +VkResult VmaAllocator_T::BindBufferMemory( + VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkBuffer hBuffer, + const void* pNext) +{ + VkResult res = VK_ERROR_UNKNOWN_COPY; + switch(hAllocation->GetType()) + { + case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: + res = BindVulkanBuffer(hAllocation->GetMemory(), allocationLocalOffset, hBuffer, pNext); + break; + case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: + { + VmaDeviceMemoryBlock* const pBlock = hAllocation->GetBlock(); + VMA_ASSERT(pBlock && "Binding buffer to allocation that doesn't belong to any block."); + res = pBlock->BindBufferMemory(this, hAllocation, allocationLocalOffset, hBuffer, pNext); + break; + } + default: + VMA_ASSERT(0); + } + return res; +} + +VkResult VmaAllocator_T::BindImageMemory( + VmaAllocation hAllocation, + VkDeviceSize allocationLocalOffset, + VkImage hImage, + const void* pNext) +{ + VkResult res = VK_ERROR_UNKNOWN_COPY; + switch(hAllocation->GetType()) + { + case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: + res = BindVulkanImage(hAllocation->GetMemory(), allocationLocalOffset, hImage, pNext); + break; + case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: + { + VmaDeviceMemoryBlock* pBlock = hAllocation->GetBlock(); + VMA_ASSERT(pBlock && "Binding image to allocation that doesn't belong to any block."); + res = pBlock->BindImageMemory(this, hAllocation, allocationLocalOffset, hImage, pNext); + break; + } + default: + VMA_ASSERT(0); + } + return res; +} + +VkResult VmaAllocator_T::FlushOrInvalidateAllocation( + VmaAllocation hAllocation, + VkDeviceSize offset, VkDeviceSize size, + VMA_CACHE_OPERATION op) +{ + VkResult res = VK_SUCCESS; + + VkMappedMemoryRange memRange = {}; + if(GetFlushOrInvalidateRange(hAllocation, offset, size, memRange)) + { + switch(op) + { + case VMA_CACHE_FLUSH: + res = (*GetVulkanFunctions().vkFlushMappedMemoryRanges)(m_hDevice, 1, &memRange); + break; + case VMA_CACHE_INVALIDATE: + res = (*GetVulkanFunctions().vkInvalidateMappedMemoryRanges)(m_hDevice, 1, &memRange); + break; + default: + VMA_ASSERT(0); + } + } + // else: Just ignore this call. + return res; +} + +VkResult VmaAllocator_T::FlushOrInvalidateAllocations( + uint32_t allocationCount, + const VmaAllocation* allocations, + const VkDeviceSize* offsets, const VkDeviceSize* sizes, + VMA_CACHE_OPERATION op) +{ + typedef VmaStlAllocator RangeAllocator; + typedef VmaSmallVector RangeVector; + RangeVector ranges = RangeVector(RangeAllocator(GetAllocationCallbacks())); + + for(uint32_t allocIndex = 0; allocIndex < allocationCount; ++allocIndex) + { + const VmaAllocation alloc = allocations[allocIndex]; + const VkDeviceSize offset = offsets != VMA_NULL ? offsets[allocIndex] : 0; + const VkDeviceSize size = sizes != VMA_NULL ? sizes[allocIndex] : VK_WHOLE_SIZE; + VkMappedMemoryRange newRange; + if(GetFlushOrInvalidateRange(alloc, offset, size, newRange)) + { + ranges.push_back(newRange); + } + } + + VkResult res = VK_SUCCESS; + if(!ranges.empty()) + { + switch(op) + { + case VMA_CACHE_FLUSH: + res = (*GetVulkanFunctions().vkFlushMappedMemoryRanges)(m_hDevice, (uint32_t)ranges.size(), ranges.data()); + break; + case VMA_CACHE_INVALIDATE: + res = (*GetVulkanFunctions().vkInvalidateMappedMemoryRanges)(m_hDevice, (uint32_t)ranges.size(), ranges.data()); + break; + default: + VMA_ASSERT(0); + } + } + // else: Just ignore this call. + return res; +} + +VkResult VmaAllocator_T::CopyMemoryToAllocation( + const void* pSrcHostPointer, + VmaAllocation dstAllocation, + VkDeviceSize dstAllocationLocalOffset, + VkDeviceSize size) +{ + void* dstMappedData = VMA_NULL; + VkResult res = Map(dstAllocation, &dstMappedData); + if(res == VK_SUCCESS) + { + memcpy((char*)dstMappedData + dstAllocationLocalOffset, pSrcHostPointer, (size_t)size); + Unmap(dstAllocation); + res = FlushOrInvalidateAllocation(dstAllocation, dstAllocationLocalOffset, size, VMA_CACHE_FLUSH); + } + return res; +} + +VkResult VmaAllocator_T::CopyAllocationToMemory( + VmaAllocation srcAllocation, + VkDeviceSize srcAllocationLocalOffset, + void* pDstHostPointer, + VkDeviceSize size) +{ + void* srcMappedData = VMA_NULL; + VkResult res = Map(srcAllocation, &srcMappedData); + if(res == VK_SUCCESS) + { + res = FlushOrInvalidateAllocation(srcAllocation, srcAllocationLocalOffset, size, VMA_CACHE_INVALIDATE); + if(res == VK_SUCCESS) + { + memcpy(pDstHostPointer, (const char*)srcMappedData + srcAllocationLocalOffset, (size_t)size); + Unmap(srcAllocation); + } + } + return res; +} + +void VmaAllocator_T::FreeDedicatedMemory(const VmaAllocation allocation) +{ + VMA_ASSERT(allocation && allocation->GetType() == VmaAllocation_T::ALLOCATION_TYPE_DEDICATED); + + const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex(); + VmaPool parentPool = allocation->GetParentPool(); + if(parentPool == VK_NULL_HANDLE) + { + // Default pool + m_DedicatedAllocations[memTypeIndex].Unregister(allocation); + } + else + { + // Custom pool + parentPool->m_DedicatedAllocations.Unregister(allocation); + } + + VkDeviceMemory hMemory = allocation->GetMemory(); + + /* + There is no need to call this, because Vulkan spec allows to skip vkUnmapMemory + before vkFreeMemory. + + if(allocation->GetMappedData() != VMA_NULL) + { + (*m_VulkanFunctions.vkUnmapMemory)(m_hDevice, hMemory); + } + */ + + FreeVulkanMemory(memTypeIndex, allocation->GetSize(), hMemory); + + m_Budget.RemoveAllocation(MemoryTypeIndexToHeapIndex(allocation->GetMemoryTypeIndex()), allocation->GetSize()); + allocation->Destroy(this); + m_AllocationObjectAllocator.Free(allocation); + + VMA_DEBUG_LOG_FORMAT(" Freed DedicatedMemory MemoryTypeIndex=%" PRIu32, memTypeIndex); +} + +uint32_t VmaAllocator_T::CalculateGpuDefragmentationMemoryTypeBits() const +{ + VkBufferCreateInfo dummyBufCreateInfo; + VmaFillGpuDefragmentationBufferCreateInfo(dummyBufCreateInfo); + + uint32_t memoryTypeBits = 0; + + // Create buffer. + VkBuffer buf = VK_NULL_HANDLE; + VkResult res = (*GetVulkanFunctions().vkCreateBuffer)( + m_hDevice, &dummyBufCreateInfo, GetAllocationCallbacks(), &buf); + if(res == VK_SUCCESS) + { + // Query for supported memory types. + VkMemoryRequirements memReq; + (*GetVulkanFunctions().vkGetBufferMemoryRequirements)(m_hDevice, buf, &memReq); + memoryTypeBits = memReq.memoryTypeBits; + + // Destroy buffer. + (*GetVulkanFunctions().vkDestroyBuffer)(m_hDevice, buf, GetAllocationCallbacks()); + } + + return memoryTypeBits; +} + +uint32_t VmaAllocator_T::CalculateGlobalMemoryTypeBits() const +{ + // Make sure memory information is already fetched. + VMA_ASSERT(GetMemoryTypeCount() > 0); + + uint32_t memoryTypeBits = UINT32_MAX; + + if(!m_UseAmdDeviceCoherentMemory) + { + // Exclude memory types that have VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD. + for(uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) + { + if((m_MemProps.memoryTypes[memTypeIndex].propertyFlags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY) != 0) + { + memoryTypeBits &= ~(1u << memTypeIndex); + } + } + } + + return memoryTypeBits; +} + +bool VmaAllocator_T::GetFlushOrInvalidateRange( + VmaAllocation allocation, + VkDeviceSize offset, VkDeviceSize size, + VkMappedMemoryRange& outRange) const +{ + const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex(); + if(size > 0 && IsMemoryTypeNonCoherent(memTypeIndex)) + { + const VkDeviceSize nonCoherentAtomSize = m_PhysicalDeviceProperties.limits.nonCoherentAtomSize; + const VkDeviceSize allocationSize = allocation->GetSize(); + VMA_ASSERT(offset <= allocationSize); + + outRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + outRange.pNext = VMA_NULL; + outRange.memory = allocation->GetMemory(); + + switch(allocation->GetType()) + { + case VmaAllocation_T::ALLOCATION_TYPE_DEDICATED: + outRange.offset = VmaAlignDown(offset, nonCoherentAtomSize); + if(size == VK_WHOLE_SIZE) + { + outRange.size = allocationSize - outRange.offset; + } + else + { + VMA_ASSERT(offset + size <= allocationSize); + outRange.size = VMA_MIN( + VmaAlignUp(size + (offset - outRange.offset), nonCoherentAtomSize), + allocationSize - outRange.offset); + } + break; + case VmaAllocation_T::ALLOCATION_TYPE_BLOCK: + { + // 1. Still within this allocation. + outRange.offset = VmaAlignDown(offset, nonCoherentAtomSize); + if(size == VK_WHOLE_SIZE) + { + size = allocationSize - offset; + } + else + { + VMA_ASSERT(offset + size <= allocationSize); + } + outRange.size = VmaAlignUp(size + (offset - outRange.offset), nonCoherentAtomSize); + + // 2. Adjust to whole block. + const VkDeviceSize allocationOffset = allocation->GetOffset(); + VMA_ASSERT(allocationOffset % nonCoherentAtomSize == 0); + const VkDeviceSize blockSize = allocation->GetBlock()->m_pMetadata->GetSize(); + outRange.offset += allocationOffset; + outRange.size = VMA_MIN(outRange.size, blockSize - outRange.offset); + + break; + } + default: + VMA_ASSERT(0); + } + return true; + } + return false; +} + +#if VMA_MEMORY_BUDGET +void VmaAllocator_T::UpdateVulkanBudget() +{ + VMA_ASSERT(m_UseExtMemoryBudget); + + VkPhysicalDeviceMemoryProperties2KHR memProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2_KHR }; + + VkPhysicalDeviceMemoryBudgetPropertiesEXT budgetProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT }; + VmaPnextChainPushFront(&memProps, &budgetProps); + + GetVulkanFunctions().vkGetPhysicalDeviceMemoryProperties2KHR(m_PhysicalDevice, &memProps); + + { + VmaMutexLockWrite lockWrite(m_Budget.m_BudgetMutex, m_UseMutex); + + for(uint32_t heapIndex = 0; heapIndex < GetMemoryHeapCount(); ++heapIndex) + { + m_Budget.m_VulkanUsage[heapIndex] = budgetProps.heapUsage[heapIndex]; + m_Budget.m_VulkanBudget[heapIndex] = budgetProps.heapBudget[heapIndex]; + m_Budget.m_BlockBytesAtBudgetFetch[heapIndex] = m_Budget.m_BlockBytes[heapIndex].load(); + + // Some bugged drivers return the budget incorrectly, e.g. 0 or much bigger than heap size. + if(m_Budget.m_VulkanBudget[heapIndex] == 0) + { + m_Budget.m_VulkanBudget[heapIndex] = m_MemProps.memoryHeaps[heapIndex].size * 8 / 10; // 80% heuristics. + } + else if(m_Budget.m_VulkanBudget[heapIndex] > m_MemProps.memoryHeaps[heapIndex].size) + { + m_Budget.m_VulkanBudget[heapIndex] = m_MemProps.memoryHeaps[heapIndex].size; + } + if(m_Budget.m_VulkanUsage[heapIndex] == 0 && m_Budget.m_BlockBytesAtBudgetFetch[heapIndex] > 0) + { + m_Budget.m_VulkanUsage[heapIndex] = m_Budget.m_BlockBytesAtBudgetFetch[heapIndex]; + } + } + m_Budget.m_OperationsSinceBudgetFetch = 0; + } +} +#endif // VMA_MEMORY_BUDGET + +void VmaAllocator_T::FillAllocation(const VmaAllocation hAllocation, uint8_t pattern) +{ + if(VMA_DEBUG_INITIALIZE_ALLOCATIONS && + hAllocation->IsMappingAllowed() && + (m_MemProps.memoryTypes[hAllocation->GetMemoryTypeIndex()].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0) + { + void* pData = VMA_NULL; + VkResult res = Map(hAllocation, &pData); + if(res == VK_SUCCESS) + { + memset(pData, (int)pattern, (size_t)hAllocation->GetSize()); + FlushOrInvalidateAllocation(hAllocation, 0, VK_WHOLE_SIZE, VMA_CACHE_FLUSH); + Unmap(hAllocation); + } + else + { + VMA_ASSERT(0 && "VMA_DEBUG_INITIALIZE_ALLOCATIONS is enabled, but couldn't map memory to fill allocation."); + } + } +} + +uint32_t VmaAllocator_T::GetGpuDefragmentationMemoryTypeBits() +{ + uint32_t memoryTypeBits = m_GpuDefragmentationMemoryTypeBits.load(); + if(memoryTypeBits == UINT32_MAX) + { + memoryTypeBits = CalculateGpuDefragmentationMemoryTypeBits(); + m_GpuDefragmentationMemoryTypeBits.store(memoryTypeBits); + } + return memoryTypeBits; +} + +#if VMA_STATS_STRING_ENABLED +void VmaAllocator_T::PrintDetailedMap(VmaJsonWriter& json) +{ + json.WriteString("DefaultPools"); + json.BeginObject(); + { + for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) + { + VmaBlockVector* pBlockVector = m_pBlockVectors[memTypeIndex]; + VmaDedicatedAllocationList& dedicatedAllocList = m_DedicatedAllocations[memTypeIndex]; + if (pBlockVector != VMA_NULL) + { + json.BeginString("Type "); + json.ContinueString(memTypeIndex); + json.EndString(); + json.BeginObject(); + { + json.WriteString("PreferredBlockSize"); + json.WriteNumber(pBlockVector->GetPreferredBlockSize()); + + json.WriteString("Blocks"); + pBlockVector->PrintDetailedMap(json); + + json.WriteString("DedicatedAllocations"); + dedicatedAllocList.BuildStatsString(json); + } + json.EndObject(); + } + } + } + json.EndObject(); + + json.WriteString("CustomPools"); + json.BeginObject(); + { + VmaMutexLockRead lock(m_PoolsMutex, m_UseMutex); + if (!m_Pools.IsEmpty()) + { + for (uint32_t memTypeIndex = 0; memTypeIndex < GetMemoryTypeCount(); ++memTypeIndex) + { + bool displayType = true; + size_t index = 0; + for (VmaPool pool = m_Pools.Front(); pool != VMA_NULL; pool = m_Pools.GetNext(pool)) + { + VmaBlockVector& blockVector = pool->m_BlockVector; + if (blockVector.GetMemoryTypeIndex() == memTypeIndex) + { + if (displayType) + { + json.BeginString("Type "); + json.ContinueString(memTypeIndex); + json.EndString(); + json.BeginArray(); + displayType = false; + } + + json.BeginObject(); + { + json.WriteString("Name"); + json.BeginString(); + json.ContinueString((uint64_t)index++); + if (pool->GetName()) + { + json.ContinueString(" - "); + json.ContinueString(pool->GetName()); + } + json.EndString(); + + json.WriteString("PreferredBlockSize"); + json.WriteNumber(blockVector.GetPreferredBlockSize()); + + json.WriteString("Blocks"); + blockVector.PrintDetailedMap(json); + + json.WriteString("DedicatedAllocations"); + pool->m_DedicatedAllocations.BuildStatsString(json); + } + json.EndObject(); + } + } + + if (!displayType) + json.EndArray(); + } + } + } + json.EndObject(); +} +#endif // VMA_STATS_STRING_ENABLED +#endif // _VMA_ALLOCATOR_T_FUNCTIONS + + +#ifndef _VMA_PUBLIC_INTERFACE +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAllocator( + const VmaAllocatorCreateInfo* pCreateInfo, + VmaAllocator* pAllocator) +{ + VMA_ASSERT(pCreateInfo && pAllocator); + VMA_ASSERT(pCreateInfo->vulkanApiVersion == 0 || + (VK_VERSION_MAJOR(pCreateInfo->vulkanApiVersion) == 1 && VK_VERSION_MINOR(pCreateInfo->vulkanApiVersion) <= 3)); + VMA_DEBUG_LOG("vmaCreateAllocator"); + *pAllocator = vma_new(pCreateInfo->pAllocationCallbacks, VmaAllocator_T)(pCreateInfo); + VkResult result = (*pAllocator)->Init(pCreateInfo); + if(result < 0) + { + vma_delete(pCreateInfo->pAllocationCallbacks, *pAllocator); + *pAllocator = VK_NULL_HANDLE; + } + return result; +} + +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyAllocator( + VmaAllocator allocator) +{ + if(allocator != VK_NULL_HANDLE) + { + VMA_DEBUG_LOG("vmaDestroyAllocator"); + VkAllocationCallbacks allocationCallbacks = allocator->m_AllocationCallbacks; // Have to copy the callbacks when destroying. + vma_delete(&allocationCallbacks, allocator); + } +} + +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocatorInfo(VmaAllocator allocator, VmaAllocatorInfo* pAllocatorInfo) +{ + VMA_ASSERT(allocator && pAllocatorInfo); + pAllocatorInfo->instance = allocator->m_hInstance; + pAllocatorInfo->physicalDevice = allocator->GetPhysicalDevice(); + pAllocatorInfo->device = allocator->m_hDevice; +} + +VMA_CALL_PRE void VMA_CALL_POST vmaGetPhysicalDeviceProperties( + VmaAllocator allocator, + const VkPhysicalDeviceProperties **ppPhysicalDeviceProperties) +{ + VMA_ASSERT(allocator && ppPhysicalDeviceProperties); + *ppPhysicalDeviceProperties = &allocator->m_PhysicalDeviceProperties; +} + +VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryProperties( + VmaAllocator allocator, + const VkPhysicalDeviceMemoryProperties** ppPhysicalDeviceMemoryProperties) +{ + VMA_ASSERT(allocator && ppPhysicalDeviceMemoryProperties); + *ppPhysicalDeviceMemoryProperties = &allocator->m_MemProps; +} + +VMA_CALL_PRE void VMA_CALL_POST vmaGetMemoryTypeProperties( + VmaAllocator allocator, + uint32_t memoryTypeIndex, + VkMemoryPropertyFlags* pFlags) +{ + VMA_ASSERT(allocator && pFlags); + VMA_ASSERT(memoryTypeIndex < allocator->GetMemoryTypeCount()); + *pFlags = allocator->m_MemProps.memoryTypes[memoryTypeIndex].propertyFlags; +} + +VMA_CALL_PRE void VMA_CALL_POST vmaSetCurrentFrameIndex( + VmaAllocator allocator, + uint32_t frameIndex) +{ + VMA_ASSERT(allocator); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + allocator->SetCurrentFrameIndex(frameIndex); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaCalculateStatistics( + VmaAllocator allocator, + VmaTotalStatistics* pStats) +{ + VMA_ASSERT(allocator && pStats); + VMA_DEBUG_GLOBAL_MUTEX_LOCK + allocator->CalculateStatistics(pStats); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaGetHeapBudgets( + VmaAllocator allocator, + VmaBudget* pBudgets) +{ + VMA_ASSERT(allocator && pBudgets); + VMA_DEBUG_GLOBAL_MUTEX_LOCK + allocator->GetHeapBudgets(pBudgets, 0, allocator->GetMemoryHeapCount()); +} + +#if VMA_STATS_STRING_ENABLED + +VMA_CALL_PRE void VMA_CALL_POST vmaBuildStatsString( + VmaAllocator allocator, + char** ppStatsString, + VkBool32 detailedMap) +{ + VMA_ASSERT(allocator && ppStatsString); + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + VmaStringBuilder sb(allocator->GetAllocationCallbacks()); + { + VmaBudget budgets[VK_MAX_MEMORY_HEAPS]; + allocator->GetHeapBudgets(budgets, 0, allocator->GetMemoryHeapCount()); + + VmaTotalStatistics stats; + allocator->CalculateStatistics(&stats); + + VmaJsonWriter json(allocator->GetAllocationCallbacks(), sb); + json.BeginObject(); + { + json.WriteString("General"); + json.BeginObject(); + { + const VkPhysicalDeviceProperties& deviceProperties = allocator->m_PhysicalDeviceProperties; + const VkPhysicalDeviceMemoryProperties& memoryProperties = allocator->m_MemProps; + + json.WriteString("API"); + json.WriteString("Vulkan"); + + json.WriteString("apiVersion"); + json.BeginString(); + json.ContinueString(VK_VERSION_MAJOR(deviceProperties.apiVersion)); + json.ContinueString("."); + json.ContinueString(VK_VERSION_MINOR(deviceProperties.apiVersion)); + json.ContinueString("."); + json.ContinueString(VK_VERSION_PATCH(deviceProperties.apiVersion)); + json.EndString(); + + json.WriteString("GPU"); + json.WriteString(deviceProperties.deviceName); + json.WriteString("deviceType"); + json.WriteNumber(static_cast(deviceProperties.deviceType)); + + json.WriteString("maxMemoryAllocationCount"); + json.WriteNumber(deviceProperties.limits.maxMemoryAllocationCount); + json.WriteString("bufferImageGranularity"); + json.WriteNumber(deviceProperties.limits.bufferImageGranularity); + json.WriteString("nonCoherentAtomSize"); + json.WriteNumber(deviceProperties.limits.nonCoherentAtomSize); + + json.WriteString("memoryHeapCount"); + json.WriteNumber(memoryProperties.memoryHeapCount); + json.WriteString("memoryTypeCount"); + json.WriteNumber(memoryProperties.memoryTypeCount); + } + json.EndObject(); + } + { + json.WriteString("Total"); + VmaPrintDetailedStatistics(json, stats.total); + } + { + json.WriteString("MemoryInfo"); + json.BeginObject(); + { + for (uint32_t heapIndex = 0; heapIndex < allocator->GetMemoryHeapCount(); ++heapIndex) + { + json.BeginString("Heap "); + json.ContinueString(heapIndex); + json.EndString(); + json.BeginObject(); + { + const VkMemoryHeap& heapInfo = allocator->m_MemProps.memoryHeaps[heapIndex]; + json.WriteString("Flags"); + json.BeginArray(true); + { + if (heapInfo.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) + json.WriteString("DEVICE_LOCAL"); + #if VMA_VULKAN_VERSION >= 1001000 + if (heapInfo.flags & VK_MEMORY_HEAP_MULTI_INSTANCE_BIT) + json.WriteString("MULTI_INSTANCE"); + #endif + + VkMemoryHeapFlags flags = heapInfo.flags & + ~(VK_MEMORY_HEAP_DEVICE_LOCAL_BIT + #if VMA_VULKAN_VERSION >= 1001000 + | VK_MEMORY_HEAP_MULTI_INSTANCE_BIT + #endif + ); + if (flags != 0) + json.WriteNumber(flags); + } + json.EndArray(); + + json.WriteString("Size"); + json.WriteNumber(heapInfo.size); + + json.WriteString("Budget"); + json.BeginObject(); + { + json.WriteString("BudgetBytes"); + json.WriteNumber(budgets[heapIndex].budget); + json.WriteString("UsageBytes"); + json.WriteNumber(budgets[heapIndex].usage); + } + json.EndObject(); + + json.WriteString("Stats"); + VmaPrintDetailedStatistics(json, stats.memoryHeap[heapIndex]); + + json.WriteString("MemoryPools"); + json.BeginObject(); + { + for (uint32_t typeIndex = 0; typeIndex < allocator->GetMemoryTypeCount(); ++typeIndex) + { + if (allocator->MemoryTypeIndexToHeapIndex(typeIndex) == heapIndex) + { + json.BeginString("Type "); + json.ContinueString(typeIndex); + json.EndString(); + json.BeginObject(); + { + json.WriteString("Flags"); + json.BeginArray(true); + { + VkMemoryPropertyFlags flags = allocator->m_MemProps.memoryTypes[typeIndex].propertyFlags; + if (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) + json.WriteString("DEVICE_LOCAL"); + if (flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) + json.WriteString("HOST_VISIBLE"); + if (flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) + json.WriteString("HOST_COHERENT"); + if (flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) + json.WriteString("HOST_CACHED"); + if (flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) + json.WriteString("LAZILY_ALLOCATED"); + #if VMA_VULKAN_VERSION >= 1001000 + if (flags & VK_MEMORY_PROPERTY_PROTECTED_BIT) + json.WriteString("PROTECTED"); + #endif + #if VK_AMD_device_coherent_memory + if (flags & VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY) + json.WriteString("DEVICE_COHERENT_AMD"); + if (flags & VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY) + json.WriteString("DEVICE_UNCACHED_AMD"); + #endif + + flags &= ~(VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT + #if VMA_VULKAN_VERSION >= 1001000 + | VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT + #endif + #if VK_AMD_device_coherent_memory + | VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD_COPY + | VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD_COPY + #endif + | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT + | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT + | VK_MEMORY_PROPERTY_HOST_CACHED_BIT); + if (flags != 0) + json.WriteNumber(flags); + } + json.EndArray(); + + json.WriteString("Stats"); + VmaPrintDetailedStatistics(json, stats.memoryType[typeIndex]); + } + json.EndObject(); + } + } + + } + json.EndObject(); + } + json.EndObject(); + } + } + json.EndObject(); + } + + if (detailedMap == VK_TRUE) + allocator->PrintDetailedMap(json); + + json.EndObject(); + } + + *ppStatsString = VmaCreateStringCopy(allocator->GetAllocationCallbacks(), sb.GetData(), sb.GetLength()); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaFreeStatsString( + VmaAllocator allocator, + char* pStatsString) +{ + if(pStatsString != VMA_NULL) + { + VMA_ASSERT(allocator); + VmaFreeString(allocator->GetAllocationCallbacks(), pStatsString); + } +} + +#endif // VMA_STATS_STRING_ENABLED + +/* +This function is not protected by any mutex because it just reads immutable data. +*/ +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndex( + VmaAllocator allocator, + uint32_t memoryTypeBits, + const VmaAllocationCreateInfo* pAllocationCreateInfo, + uint32_t* pMemoryTypeIndex) +{ + VMA_ASSERT(allocator != VK_NULL_HANDLE); + VMA_ASSERT(pAllocationCreateInfo != VMA_NULL); + VMA_ASSERT(pMemoryTypeIndex != VMA_NULL); + + return allocator->FindMemoryTypeIndex(memoryTypeBits, pAllocationCreateInfo, VmaBufferImageUsage::UNKNOWN, pMemoryTypeIndex); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForBufferInfo( + VmaAllocator allocator, + const VkBufferCreateInfo* pBufferCreateInfo, + const VmaAllocationCreateInfo* pAllocationCreateInfo, + uint32_t* pMemoryTypeIndex) +{ + VMA_ASSERT(allocator != VK_NULL_HANDLE); + VMA_ASSERT(pBufferCreateInfo != VMA_NULL); + VMA_ASSERT(pAllocationCreateInfo != VMA_NULL); + VMA_ASSERT(pMemoryTypeIndex != VMA_NULL); + + const VkDevice hDev = allocator->m_hDevice; + const VmaVulkanFunctions* funcs = &allocator->GetVulkanFunctions(); + VkResult res; + +#if VMA_KHR_MAINTENANCE4 || VMA_VULKAN_VERSION >= 1003000 + if(funcs->vkGetDeviceBufferMemoryRequirements) + { + // Can query straight from VkBufferCreateInfo :) + VkDeviceBufferMemoryRequirementsKHR devBufMemReq = {VK_STRUCTURE_TYPE_DEVICE_BUFFER_MEMORY_REQUIREMENTS_KHR}; + devBufMemReq.pCreateInfo = pBufferCreateInfo; + + VkMemoryRequirements2 memReq = {VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2}; + (*funcs->vkGetDeviceBufferMemoryRequirements)(hDev, &devBufMemReq, &memReq); + + res = allocator->FindMemoryTypeIndex( + memReq.memoryRequirements.memoryTypeBits, pAllocationCreateInfo, + VmaBufferImageUsage(*pBufferCreateInfo, allocator->m_UseKhrMaintenance5), pMemoryTypeIndex); + } + else +#endif // VMA_KHR_MAINTENANCE4 || VMA_VULKAN_VERSION >= 1003000 + { + // Must create a dummy buffer to query :( + VkBuffer hBuffer = VK_NULL_HANDLE; + res = funcs->vkCreateBuffer( + hDev, pBufferCreateInfo, allocator->GetAllocationCallbacks(), &hBuffer); + if(res == VK_SUCCESS) + { + VkMemoryRequirements memReq = {}; + funcs->vkGetBufferMemoryRequirements(hDev, hBuffer, &memReq); + + res = allocator->FindMemoryTypeIndex( + memReq.memoryTypeBits, pAllocationCreateInfo, + VmaBufferImageUsage(*pBufferCreateInfo, allocator->m_UseKhrMaintenance5), pMemoryTypeIndex); + + funcs->vkDestroyBuffer( + hDev, hBuffer, allocator->GetAllocationCallbacks()); + } + } + return res; +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForImageInfo( + VmaAllocator allocator, + const VkImageCreateInfo* pImageCreateInfo, + const VmaAllocationCreateInfo* pAllocationCreateInfo, + uint32_t* pMemoryTypeIndex) +{ + VMA_ASSERT(allocator != VK_NULL_HANDLE); + VMA_ASSERT(pImageCreateInfo != VMA_NULL); + VMA_ASSERT(pAllocationCreateInfo != VMA_NULL); + VMA_ASSERT(pMemoryTypeIndex != VMA_NULL); + + const VkDevice hDev = allocator->m_hDevice; + const VmaVulkanFunctions* funcs = &allocator->GetVulkanFunctions(); + VkResult res; + +#if VMA_KHR_MAINTENANCE4 || VMA_VULKAN_VERSION >= 1003000 + if(funcs->vkGetDeviceImageMemoryRequirements) + { + // Can query straight from VkImageCreateInfo :) + VkDeviceImageMemoryRequirementsKHR devImgMemReq = {VK_STRUCTURE_TYPE_DEVICE_IMAGE_MEMORY_REQUIREMENTS_KHR}; + devImgMemReq.pCreateInfo = pImageCreateInfo; + VMA_ASSERT(pImageCreateInfo->tiling != VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT_COPY && (pImageCreateInfo->flags & VK_IMAGE_CREATE_DISJOINT_BIT_COPY) == 0 && + "Cannot use this VkImageCreateInfo with vmaFindMemoryTypeIndexForImageInfo as I don't know what to pass as VkDeviceImageMemoryRequirements::planeAspect."); + + VkMemoryRequirements2 memReq = {VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2}; + (*funcs->vkGetDeviceImageMemoryRequirements)(hDev, &devImgMemReq, &memReq); + + res = allocator->FindMemoryTypeIndex( + memReq.memoryRequirements.memoryTypeBits, pAllocationCreateInfo, + VmaBufferImageUsage(*pImageCreateInfo), pMemoryTypeIndex); + } + else +#endif // VMA_KHR_MAINTENANCE4 || VMA_VULKAN_VERSION >= 1003000 + { + // Must create a dummy image to query :( + VkImage hImage = VK_NULL_HANDLE; + res = funcs->vkCreateImage( + hDev, pImageCreateInfo, allocator->GetAllocationCallbacks(), &hImage); + if(res == VK_SUCCESS) + { + VkMemoryRequirements memReq = {}; + funcs->vkGetImageMemoryRequirements(hDev, hImage, &memReq); + + res = allocator->FindMemoryTypeIndex( + memReq.memoryTypeBits, pAllocationCreateInfo, + VmaBufferImageUsage(*pImageCreateInfo), pMemoryTypeIndex); + + funcs->vkDestroyImage( + hDev, hImage, allocator->GetAllocationCallbacks()); + } + } + return res; +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreatePool( + VmaAllocator allocator, + const VmaPoolCreateInfo* pCreateInfo, + VmaPool* pPool) +{ + VMA_ASSERT(allocator && pCreateInfo && pPool); + + VMA_DEBUG_LOG("vmaCreatePool"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->CreatePool(pCreateInfo, pPool); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyPool( + VmaAllocator allocator, + VmaPool pool) +{ + VMA_ASSERT(allocator); + + if(pool == VK_NULL_HANDLE) + { + return; + } + + VMA_DEBUG_LOG("vmaDestroyPool"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + allocator->DestroyPool(pool); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolStatistics( + VmaAllocator allocator, + VmaPool pool, + VmaStatistics* pPoolStats) +{ + VMA_ASSERT(allocator && pool && pPoolStats); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + allocator->GetPoolStatistics(pool, pPoolStats); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaCalculatePoolStatistics( + VmaAllocator allocator, + VmaPool pool, + VmaDetailedStatistics* pPoolStats) +{ + VMA_ASSERT(allocator && pool && pPoolStats); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + allocator->CalculatePoolStatistics(pool, pPoolStats); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckPoolCorruption(VmaAllocator allocator, VmaPool pool) +{ + VMA_ASSERT(allocator && pool); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + VMA_DEBUG_LOG("vmaCheckPoolCorruption"); + + return allocator->CheckPoolCorruption(pool); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolName( + VmaAllocator allocator, + VmaPool pool, + const char** ppName) +{ + VMA_ASSERT(allocator && pool && ppName); + + VMA_DEBUG_LOG("vmaGetPoolName"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + *ppName = pool->GetName(); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaSetPoolName( + VmaAllocator allocator, + VmaPool pool, + const char* pName) +{ + VMA_ASSERT(allocator && pool); + + VMA_DEBUG_LOG("vmaSetPoolName"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + pool->SetName(pName); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemory( + VmaAllocator allocator, + const VkMemoryRequirements* pVkMemoryRequirements, + const VmaAllocationCreateInfo* pCreateInfo, + VmaAllocation* pAllocation, + VmaAllocationInfo* pAllocationInfo) +{ + VMA_ASSERT(allocator && pVkMemoryRequirements && pCreateInfo && pAllocation); + + VMA_DEBUG_LOG("vmaAllocateMemory"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + VkResult result = allocator->AllocateMemory( + *pVkMemoryRequirements, + false, // requiresDedicatedAllocation + false, // prefersDedicatedAllocation + VK_NULL_HANDLE, // dedicatedBuffer + VK_NULL_HANDLE, // dedicatedImage + VmaBufferImageUsage::UNKNOWN, // dedicatedBufferImageUsage + *pCreateInfo, + VMA_SUBALLOCATION_TYPE_UNKNOWN, + 1, // allocationCount + pAllocation); + + if(pAllocationInfo != VMA_NULL && result == VK_SUCCESS) + { + allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); + } + + return result; +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryPages( + VmaAllocator allocator, + const VkMemoryRequirements* pVkMemoryRequirements, + const VmaAllocationCreateInfo* pCreateInfo, + size_t allocationCount, + VmaAllocation* pAllocations, + VmaAllocationInfo* pAllocationInfo) +{ + if(allocationCount == 0) + { + return VK_SUCCESS; + } + + VMA_ASSERT(allocator && pVkMemoryRequirements && pCreateInfo && pAllocations); + + VMA_DEBUG_LOG("vmaAllocateMemoryPages"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + VkResult result = allocator->AllocateMemory( + *pVkMemoryRequirements, + false, // requiresDedicatedAllocation + false, // prefersDedicatedAllocation + VK_NULL_HANDLE, // dedicatedBuffer + VK_NULL_HANDLE, // dedicatedImage + VmaBufferImageUsage::UNKNOWN, // dedicatedBufferImageUsage + *pCreateInfo, + VMA_SUBALLOCATION_TYPE_UNKNOWN, + allocationCount, + pAllocations); + + if(pAllocationInfo != VMA_NULL && result == VK_SUCCESS) + { + for(size_t i = 0; i < allocationCount; ++i) + { + allocator->GetAllocationInfo(pAllocations[i], pAllocationInfo + i); + } + } + + return result; +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForBuffer( + VmaAllocator allocator, + VkBuffer buffer, + const VmaAllocationCreateInfo* pCreateInfo, + VmaAllocation* pAllocation, + VmaAllocationInfo* pAllocationInfo) +{ + VMA_ASSERT(allocator && buffer != VK_NULL_HANDLE && pCreateInfo && pAllocation); + + VMA_DEBUG_LOG("vmaAllocateMemoryForBuffer"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + VkMemoryRequirements vkMemReq = {}; + bool requiresDedicatedAllocation = false; + bool prefersDedicatedAllocation = false; + allocator->GetBufferMemoryRequirements(buffer, vkMemReq, + requiresDedicatedAllocation, + prefersDedicatedAllocation); + + VkResult result = allocator->AllocateMemory( + vkMemReq, + requiresDedicatedAllocation, + prefersDedicatedAllocation, + buffer, // dedicatedBuffer + VK_NULL_HANDLE, // dedicatedImage + VmaBufferImageUsage::UNKNOWN, // dedicatedBufferImageUsage + *pCreateInfo, + VMA_SUBALLOCATION_TYPE_BUFFER, + 1, // allocationCount + pAllocation); + + if(pAllocationInfo && result == VK_SUCCESS) + { + allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); + } + + return result; +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemoryForImage( + VmaAllocator allocator, + VkImage image, + const VmaAllocationCreateInfo* pCreateInfo, + VmaAllocation* pAllocation, + VmaAllocationInfo* pAllocationInfo) +{ + VMA_ASSERT(allocator && image != VK_NULL_HANDLE && pCreateInfo && pAllocation); + + VMA_DEBUG_LOG("vmaAllocateMemoryForImage"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + VkMemoryRequirements vkMemReq = {}; + bool requiresDedicatedAllocation = false; + bool prefersDedicatedAllocation = false; + allocator->GetImageMemoryRequirements(image, vkMemReq, + requiresDedicatedAllocation, prefersDedicatedAllocation); + + VkResult result = allocator->AllocateMemory( + vkMemReq, + requiresDedicatedAllocation, + prefersDedicatedAllocation, + VK_NULL_HANDLE, // dedicatedBuffer + image, // dedicatedImage + VmaBufferImageUsage::UNKNOWN, // dedicatedBufferImageUsage + *pCreateInfo, + VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN, + 1, // allocationCount + pAllocation); + + if(pAllocationInfo && result == VK_SUCCESS) + { + allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); + } + + return result; +} + +VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemory( + VmaAllocator allocator, + VmaAllocation allocation) +{ + VMA_ASSERT(allocator); + + if(allocation == VK_NULL_HANDLE) + { + return; + } + + VMA_DEBUG_LOG("vmaFreeMemory"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + allocator->FreeMemory( + 1, // allocationCount + &allocation); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemoryPages( + VmaAllocator allocator, + size_t allocationCount, + const VmaAllocation* pAllocations) +{ + if(allocationCount == 0) + { + return; + } + + VMA_ASSERT(allocator); + + VMA_DEBUG_LOG("vmaFreeMemoryPages"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + allocator->FreeMemory(allocationCount, pAllocations); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationInfo( + VmaAllocator allocator, + VmaAllocation allocation, + VmaAllocationInfo* pAllocationInfo) +{ + VMA_ASSERT(allocator && allocation && pAllocationInfo); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + allocator->GetAllocationInfo(allocation, pAllocationInfo); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationInfo2( + VmaAllocator allocator, + VmaAllocation allocation, + VmaAllocationInfo2* pAllocationInfo) +{ + VMA_ASSERT(allocator && allocation && pAllocationInfo); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + allocator->GetAllocationInfo2(allocation, pAllocationInfo); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationUserData( + VmaAllocator allocator, + VmaAllocation allocation, + void* pUserData) +{ + VMA_ASSERT(allocator && allocation); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + allocation->SetUserData(allocator, pUserData); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaSetAllocationName( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + const char* VMA_NULLABLE pName) +{ + allocation->SetName(allocator, pName); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationMemoryProperties( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkMemoryPropertyFlags* VMA_NOT_NULL pFlags) +{ + VMA_ASSERT(allocator && allocation && pFlags); + const uint32_t memTypeIndex = allocation->GetMemoryTypeIndex(); + *pFlags = allocator->m_MemProps.memoryTypes[memTypeIndex].propertyFlags; +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaMapMemory( + VmaAllocator allocator, + VmaAllocation allocation, + void** ppData) +{ + VMA_ASSERT(allocator && allocation && ppData); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->Map(allocation, ppData); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaUnmapMemory( + VmaAllocator allocator, + VmaAllocation allocation) +{ + VMA_ASSERT(allocator && allocation); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + allocator->Unmap(allocation); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocation( + VmaAllocator allocator, + VmaAllocation allocation, + VkDeviceSize offset, + VkDeviceSize size) +{ + VMA_ASSERT(allocator && allocation); + + VMA_DEBUG_LOG("vmaFlushAllocation"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_FLUSH); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocation( + VmaAllocator allocator, + VmaAllocation allocation, + VkDeviceSize offset, + VkDeviceSize size) +{ + VMA_ASSERT(allocator && allocation); + + VMA_DEBUG_LOG("vmaInvalidateAllocation"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->FlushOrInvalidateAllocation(allocation, offset, size, VMA_CACHE_INVALIDATE); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocations( + VmaAllocator allocator, + uint32_t allocationCount, + const VmaAllocation* allocations, + const VkDeviceSize* offsets, + const VkDeviceSize* sizes) +{ + VMA_ASSERT(allocator); + + if(allocationCount == 0) + { + return VK_SUCCESS; + } + + VMA_ASSERT(allocations); + + VMA_DEBUG_LOG("vmaFlushAllocations"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->FlushOrInvalidateAllocations(allocationCount, allocations, offsets, sizes, VMA_CACHE_FLUSH); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocations( + VmaAllocator allocator, + uint32_t allocationCount, + const VmaAllocation* allocations, + const VkDeviceSize* offsets, + const VkDeviceSize* sizes) +{ + VMA_ASSERT(allocator); + + if(allocationCount == 0) + { + return VK_SUCCESS; + } + + VMA_ASSERT(allocations); + + VMA_DEBUG_LOG("vmaInvalidateAllocations"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->FlushOrInvalidateAllocations(allocationCount, allocations, offsets, sizes, VMA_CACHE_INVALIDATE); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCopyMemoryToAllocation( + VmaAllocator allocator, + const void* pSrcHostPointer, + VmaAllocation dstAllocation, + VkDeviceSize dstAllocationLocalOffset, + VkDeviceSize size) +{ + VMA_ASSERT(allocator && pSrcHostPointer && dstAllocation); + + if(size == 0) + { + return VK_SUCCESS; + } + + VMA_DEBUG_LOG("vmaCopyMemoryToAllocation"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->CopyMemoryToAllocation(pSrcHostPointer, dstAllocation, dstAllocationLocalOffset, size); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCopyAllocationToMemory( + VmaAllocator allocator, + VmaAllocation srcAllocation, + VkDeviceSize srcAllocationLocalOffset, + void* pDstHostPointer, + VkDeviceSize size) +{ + VMA_ASSERT(allocator && srcAllocation && pDstHostPointer); + + if(size == 0) + { + return VK_SUCCESS; + } + + VMA_DEBUG_LOG("vmaCopyAllocationToMemory"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->CopyAllocationToMemory(srcAllocation, srcAllocationLocalOffset, pDstHostPointer, size); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckCorruption( + VmaAllocator allocator, + uint32_t memoryTypeBits) +{ + VMA_ASSERT(allocator); + + VMA_DEBUG_LOG("vmaCheckCorruption"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->CheckCorruption(memoryTypeBits); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBeginDefragmentation( + VmaAllocator allocator, + const VmaDefragmentationInfo* pInfo, + VmaDefragmentationContext* pContext) +{ + VMA_ASSERT(allocator && pInfo && pContext); + + VMA_DEBUG_LOG("vmaBeginDefragmentation"); + + if (pInfo->pool != VMA_NULL) + { + // Check if run on supported algorithms + if (pInfo->pool->m_BlockVector.GetAlgorithm() & VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT) + return VK_ERROR_FEATURE_NOT_PRESENT; + } + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + *pContext = vma_new(allocator, VmaDefragmentationContext_T)(allocator, *pInfo); + return VK_SUCCESS; +} + +VMA_CALL_PRE void VMA_CALL_POST vmaEndDefragmentation( + VmaAllocator allocator, + VmaDefragmentationContext context, + VmaDefragmentationStats* pStats) +{ + VMA_ASSERT(allocator && context); + + VMA_DEBUG_LOG("vmaEndDefragmentation"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + if (pStats) + context->GetStats(*pStats); + vma_delete(allocator, context); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBeginDefragmentationPass( + VmaAllocator VMA_NOT_NULL allocator, + VmaDefragmentationContext VMA_NOT_NULL context, + VmaDefragmentationPassMoveInfo* VMA_NOT_NULL pPassInfo) +{ + VMA_ASSERT(context && pPassInfo); + + VMA_DEBUG_LOG("vmaBeginDefragmentationPass"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return context->DefragmentPassBegin(*pPassInfo); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaEndDefragmentationPass( + VmaAllocator VMA_NOT_NULL allocator, + VmaDefragmentationContext VMA_NOT_NULL context, + VmaDefragmentationPassMoveInfo* VMA_NOT_NULL pPassInfo) +{ + VMA_ASSERT(context && pPassInfo); + + VMA_DEBUG_LOG("vmaEndDefragmentationPass"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return context->DefragmentPassEnd(*pPassInfo); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory( + VmaAllocator allocator, + VmaAllocation allocation, + VkBuffer buffer) +{ + VMA_ASSERT(allocator && allocation && buffer); + + VMA_DEBUG_LOG("vmaBindBufferMemory"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->BindBufferMemory(allocation, 0, buffer, VMA_NULL); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindBufferMemory2( + VmaAllocator allocator, + VmaAllocation allocation, + VkDeviceSize allocationLocalOffset, + VkBuffer buffer, + const void* pNext) +{ + VMA_ASSERT(allocator && allocation && buffer); + + VMA_DEBUG_LOG("vmaBindBufferMemory2"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->BindBufferMemory(allocation, allocationLocalOffset, buffer, pNext); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory( + VmaAllocator allocator, + VmaAllocation allocation, + VkImage image) +{ + VMA_ASSERT(allocator && allocation && image); + + VMA_DEBUG_LOG("vmaBindImageMemory"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->BindImageMemory(allocation, 0, image, VMA_NULL); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory2( + VmaAllocator allocator, + VmaAllocation allocation, + VkDeviceSize allocationLocalOffset, + VkImage image, + const void* pNext) +{ + VMA_ASSERT(allocator && allocation && image); + + VMA_DEBUG_LOG("vmaBindImageMemory2"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + return allocator->BindImageMemory(allocation, allocationLocalOffset, image, pNext); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer( + VmaAllocator allocator, + const VkBufferCreateInfo* pBufferCreateInfo, + const VmaAllocationCreateInfo* pAllocationCreateInfo, + VkBuffer* pBuffer, + VmaAllocation* pAllocation, + VmaAllocationInfo* pAllocationInfo) +{ + VMA_ASSERT(allocator && pBufferCreateInfo && pAllocationCreateInfo && pBuffer && pAllocation); + + if(pBufferCreateInfo->size == 0) + { + return VK_ERROR_INITIALIZATION_FAILED; + } + if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_COPY) != 0 && + !allocator->m_UseKhrBufferDeviceAddress) + { + VMA_ASSERT(0 && "Creating a buffer with VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT is not valid if VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT was not used."); + return VK_ERROR_INITIALIZATION_FAILED; + } + + VMA_DEBUG_LOG("vmaCreateBuffer"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + *pBuffer = VK_NULL_HANDLE; + *pAllocation = VK_NULL_HANDLE; + + // 1. Create VkBuffer. + VkResult res = (*allocator->GetVulkanFunctions().vkCreateBuffer)( + allocator->m_hDevice, + pBufferCreateInfo, + allocator->GetAllocationCallbacks(), + pBuffer); + if(res >= 0) + { + // 2. vkGetBufferMemoryRequirements. + VkMemoryRequirements vkMemReq = {}; + bool requiresDedicatedAllocation = false; + bool prefersDedicatedAllocation = false; + allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq, + requiresDedicatedAllocation, prefersDedicatedAllocation); + + // 3. Allocate memory using allocator. + res = allocator->AllocateMemory( + vkMemReq, + requiresDedicatedAllocation, + prefersDedicatedAllocation, + *pBuffer, // dedicatedBuffer + VK_NULL_HANDLE, // dedicatedImage + VmaBufferImageUsage(*pBufferCreateInfo, allocator->m_UseKhrMaintenance5), // dedicatedBufferImageUsage + *pAllocationCreateInfo, + VMA_SUBALLOCATION_TYPE_BUFFER, + 1, // allocationCount + pAllocation); + + if(res >= 0) + { + // 3. Bind buffer with memory. + if((pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_DONT_BIND_BIT) == 0) + { + res = allocator->BindBufferMemory(*pAllocation, 0, *pBuffer, VMA_NULL); + } + if(res >= 0) + { + // All steps succeeded. + #if VMA_STATS_STRING_ENABLED + (*pAllocation)->InitBufferUsage(*pBufferCreateInfo, allocator->m_UseKhrMaintenance5); + #endif + if(pAllocationInfo != VMA_NULL) + { + allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); + } + + return VK_SUCCESS; + } + allocator->FreeMemory( + 1, // allocationCount + pAllocation); + *pAllocation = VK_NULL_HANDLE; + (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, *pBuffer, allocator->GetAllocationCallbacks()); + *pBuffer = VK_NULL_HANDLE; + return res; + } + (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, *pBuffer, allocator->GetAllocationCallbacks()); + *pBuffer = VK_NULL_HANDLE; + return res; + } + return res; +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBufferWithAlignment( + VmaAllocator allocator, + const VkBufferCreateInfo* pBufferCreateInfo, + const VmaAllocationCreateInfo* pAllocationCreateInfo, + VkDeviceSize minAlignment, + VkBuffer* pBuffer, + VmaAllocation* pAllocation, + VmaAllocationInfo* pAllocationInfo) +{ + VMA_ASSERT(allocator && pBufferCreateInfo && pAllocationCreateInfo && VmaIsPow2(minAlignment) && pBuffer && pAllocation); + + if(pBufferCreateInfo->size == 0) + { + return VK_ERROR_INITIALIZATION_FAILED; + } + if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_COPY) != 0 && + !allocator->m_UseKhrBufferDeviceAddress) + { + VMA_ASSERT(0 && "Creating a buffer with VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT is not valid if VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT was not used."); + return VK_ERROR_INITIALIZATION_FAILED; + } + + VMA_DEBUG_LOG("vmaCreateBufferWithAlignment"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + *pBuffer = VK_NULL_HANDLE; + *pAllocation = VK_NULL_HANDLE; + + // 1. Create VkBuffer. + VkResult res = (*allocator->GetVulkanFunctions().vkCreateBuffer)( + allocator->m_hDevice, + pBufferCreateInfo, + allocator->GetAllocationCallbacks(), + pBuffer); + if(res >= 0) + { + // 2. vkGetBufferMemoryRequirements. + VkMemoryRequirements vkMemReq = {}; + bool requiresDedicatedAllocation = false; + bool prefersDedicatedAllocation = false; + allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq, + requiresDedicatedAllocation, prefersDedicatedAllocation); + + // 2a. Include minAlignment + vkMemReq.alignment = VMA_MAX(vkMemReq.alignment, minAlignment); + + // 3. Allocate memory using allocator. + res = allocator->AllocateMemory( + vkMemReq, + requiresDedicatedAllocation, + prefersDedicatedAllocation, + *pBuffer, // dedicatedBuffer + VK_NULL_HANDLE, // dedicatedImage + VmaBufferImageUsage(*pBufferCreateInfo, allocator->m_UseKhrMaintenance5), // dedicatedBufferImageUsage + *pAllocationCreateInfo, + VMA_SUBALLOCATION_TYPE_BUFFER, + 1, // allocationCount + pAllocation); + + if(res >= 0) + { + // 3. Bind buffer with memory. + if((pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_DONT_BIND_BIT) == 0) + { + res = allocator->BindBufferMemory(*pAllocation, 0, *pBuffer, VMA_NULL); + } + if(res >= 0) + { + // All steps succeeded. + #if VMA_STATS_STRING_ENABLED + (*pAllocation)->InitBufferUsage(*pBufferCreateInfo, allocator->m_UseKhrMaintenance5); + #endif + if(pAllocationInfo != VMA_NULL) + { + allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); + } + + return VK_SUCCESS; + } + allocator->FreeMemory( + 1, // allocationCount + pAllocation); + *pAllocation = VK_NULL_HANDLE; + (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, *pBuffer, allocator->GetAllocationCallbacks()); + *pBuffer = VK_NULL_HANDLE; + return res; + } + (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, *pBuffer, allocator->GetAllocationCallbacks()); + *pBuffer = VK_NULL_HANDLE; + return res; + } + return res; +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAliasingBuffer( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo, + VkBuffer VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pBuffer) +{ + return vmaCreateAliasingBuffer2(allocator, allocation, 0, pBufferCreateInfo, pBuffer); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAliasingBuffer2( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize allocationLocalOffset, + const VkBufferCreateInfo* VMA_NOT_NULL pBufferCreateInfo, + VkBuffer VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pBuffer) +{ + VMA_ASSERT(allocator && pBufferCreateInfo && pBuffer && allocation); + VMA_ASSERT(allocationLocalOffset + pBufferCreateInfo->size <= allocation->GetSize()); + + VMA_DEBUG_LOG("vmaCreateAliasingBuffer2"); + + *pBuffer = VK_NULL_HANDLE; + + if (pBufferCreateInfo->size == 0) + { + return VK_ERROR_INITIALIZATION_FAILED; + } + if ((pBufferCreateInfo->usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_COPY) != 0 && + !allocator->m_UseKhrBufferDeviceAddress) + { + VMA_ASSERT(0 && "Creating a buffer with VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT is not valid if VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT was not used."); + return VK_ERROR_INITIALIZATION_FAILED; + } + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + // 1. Create VkBuffer. + VkResult res = (*allocator->GetVulkanFunctions().vkCreateBuffer)( + allocator->m_hDevice, + pBufferCreateInfo, + allocator->GetAllocationCallbacks(), + pBuffer); + if (res >= 0) + { + // 2. Bind buffer with memory. + res = allocator->BindBufferMemory(allocation, allocationLocalOffset, *pBuffer, VMA_NULL); + if (res >= 0) + { + return VK_SUCCESS; + } + (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, *pBuffer, allocator->GetAllocationCallbacks()); + } + return res; +} + +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyBuffer( + VmaAllocator allocator, + VkBuffer buffer, + VmaAllocation allocation) +{ + VMA_ASSERT(allocator); + + if(buffer == VK_NULL_HANDLE && allocation == VK_NULL_HANDLE) + { + return; + } + + VMA_DEBUG_LOG("vmaDestroyBuffer"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + if(buffer != VK_NULL_HANDLE) + { + (*allocator->GetVulkanFunctions().vkDestroyBuffer)(allocator->m_hDevice, buffer, allocator->GetAllocationCallbacks()); + } + + if(allocation != VK_NULL_HANDLE) + { + allocator->FreeMemory( + 1, // allocationCount + &allocation); + } +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateImage( + VmaAllocator allocator, + const VkImageCreateInfo* pImageCreateInfo, + const VmaAllocationCreateInfo* pAllocationCreateInfo, + VkImage* pImage, + VmaAllocation* pAllocation, + VmaAllocationInfo* pAllocationInfo) +{ + VMA_ASSERT(allocator && pImageCreateInfo && pAllocationCreateInfo && pImage && pAllocation); + + if(pImageCreateInfo->extent.width == 0 || + pImageCreateInfo->extent.height == 0 || + pImageCreateInfo->extent.depth == 0 || + pImageCreateInfo->mipLevels == 0 || + pImageCreateInfo->arrayLayers == 0) + { + return VK_ERROR_INITIALIZATION_FAILED; + } + + VMA_DEBUG_LOG("vmaCreateImage"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + *pImage = VK_NULL_HANDLE; + *pAllocation = VK_NULL_HANDLE; + + // 1. Create VkImage. + VkResult res = (*allocator->GetVulkanFunctions().vkCreateImage)( + allocator->m_hDevice, + pImageCreateInfo, + allocator->GetAllocationCallbacks(), + pImage); + if(res >= 0) + { + VmaSuballocationType suballocType = pImageCreateInfo->tiling == VK_IMAGE_TILING_OPTIMAL ? + VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL : + VMA_SUBALLOCATION_TYPE_IMAGE_LINEAR; + + // 2. Allocate memory using allocator. + VkMemoryRequirements vkMemReq = {}; + bool requiresDedicatedAllocation = false; + bool prefersDedicatedAllocation = false; + allocator->GetImageMemoryRequirements(*pImage, vkMemReq, + requiresDedicatedAllocation, prefersDedicatedAllocation); + + res = allocator->AllocateMemory( + vkMemReq, + requiresDedicatedAllocation, + prefersDedicatedAllocation, + VK_NULL_HANDLE, // dedicatedBuffer + *pImage, // dedicatedImage + VmaBufferImageUsage(*pImageCreateInfo), // dedicatedBufferImageUsage + *pAllocationCreateInfo, + suballocType, + 1, // allocationCount + pAllocation); + + if(res >= 0) + { + // 3. Bind image with memory. + if((pAllocationCreateInfo->flags & VMA_ALLOCATION_CREATE_DONT_BIND_BIT) == 0) + { + res = allocator->BindImageMemory(*pAllocation, 0, *pImage, VMA_NULL); + } + if(res >= 0) + { + // All steps succeeded. + #if VMA_STATS_STRING_ENABLED + (*pAllocation)->InitImageUsage(*pImageCreateInfo); + #endif + if(pAllocationInfo != VMA_NULL) + { + allocator->GetAllocationInfo(*pAllocation, pAllocationInfo); + } + + return VK_SUCCESS; + } + allocator->FreeMemory( + 1, // allocationCount + pAllocation); + *pAllocation = VK_NULL_HANDLE; + (*allocator->GetVulkanFunctions().vkDestroyImage)(allocator->m_hDevice, *pImage, allocator->GetAllocationCallbacks()); + *pImage = VK_NULL_HANDLE; + return res; + } + (*allocator->GetVulkanFunctions().vkDestroyImage)(allocator->m_hDevice, *pImage, allocator->GetAllocationCallbacks()); + *pImage = VK_NULL_HANDLE; + return res; + } + return res; +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAliasingImage( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo, + VkImage VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pImage) +{ + return vmaCreateAliasingImage2(allocator, allocation, 0, pImageCreateInfo, pImage); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAliasingImage2( + VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, + VkDeviceSize allocationLocalOffset, + const VkImageCreateInfo* VMA_NOT_NULL pImageCreateInfo, + VkImage VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pImage) +{ + VMA_ASSERT(allocator && pImageCreateInfo && pImage && allocation); + + *pImage = VK_NULL_HANDLE; + + VMA_DEBUG_LOG("vmaCreateImage2"); + + if (pImageCreateInfo->extent.width == 0 || + pImageCreateInfo->extent.height == 0 || + pImageCreateInfo->extent.depth == 0 || + pImageCreateInfo->mipLevels == 0 || + pImageCreateInfo->arrayLayers == 0) + { + return VK_ERROR_INITIALIZATION_FAILED; + } + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + // 1. Create VkImage. + VkResult res = (*allocator->GetVulkanFunctions().vkCreateImage)( + allocator->m_hDevice, + pImageCreateInfo, + allocator->GetAllocationCallbacks(), + pImage); + if (res >= 0) + { + // 2. Bind image with memory. + res = allocator->BindImageMemory(allocation, allocationLocalOffset, *pImage, VMA_NULL); + if (res >= 0) + { + return VK_SUCCESS; + } + (*allocator->GetVulkanFunctions().vkDestroyImage)(allocator->m_hDevice, *pImage, allocator->GetAllocationCallbacks()); + } + return res; +} + +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyImage( + VmaAllocator VMA_NOT_NULL allocator, + VkImage VMA_NULLABLE_NON_DISPATCHABLE image, + VmaAllocation VMA_NULLABLE allocation) +{ + VMA_ASSERT(allocator); + + if(image == VK_NULL_HANDLE && allocation == VK_NULL_HANDLE) + { + return; + } + + VMA_DEBUG_LOG("vmaDestroyImage"); + + VMA_DEBUG_GLOBAL_MUTEX_LOCK + + if(image != VK_NULL_HANDLE) + { + (*allocator->GetVulkanFunctions().vkDestroyImage)(allocator->m_hDevice, image, allocator->GetAllocationCallbacks()); + } + if(allocation != VK_NULL_HANDLE) + { + allocator->FreeMemory( + 1, // allocationCount + &allocation); + } +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateVirtualBlock( + const VmaVirtualBlockCreateInfo* VMA_NOT_NULL pCreateInfo, + VmaVirtualBlock VMA_NULLABLE * VMA_NOT_NULL pVirtualBlock) +{ + VMA_ASSERT(pCreateInfo && pVirtualBlock); + VMA_ASSERT(pCreateInfo->size > 0); + VMA_DEBUG_LOG("vmaCreateVirtualBlock"); + VMA_DEBUG_GLOBAL_MUTEX_LOCK; + *pVirtualBlock = vma_new(pCreateInfo->pAllocationCallbacks, VmaVirtualBlock_T)(*pCreateInfo); + VkResult res = (*pVirtualBlock)->Init(); + if(res < 0) + { + vma_delete(pCreateInfo->pAllocationCallbacks, *pVirtualBlock); + *pVirtualBlock = VK_NULL_HANDLE; + } + return res; +} + +VMA_CALL_PRE void VMA_CALL_POST vmaDestroyVirtualBlock(VmaVirtualBlock VMA_NULLABLE virtualBlock) +{ + if(virtualBlock != VK_NULL_HANDLE) + { + VMA_DEBUG_LOG("vmaDestroyVirtualBlock"); + VMA_DEBUG_GLOBAL_MUTEX_LOCK; + VkAllocationCallbacks allocationCallbacks = virtualBlock->m_AllocationCallbacks; // Have to copy the callbacks when destroying. + vma_delete(&allocationCallbacks, virtualBlock); + } +} + +VMA_CALL_PRE VkBool32 VMA_CALL_POST vmaIsVirtualBlockEmpty(VmaVirtualBlock VMA_NOT_NULL virtualBlock) +{ + VMA_ASSERT(virtualBlock != VK_NULL_HANDLE); + VMA_DEBUG_LOG("vmaIsVirtualBlockEmpty"); + VMA_DEBUG_GLOBAL_MUTEX_LOCK; + return virtualBlock->IsEmpty() ? VK_TRUE : VK_FALSE; +} + +VMA_CALL_PRE void VMA_CALL_POST vmaGetVirtualAllocationInfo(VmaVirtualBlock VMA_NOT_NULL virtualBlock, + VmaVirtualAllocation VMA_NOT_NULL_NON_DISPATCHABLE allocation, VmaVirtualAllocationInfo* VMA_NOT_NULL pVirtualAllocInfo) +{ + VMA_ASSERT(virtualBlock != VK_NULL_HANDLE && pVirtualAllocInfo != VMA_NULL); + VMA_DEBUG_LOG("vmaGetVirtualAllocationInfo"); + VMA_DEBUG_GLOBAL_MUTEX_LOCK; + virtualBlock->GetAllocationInfo(allocation, *pVirtualAllocInfo); +} + +VMA_CALL_PRE VkResult VMA_CALL_POST vmaVirtualAllocate(VmaVirtualBlock VMA_NOT_NULL virtualBlock, + const VmaVirtualAllocationCreateInfo* VMA_NOT_NULL pCreateInfo, VmaVirtualAllocation VMA_NULLABLE_NON_DISPATCHABLE* VMA_NOT_NULL pAllocation, + VkDeviceSize* VMA_NULLABLE pOffset) +{ + VMA_ASSERT(virtualBlock != VK_NULL_HANDLE && pCreateInfo != VMA_NULL && pAllocation != VMA_NULL); + VMA_DEBUG_LOG("vmaVirtualAllocate"); + VMA_DEBUG_GLOBAL_MUTEX_LOCK; + return virtualBlock->Allocate(*pCreateInfo, *pAllocation, pOffset); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaVirtualFree(VmaVirtualBlock VMA_NOT_NULL virtualBlock, VmaVirtualAllocation VMA_NULLABLE_NON_DISPATCHABLE allocation) +{ + if(allocation != VK_NULL_HANDLE) + { + VMA_ASSERT(virtualBlock != VK_NULL_HANDLE); + VMA_DEBUG_LOG("vmaVirtualFree"); + VMA_DEBUG_GLOBAL_MUTEX_LOCK; + virtualBlock->Free(allocation); + } +} + +VMA_CALL_PRE void VMA_CALL_POST vmaClearVirtualBlock(VmaVirtualBlock VMA_NOT_NULL virtualBlock) +{ + VMA_ASSERT(virtualBlock != VK_NULL_HANDLE); + VMA_DEBUG_LOG("vmaClearVirtualBlock"); + VMA_DEBUG_GLOBAL_MUTEX_LOCK; + virtualBlock->Clear(); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaSetVirtualAllocationUserData(VmaVirtualBlock VMA_NOT_NULL virtualBlock, + VmaVirtualAllocation VMA_NOT_NULL_NON_DISPATCHABLE allocation, void* VMA_NULLABLE pUserData) +{ + VMA_ASSERT(virtualBlock != VK_NULL_HANDLE); + VMA_DEBUG_LOG("vmaSetVirtualAllocationUserData"); + VMA_DEBUG_GLOBAL_MUTEX_LOCK; + virtualBlock->SetAllocationUserData(allocation, pUserData); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaGetVirtualBlockStatistics(VmaVirtualBlock VMA_NOT_NULL virtualBlock, + VmaStatistics* VMA_NOT_NULL pStats) +{ + VMA_ASSERT(virtualBlock != VK_NULL_HANDLE && pStats != VMA_NULL); + VMA_DEBUG_LOG("vmaGetVirtualBlockStatistics"); + VMA_DEBUG_GLOBAL_MUTEX_LOCK; + virtualBlock->GetStatistics(*pStats); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaCalculateVirtualBlockStatistics(VmaVirtualBlock VMA_NOT_NULL virtualBlock, + VmaDetailedStatistics* VMA_NOT_NULL pStats) +{ + VMA_ASSERT(virtualBlock != VK_NULL_HANDLE && pStats != VMA_NULL); + VMA_DEBUG_LOG("vmaCalculateVirtualBlockStatistics"); + VMA_DEBUG_GLOBAL_MUTEX_LOCK; + virtualBlock->CalculateDetailedStatistics(*pStats); +} + +#if VMA_STATS_STRING_ENABLED + +VMA_CALL_PRE void VMA_CALL_POST vmaBuildVirtualBlockStatsString(VmaVirtualBlock VMA_NOT_NULL virtualBlock, + char* VMA_NULLABLE * VMA_NOT_NULL ppStatsString, VkBool32 detailedMap) +{ + VMA_ASSERT(virtualBlock != VK_NULL_HANDLE && ppStatsString != VMA_NULL); + VMA_DEBUG_GLOBAL_MUTEX_LOCK; + const VkAllocationCallbacks* allocationCallbacks = virtualBlock->GetAllocationCallbacks(); + VmaStringBuilder sb(allocationCallbacks); + virtualBlock->BuildStatsString(detailedMap != VK_FALSE, sb); + *ppStatsString = VmaCreateStringCopy(allocationCallbacks, sb.GetData(), sb.GetLength()); +} + +VMA_CALL_PRE void VMA_CALL_POST vmaFreeVirtualBlockStatsString(VmaVirtualBlock VMA_NOT_NULL virtualBlock, + char* VMA_NULLABLE pStatsString) +{ + if(pStatsString != VMA_NULL) + { + VMA_ASSERT(virtualBlock != VK_NULL_HANDLE); + VMA_DEBUG_GLOBAL_MUTEX_LOCK; + VmaFreeString(virtualBlock->GetAllocationCallbacks(), pStatsString); + } +} +#if VMA_EXTERNAL_MEMORY_WIN32 +VMA_CALL_PRE VkResult VMA_CALL_POST vmaGetMemoryWin32Handle(VmaAllocator VMA_NOT_NULL allocator, + VmaAllocation VMA_NOT_NULL allocation, HANDLE hTargetProcess, HANDLE* VMA_NOT_NULL pHandle) +{ + VMA_ASSERT(allocator && allocation && pHandle); + VMA_DEBUG_GLOBAL_MUTEX_LOCK; + return allocation->GetWin32Handle(allocator, hTargetProcess, pHandle); +} +#endif // VMA_EXTERNAL_MEMORY_WIN32 +#endif // VMA_STATS_STRING_ENABLED +#endif // _VMA_PUBLIC_INTERFACE +#endif // VMA_IMPLEMENTATION + +/** +\page quick_start Quick start + +\section quick_start_project_setup Project setup + +Vulkan Memory Allocator comes in form of a "stb-style" single header file. +While you can pull the entire repository e.g. as Git module, there is also Cmake script provided, +you don't need to build it as a separate library project. +You can add file "vk_mem_alloc.h" directly to your project and submit it to code repository next to your other source files. + +"Single header" doesn't mean that everything is contained in C/C++ declarations, +like it tends to be in case of inline functions or C++ templates. +It means that implementation is bundled with interface in a single file and needs to be extracted using preprocessor macro. +If you don't do it properly, it will result in linker errors. + +To do it properly: + +-# Include "vk_mem_alloc.h" file in each CPP file where you want to use the library. + This includes declarations of all members of the library. +-# In exactly one CPP file define following macro before this include. + It enables also internal definitions. + +\code +#define VMA_IMPLEMENTATION +#include "vk_mem_alloc.h" +\endcode + +It may be a good idea to create dedicated CPP file just for this purpose, e.g. "VmaUsage.cpp". + +This library includes header ``, which in turn +includes `` on Windows. If you need some specific macros defined +before including these headers (like `WIN32_LEAN_AND_MEAN` or +`WINVER` for Windows, `VK_USE_PLATFORM_WIN32_KHR` for Vulkan), you must define +them before every `#include` of this library. +It may be a good idea to create a dedicate header file for this purpose, e.g. "VmaUsage.h", +that will be included in other source files instead of VMA header directly. + +This library is written in C++, but has C-compatible interface. +Thus, you can include and use "vk_mem_alloc.h" in C or C++ code, but full +implementation with `VMA_IMPLEMENTATION` macro must be compiled as C++, NOT as C. +Some features of C++14 are used and required. Features of C++20 are used optionally when available. +Some headers of standard C and C++ library are used, but STL containers, RTTI, or C++ exceptions are not used. + + +\section quick_start_initialization Initialization + +VMA offers library interface in a style similar to Vulkan, with object handles like #VmaAllocation, +structures describing parameters of objects to be created like #VmaAllocationCreateInfo, +and errors codes returned from functions using `VkResult` type. + +The first and the main object that needs to be created is #VmaAllocator. +It represents the initialization of the entire library. +Only one such object should be created per `VkDevice`. +You should create it at program startup, after `VkDevice` was created, and before any device memory allocator needs to be made. +It must be destroyed before `VkDevice` is destroyed. + +At program startup: + +-# Initialize Vulkan to have `VkInstance`, `VkPhysicalDevice`, `VkDevice` object. +-# Fill VmaAllocatorCreateInfo structure and call vmaCreateAllocator() to create #VmaAllocator object. + +Only members `physicalDevice`, `device`, `instance` are required. +However, you should inform the library which Vulkan version do you use by setting +VmaAllocatorCreateInfo::vulkanApiVersion and which extensions did you enable +by setting VmaAllocatorCreateInfo::flags. +Otherwise, VMA would use only features of Vulkan 1.0 core with no extensions. +See below for details. + +\subsection quick_start_initialization_selecting_vulkan_version Selecting Vulkan version + +VMA supports Vulkan version down to 1.0, for backward compatibility. +If you want to use higher version, you need to inform the library about it. +This is a two-step process. + +Step 1: Compile time. By default, VMA compiles with code supporting the highest +Vulkan version found in the included `` that is also supported by the library. +If this is OK, you don't need to do anything. +However, if you want to compile VMA as if only some lower Vulkan version was available, +define macro `VMA_VULKAN_VERSION` before every `#include "vk_mem_alloc.h"`. +It should have decimal numeric value in form of ABBBCCC, where A = major, BBB = minor, CCC = patch Vulkan version. +For example, to compile against Vulkan 1.2: + +\code +#define VMA_VULKAN_VERSION 1002000 // Vulkan 1.2 +#include "vk_mem_alloc.h" +\endcode + +Step 2: Runtime. Even when compiled with higher Vulkan version available, +VMA can use only features of a lower version, which is configurable during creation of the #VmaAllocator object. +By default, only Vulkan 1.0 is used. +To initialize the allocator with support for higher Vulkan version, you need to set member +VmaAllocatorCreateInfo::vulkanApiVersion to an appropriate value, e.g. using constants like `VK_API_VERSION_1_2`. +See code sample below. + +\subsection quick_start_initialization_importing_vulkan_functions Importing Vulkan functions + +You may need to configure importing Vulkan functions. There are 3 ways to do this: + +-# **If you link with Vulkan static library** (e.g. "vulkan-1.lib" on Windows): + - You don't need to do anything. + - VMA will use these, as macro `VMA_STATIC_VULKAN_FUNCTIONS` is defined to 1 by default. +-# **If you want VMA to fetch pointers to Vulkan functions dynamically** using `vkGetInstanceProcAddr`, + `vkGetDeviceProcAddr` (this is the option presented in the example below): + - Define `VMA_STATIC_VULKAN_FUNCTIONS` to 0, `VMA_DYNAMIC_VULKAN_FUNCTIONS` to 1. + - Provide pointers to these two functions via VmaVulkanFunctions::vkGetInstanceProcAddr, + VmaVulkanFunctions::vkGetDeviceProcAddr. + - The library will fetch pointers to all other functions it needs internally. +-# **If you fetch pointers to all Vulkan functions in a custom way**, e.g. using some loader like + [Volk](https://github.com/zeux/volk): + - Define `VMA_STATIC_VULKAN_FUNCTIONS` and `VMA_DYNAMIC_VULKAN_FUNCTIONS` to 0. + - Pass these pointers via structure #VmaVulkanFunctions. + +\subsection quick_start_initialization_enabling_extensions Enabling extensions + +VMA can automatically use following Vulkan extensions. +If you found them available on the selected physical device and you enabled them +while creating `VkInstance` / `VkDevice` object, inform VMA about their availability +by setting appropriate flags in VmaAllocatorCreateInfo::flags. + +Vulkan extension | VMA flag +------------------------------|----------------------------------------------------- +VK_KHR_dedicated_allocation | #VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT +VK_KHR_bind_memory2 | #VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT +VK_KHR_maintenance4 | #VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE4_BIT +VK_KHR_maintenance5 | #VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT +VK_EXT_memory_budget | #VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT +VK_KHR_buffer_device_address | #VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT +VK_EXT_memory_priority | #VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT +VK_AMD_device_coherent_memory | #VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT +VK_KHR_external_memory_win32 | #VMA_ALLOCATOR_CREATE_KHR_EXTERNAL_MEMORY_WIN32_BIT + +Example with fetching pointers to Vulkan functions dynamically: + +\code +#define VMA_STATIC_VULKAN_FUNCTIONS 0 +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 +#include "vk_mem_alloc.h" + +... + +VmaVulkanFunctions vulkanFunctions = {}; +vulkanFunctions.vkGetInstanceProcAddr = &vkGetInstanceProcAddr; +vulkanFunctions.vkGetDeviceProcAddr = &vkGetDeviceProcAddr; + +VmaAllocatorCreateInfo allocatorCreateInfo = {}; +allocatorCreateInfo.flags = VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; +allocatorCreateInfo.vulkanApiVersion = VK_API_VERSION_1_2; +allocatorCreateInfo.physicalDevice = physicalDevice; +allocatorCreateInfo.device = device; +allocatorCreateInfo.instance = instance; +allocatorCreateInfo.pVulkanFunctions = &vulkanFunctions; + +VmaAllocator allocator; +vmaCreateAllocator(&allocatorCreateInfo, &allocator); + +// Entire program... + +// At the end, don't forget to: +vmaDestroyAllocator(allocator); +\endcode + + +\subsection quick_start_initialization_other_config Other configuration options + +There are additional configuration options available through preprocessor macros that you can define +before including VMA header and through parameters passed in #VmaAllocatorCreateInfo. +They include a possibility to use your own callbacks for host memory allocations (`VkAllocationCallbacks`), +callbacks for device memory allocations (instead of `vkAllocateMemory`, `vkFreeMemory`), +or your custom `VMA_ASSERT` macro, among others. +For more information, see: @ref configuration. + + +\section quick_start_resource_allocation Resource allocation + +When you want to create a buffer or image: + +-# Fill `VkBufferCreateInfo` / `VkImageCreateInfo` structure. +-# Fill VmaAllocationCreateInfo structure. +-# Call vmaCreateBuffer() / vmaCreateImage() to get `VkBuffer`/`VkImage` with memory + already allocated and bound to it, plus #VmaAllocation objects that represents its underlying memory. + +\code +VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; +bufferInfo.size = 65536; +bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + +VmaAllocationCreateInfo allocInfo = {}; +allocInfo.usage = VMA_MEMORY_USAGE_AUTO; + +VkBuffer buffer; +VmaAllocation allocation; +vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr); +\endcode + +Don't forget to destroy your buffer and allocation objects when no longer needed: + +\code +vmaDestroyBuffer(allocator, buffer, allocation); +\endcode + +If you need to map the buffer, you must set flag +#VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT +in VmaAllocationCreateInfo::flags. +There are many additional parameters that can control the choice of memory type to be used for the allocation +and other features. +For more information, see documentation chapters: @ref choosing_memory_type, @ref memory_mapping. + + +\page choosing_memory_type Choosing memory type + +Physical devices in Vulkan support various combinations of memory heaps and +types. Help with choosing correct and optimal memory type for your specific +resource is one of the key features of this library. You can use it by filling +appropriate members of VmaAllocationCreateInfo structure, as described below. +You can also combine multiple methods. + +-# If you just want to find memory type index that meets your requirements, you + can use function: vmaFindMemoryTypeIndexForBufferInfo(), + vmaFindMemoryTypeIndexForImageInfo(), vmaFindMemoryTypeIndex(). +-# If you want to allocate a region of device memory without association with any + specific image or buffer, you can use function vmaAllocateMemory(). Usage of + this function is not recommended and usually not needed. + vmaAllocateMemoryPages() function is also provided for creating multiple allocations at once, + which may be useful for sparse binding. +-# If you already have a buffer or an image created, you want to allocate memory + for it and then you will bind it yourself, you can use function + vmaAllocateMemoryForBuffer(), vmaAllocateMemoryForImage(). + For binding you should use functions: vmaBindBufferMemory(), vmaBindImageMemory() + or their extended versions: vmaBindBufferMemory2(), vmaBindImageMemory2(). +-# If you want to create a buffer or an image, allocate memory for it, and bind + them together, all in one call, you can use function vmaCreateBuffer(), + vmaCreateImage(). + This is the easiest and recommended way to use this library! + +When using 3. or 4., the library internally queries Vulkan for memory types +supported for that buffer or image (function `vkGetBufferMemoryRequirements()`) +and uses only one of these types. + +If no memory type can be found that meets all the requirements, these functions +return `VK_ERROR_FEATURE_NOT_PRESENT`. + +You can leave VmaAllocationCreateInfo structure completely filled with zeros. +It means no requirements are specified for memory type. +It is valid, although not very useful. + +\section choosing_memory_type_usage Usage + +The easiest way to specify memory requirements is to fill member +VmaAllocationCreateInfo::usage using one of the values of enum #VmaMemoryUsage. +It defines high level, common usage types. +Since version 3 of the library, it is recommended to use #VMA_MEMORY_USAGE_AUTO to let it select best memory type for your resource automatically. + +For example, if you want to create a uniform buffer that will be filled using +transfer only once or infrequently and then used for rendering every frame as a uniform buffer, you can +do it using following code. The buffer will most likely end up in a memory type with +`VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT` to be fast to access by the GPU device. + +\code +VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; +bufferInfo.size = 65536; +bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + +VmaAllocationCreateInfo allocInfo = {}; +allocInfo.usage = VMA_MEMORY_USAGE_AUTO; + +VkBuffer buffer; +VmaAllocation allocation; +vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr); +\endcode + +If you have a preference for putting the resource in GPU (device) memory or CPU (host) memory +on systems with discrete graphics card that have the memories separate, you can use +#VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE or #VMA_MEMORY_USAGE_AUTO_PREFER_HOST. + +When using `VMA_MEMORY_USAGE_AUTO*` while you want to map the allocated memory, +you also need to specify one of the host access flags: +#VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT. +This will help the library decide about preferred memory type to ensure it has `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` +so you can map it. + +For example, a staging buffer that will be filled via mapped pointer and then +used as a source of transfer to the buffer described previously can be created like this. +It will likely end up in a memory type that is `HOST_VISIBLE` and `HOST_COHERENT` +but not `HOST_CACHED` (meaning uncached, write-combined) and not `DEVICE_LOCAL` (meaning system RAM). + +\code +VkBufferCreateInfo stagingBufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; +stagingBufferInfo.size = 65536; +stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + +VmaAllocationCreateInfo stagingAllocInfo = {}; +stagingAllocInfo.usage = VMA_MEMORY_USAGE_AUTO; +stagingAllocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + +VkBuffer stagingBuffer; +VmaAllocation stagingAllocation; +vmaCreateBuffer(allocator, &stagingBufferInfo, &stagingAllocInfo, &stagingBuffer, &stagingAllocation, nullptr); +\endcode + +For more examples of creating different kinds of resources, see chapter \ref usage_patterns. +See also: @ref memory_mapping. + +Usage values `VMA_MEMORY_USAGE_AUTO*` are legal to use only when the library knows +about the resource being created by having `VkBufferCreateInfo` / `VkImageCreateInfo` passed, +so they work with functions like: vmaCreateBuffer(), vmaCreateImage(), vmaFindMemoryTypeIndexForBufferInfo() etc. +If you allocate raw memory using function vmaAllocateMemory(), you have to use other means of selecting +memory type, as described below. + +\note +Old usage values (`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`) +are still available and work same way as in previous versions of the library +for backward compatibility, but they are deprecated. + +\section choosing_memory_type_required_preferred_flags Required and preferred flags + +You can specify more detailed requirements by filling members +VmaAllocationCreateInfo::requiredFlags and VmaAllocationCreateInfo::preferredFlags +with a combination of bits from enum `VkMemoryPropertyFlags`. For example, +if you want to create a buffer that will be persistently mapped on host (so it +must be `HOST_VISIBLE`) and preferably will also be `HOST_COHERENT` and `HOST_CACHED`, +use following code: + +\code +VmaAllocationCreateInfo allocInfo = {}; +allocInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; +allocInfo.preferredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; +allocInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; + +VkBuffer buffer; +VmaAllocation allocation; +vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr); +\endcode + +A memory type is chosen that has all the required flags and as many preferred +flags set as possible. + +Value passed in VmaAllocationCreateInfo::usage is internally converted to a set of required and preferred flags, +plus some extra "magic" (heuristics). + +\section choosing_memory_type_explicit_memory_types Explicit memory types + +If you inspected memory types available on the physical device and you have +a preference for memory types that you want to use, you can fill member +VmaAllocationCreateInfo::memoryTypeBits. It is a bit mask, where each bit set +means that a memory type with that index is allowed to be used for the +allocation. Special value 0, just like `UINT32_MAX`, means there are no +restrictions to memory type index. + +Please note that this member is NOT just a memory type index. +Still you can use it to choose just one, specific memory type. +For example, if you already determined that your buffer should be created in +memory type 2, use following code: + +\code +uint32_t memoryTypeIndex = 2; + +VmaAllocationCreateInfo allocInfo = {}; +allocInfo.memoryTypeBits = 1u << memoryTypeIndex; + +VkBuffer buffer; +VmaAllocation allocation; +vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr); +\endcode + +You can also use this parameter to exclude some memory types. +If you inspect memory heaps and types available on the current physical device and +you determine that for some reason you don't want to use a specific memory type for the allocation, +you can enable automatic memory type selection but exclude certain memory type or types +by setting all bits of `memoryTypeBits` to 1 except the ones you choose. + +\code +// ... +uint32_t excludedMemoryTypeIndex = 2; +VmaAllocationCreateInfo allocInfo = {}; +allocInfo.usage = VMA_MEMORY_USAGE_AUTO; +allocInfo.memoryTypeBits = ~(1u << excludedMemoryTypeIndex); +// ... +\endcode + + +\section choosing_memory_type_custom_memory_pools Custom memory pools + +If you allocate from custom memory pool, all the ways of specifying memory +requirements described above are not applicable and the aforementioned members +of VmaAllocationCreateInfo structure are ignored. Memory type is selected +explicitly when creating the pool and then used to make all the allocations from +that pool. For further details, see \ref custom_memory_pools. + +\section choosing_memory_type_dedicated_allocations Dedicated allocations + +Memory for allocations is reserved out of larger block of `VkDeviceMemory` +allocated from Vulkan internally. That is the main feature of this whole library. +You can still request a separate memory block to be created for an allocation, +just like you would do in a trivial solution without using any allocator. +In that case, a buffer or image is always bound to that memory at offset 0. +This is called a "dedicated allocation". +You can explicitly request it by using flag #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. +The library can also internally decide to use dedicated allocation in some cases, e.g.: + +- When the size of the allocation is large. +- When [VK_KHR_dedicated_allocation](@ref vk_khr_dedicated_allocation) extension is enabled + and it reports that dedicated allocation is required or recommended for the resource. +- When allocation of next big memory block fails due to not enough device memory, + but allocation with the exact requested size succeeds. + + +\page memory_mapping Memory mapping + +To "map memory" in Vulkan means to obtain a CPU pointer to `VkDeviceMemory`, +to be able to read from it or write to it in CPU code. +Mapping is possible only of memory allocated from a memory type that has +`VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` flag. +Functions `vkMapMemory()`, `vkUnmapMemory()` are designed for this purpose. +You can use them directly with memory allocated by this library, +but it is not recommended because of following issue: +Mapping the same `VkDeviceMemory` block multiple times is illegal - only one mapping at a time is allowed. +This includes mapping disjoint regions. Mapping is not reference-counted internally by Vulkan. +It is also not thread-safe. +Because of this, Vulkan Memory Allocator provides following facilities: + +\note If you want to be able to map an allocation, you need to specify one of the flags +#VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT +in VmaAllocationCreateInfo::flags. These flags are required for an allocation to be mappable +when using #VMA_MEMORY_USAGE_AUTO or other `VMA_MEMORY_USAGE_AUTO*` enum values. +For other usage values they are ignored and every such allocation made in `HOST_VISIBLE` memory type is mappable, +but these flags can still be used for consistency. + +\section memory_mapping_copy_functions Copy functions + +The easiest way to copy data from a host pointer to an allocation is to use convenience function vmaCopyMemoryToAllocation(). +It automatically maps the Vulkan memory temporarily (if not already mapped), performs `memcpy`, +and calls `vkFlushMappedMemoryRanges` (if required - if memory type is not `HOST_COHERENT`). + +It is also the safest one, because using `memcpy` avoids a risk of accidentally introducing memory reads +(e.g. by doing `pMappedVectors[i] += v`), which may be very slow on memory types that are not `HOST_CACHED`. + +\code +struct ConstantBuffer +{ + ... +}; +ConstantBuffer constantBufferData = ... + +VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; +bufCreateInfo.size = sizeof(ConstantBuffer); +bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; +allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + +VkBuffer buf; +VmaAllocation alloc; +vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, nullptr); + +vmaCopyMemoryToAllocation(allocator, &constantBufferData, alloc, 0, sizeof(ConstantBuffer)); +\endcode + +Copy in the other direction - from an allocation to a host pointer can be performed the same way using function vmaCopyAllocationToMemory(). + +\section memory_mapping_mapping_functions Mapping functions + +The library provides following functions for mapping of a specific allocation: vmaMapMemory(), vmaUnmapMemory(). +They are safer and more convenient to use than standard Vulkan functions. +You can map an allocation multiple times simultaneously - mapping is reference-counted internally. +You can also map different allocations simultaneously regardless of whether they use the same `VkDeviceMemory` block. +The way it is implemented is that the library always maps entire memory block, not just region of the allocation. +For further details, see description of vmaMapMemory() function. +Example: + +\code +// Having these objects initialized: +struct ConstantBuffer +{ + ... +}; +ConstantBuffer constantBufferData = ... + +VmaAllocator allocator = ... +VkBuffer constantBuffer = ... +VmaAllocation constantBufferAllocation = ... + +// You can map and fill your buffer using following code: + +void* mappedData; +vmaMapMemory(allocator, constantBufferAllocation, &mappedData); +memcpy(mappedData, &constantBufferData, sizeof(constantBufferData)); +vmaUnmapMemory(allocator, constantBufferAllocation); +\endcode + +When mapping, you may see a warning from Vulkan validation layer similar to this one: + +Mapping an image with layout VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL can result in undefined behavior if this memory is used by the device. Only GENERAL or PREINITIALIZED should be used. + +It happens because the library maps entire `VkDeviceMemory` block, where different +types of images and buffers may end up together, especially on GPUs with unified memory like Intel. +You can safely ignore it if you are sure you access only memory of the intended +object that you wanted to map. + + +\section memory_mapping_persistently_mapped_memory Persistently mapped memory + +Keeping your memory persistently mapped is generally OK in Vulkan. +You don't need to unmap it before using its data on the GPU. +The library provides a special feature designed for that: +Allocations made with #VMA_ALLOCATION_CREATE_MAPPED_BIT flag set in +VmaAllocationCreateInfo::flags stay mapped all the time, +so you can just access CPU pointer to it any time +without a need to call any "map" or "unmap" function. +Example: + +\code +VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; +bufCreateInfo.size = sizeof(ConstantBuffer); +bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; +allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT; + +VkBuffer buf; +VmaAllocation alloc; +VmaAllocationInfo allocInfo; +vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + +// Buffer is already mapped. You can access its memory. +memcpy(allocInfo.pMappedData, &constantBufferData, sizeof(constantBufferData)); +\endcode + +\note #VMA_ALLOCATION_CREATE_MAPPED_BIT by itself doesn't guarantee that the allocation will end up +in a mappable memory type. +For this, you need to also specify #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or +#VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT. +#VMA_ALLOCATION_CREATE_MAPPED_BIT only guarantees that if the memory is `HOST_VISIBLE`, the allocation will be mapped on creation. +For an example of how to make use of this fact, see section \ref usage_patterns_advanced_data_uploading. + +\section memory_mapping_cache_control Cache flush and invalidate + +Memory in Vulkan doesn't need to be unmapped before using it on GPU, +but unless a memory types has `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` flag set, +you need to manually **invalidate** cache before reading of mapped pointer +and **flush** cache after writing to mapped pointer. +Map/unmap operations don't do that automatically. +Vulkan provides following functions for this purpose `vkFlushMappedMemoryRanges()`, +`vkInvalidateMappedMemoryRanges()`, but this library provides more convenient +functions that refer to given allocation object: vmaFlushAllocation(), +vmaInvalidateAllocation(), +or multiple objects at once: vmaFlushAllocations(), vmaInvalidateAllocations(). + +Regions of memory specified for flush/invalidate must be aligned to +`VkPhysicalDeviceLimits::nonCoherentAtomSize`. This is automatically ensured by the library. +In any memory type that is `HOST_VISIBLE` but not `HOST_COHERENT`, all allocations +within blocks are aligned to this value, so their offsets are always multiply of +`nonCoherentAtomSize` and two different allocations never share same "line" of this size. + +Also, Windows drivers from all 3 PC GPU vendors (AMD, Intel, NVIDIA) +currently provide `HOST_COHERENT` flag on all memory types that are +`HOST_VISIBLE`, so on PC you may not need to bother. + + +\page staying_within_budget Staying within budget + +When developing a graphics-intensive game or program, it is important to avoid allocating +more GPU memory than it is physically available. When the memory is over-committed, +various bad things can happen, depending on the specific GPU, graphics driver, and +operating system: + +- It may just work without any problems. +- The application may slow down because some memory blocks are moved to system RAM + and the GPU has to access them through PCI Express bus. +- A new allocation may take very long time to complete, even few seconds, and possibly + freeze entire system. +- The new allocation may fail with `VK_ERROR_OUT_OF_DEVICE_MEMORY`. +- It may even result in GPU crash (TDR), observed as `VK_ERROR_DEVICE_LOST` + returned somewhere later. + +\section staying_within_budget_querying_for_budget Querying for budget + +To query for current memory usage and available budget, use function vmaGetHeapBudgets(). +Returned structure #VmaBudget contains quantities expressed in bytes, per Vulkan memory heap. + +Please note that this function returns different information and works faster than +vmaCalculateStatistics(). vmaGetHeapBudgets() can be called every frame or even before every +allocation, while vmaCalculateStatistics() is intended to be used rarely, +only to obtain statistical information, e.g. for debugging purposes. + +It is recommended to use VK_EXT_memory_budget device extension to obtain information +about the budget from Vulkan device. VMA is able to use this extension automatically. +When not enabled, the allocator behaves same way, but then it estimates current usage +and available budget based on its internal information and Vulkan memory heap sizes, +which may be less precise. In order to use this extension: + +1. Make sure extensions VK_EXT_memory_budget and VK_KHR_get_physical_device_properties2 + required by it are available and enable them. Please note that the first is a device + extension and the second is instance extension! +2. Use flag #VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT when creating #VmaAllocator object. +3. Make sure to call vmaSetCurrentFrameIndex() every frame. Budget is queried from + Vulkan inside of it to avoid overhead of querying it with every allocation. + +\section staying_within_budget_controlling_memory_usage Controlling memory usage + +There are many ways in which you can try to stay within the budget. + +First, when making new allocation requires allocating a new memory block, the library +tries not to exceed the budget automatically. If a block with default recommended size +(e.g. 256 MB) would go over budget, a smaller block is allocated, possibly even +dedicated memory for just this resource. + +If the size of the requested resource plus current memory usage is more than the +budget, by default the library still tries to create it, leaving it to the Vulkan +implementation whether the allocation succeeds or fails. You can change this behavior +by using #VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT flag. With it, the allocation is +not made if it would exceed the budget or if the budget is already exceeded. +VMA then tries to make the allocation from the next eligible Vulkan memory type. +If all of them fail, the call then fails with `VK_ERROR_OUT_OF_DEVICE_MEMORY`. +Example usage pattern may be to pass the #VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT flag +when creating resources that are not essential for the application (e.g. the texture +of a specific object) and not to pass it when creating critically important resources +(e.g. render targets). + +On AMD graphics cards there is a custom vendor extension available: VK_AMD_memory_overallocation_behavior +that allows to control the behavior of the Vulkan implementation in out-of-memory cases - +whether it should fail with an error code or still allow the allocation. +Usage of this extension involves only passing extra structure on Vulkan device creation, +so it is out of scope of this library. + +Finally, you can also use #VMA_ALLOCATION_CREATE_NEVER_ALLOCATE_BIT flag to make sure +a new allocation is created only when it fits inside one of the existing memory blocks. +If it would require to allocate a new block, if fails instead with `VK_ERROR_OUT_OF_DEVICE_MEMORY`. +This also ensures that the function call is very fast because it never goes to Vulkan +to obtain a new block. + +\note Creating \ref custom_memory_pools with VmaPoolCreateInfo::minBlockCount +set to more than 0 will currently try to allocate memory blocks without checking whether they +fit within budget. + + +\page resource_aliasing Resource aliasing (overlap) + +New explicit graphics APIs (Vulkan and Direct3D 12), thanks to manual memory +management, give an opportunity to alias (overlap) multiple resources in the +same region of memory - a feature not available in the old APIs (Direct3D 11, OpenGL). +It can be useful to save video memory, but it must be used with caution. + +For example, if you know the flow of your whole render frame in advance, you +are going to use some intermediate textures or buffers only during a small range of render passes, +and you know these ranges don't overlap in time, you can bind these resources to +the same place in memory, even if they have completely different parameters (width, height, format etc.). + +![Resource aliasing (overlap)](../gfx/Aliasing.png) + +Such scenario is possible using VMA, but you need to create your images manually. +Then you need to calculate parameters of an allocation to be made using formula: + +- allocation size = max(size of each image) +- allocation alignment = max(alignment of each image) +- allocation memoryTypeBits = bitwise AND(memoryTypeBits of each image) + +Following example shows two different images bound to the same place in memory, +allocated to fit largest of them. + +\code +// 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; +res = vkCreateImage(device, &img1CreateInfo, nullptr, &img1); +VkImage img2; +res = vkCreateImage(device, &img2CreateInfo, nullptr, &img2); + +VkMemoryRequirements img1MemReq; +vkGetImageMemoryRequirements(device, img1, &img1MemReq); +VkMemoryRequirements img2MemReq; +vkGetImageMemoryRequirements(device, 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; +// Validate if(finalMemReq.memoryTypeBits != 0) + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + +VmaAllocation alloc; +res = vmaAllocateMemory(allocator, &finalMemReq, &allocCreateInfo, &alloc, nullptr); + +res = vmaBindImageMemory(allocator, alloc, img1); +res = vmaBindImageMemory(allocator, alloc, img2); + +// You can use img1, img2 here, but not at the same time! + +vmaFreeMemory(allocator, alloc); +vkDestroyImage(allocator, img2, nullptr); +vkDestroyImage(allocator, img1, nullptr); +\endcode + +VMA also provides convenience functions that create a buffer or image and bind it to memory +represented by an existing #VmaAllocation: +vmaCreateAliasingBuffer(), vmaCreateAliasingBuffer2(), +vmaCreateAliasingImage(), vmaCreateAliasingImage2(). +Versions with "2" offer additional parameter `allocationLocalOffset`. + +Remember that using resources that alias in memory requires proper synchronization. +You need to issue a memory barrier to make sure commands that use `img1` and `img2` +don't overlap on GPU timeline. +You also need to treat a resource after aliasing as uninitialized - containing garbage data. +For example, if you use `img1` and then want to use `img2`, you need to issue +an image memory barrier for `img2` with `oldLayout` = `VK_IMAGE_LAYOUT_UNDEFINED`. + +Additional considerations: + +- Vulkan also allows to interpret contents of memory between aliasing resources consistently in some cases. +See chapter 11.8. "Memory Aliasing" of Vulkan specification or `VK_IMAGE_CREATE_ALIAS_BIT` flag. +- You can create more complex layout where different images and buffers are bound +at different offsets inside one large allocation. For example, one can imagine +a big texture used in some render passes, aliasing with a set of many small buffers +used between in some further passes. To bind a resource at non-zero offset in an allocation, +use vmaBindBufferMemory2() / vmaBindImageMemory2(). +- Before allocating memory for the resources you want to alias, check `memoryTypeBits` +returned in memory requirements of each resource to make sure the bits overlap. +Some GPUs may expose multiple memory types suitable e.g. only for buffers or +images with `COLOR_ATTACHMENT` usage, so the sets of memory types supported by your +resources may be disjoint. Aliasing them is not possible in that case. + + +\page custom_memory_pools Custom memory pools + +A memory pool contains a number of `VkDeviceMemory` blocks. +The library automatically creates and manages default pool for each memory type available on the device. +Default memory pool automatically grows in size. +Size of allocated blocks is also variable and managed automatically. +You are using default pools whenever you leave VmaAllocationCreateInfo::pool = null. + +You can create custom pool and allocate memory out of it. +It can be useful if you want to: + +- Keep certain kind of allocations separate from others. +- Enforce particular, fixed size of Vulkan memory blocks. +- Limit maximum amount of Vulkan memory allocated for that pool. +- Reserve minimum or fixed amount of Vulkan memory always preallocated for that pool. +- Use extra parameters for a set of your allocations that are available in #VmaPoolCreateInfo but not in + #VmaAllocationCreateInfo - e.g., custom minimum alignment, custom `pNext` chain. +- Perform defragmentation on a specific subset of your allocations. + +To use custom memory pools: + +-# Fill VmaPoolCreateInfo structure. +-# Call vmaCreatePool() to obtain #VmaPool handle. +-# When making an allocation, set VmaAllocationCreateInfo::pool to this handle. + You don't need to specify any other parameters of this structure, like `usage`. + +Example: + +\code +// Find memoryTypeIndex for the pool. +VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; +sampleBufCreateInfo.size = 0x10000; // Doesn't matter. +sampleBufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + +VmaAllocationCreateInfo sampleAllocCreateInfo = {}; +sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; + +uint32_t memTypeIndex; +VkResult res = vmaFindMemoryTypeIndexForBufferInfo(allocator, + &sampleBufCreateInfo, &sampleAllocCreateInfo, &memTypeIndex); +// Check res... + +// Create a pool that can have at most 2 blocks, 128 MiB each. +VmaPoolCreateInfo poolCreateInfo = {}; +poolCreateInfo.memoryTypeIndex = memTypeIndex; +poolCreateInfo.blockSize = 128ull * 1024 * 1024; +poolCreateInfo.maxBlockCount = 2; + +VmaPool pool; +res = vmaCreatePool(allocator, &poolCreateInfo, &pool); +// Check res... + +// Allocate a buffer out of it. +VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; +bufCreateInfo.size = 1024; +bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.pool = pool; + +VkBuffer buf; +VmaAllocation alloc; +res = vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, nullptr); +// Check res... +\endcode + +You have to free all allocations made from this pool before destroying it. + +\code +vmaDestroyBuffer(allocator, buf, alloc); +vmaDestroyPool(allocator, pool); +\endcode + +New versions of this library support creating dedicated allocations in custom pools. +It is supported only when VmaPoolCreateInfo::blockSize = 0. +To use this feature, set VmaAllocationCreateInfo::pool to the pointer to your custom pool and +VmaAllocationCreateInfo::flags to #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. + + +\section custom_memory_pools_MemTypeIndex Choosing memory type index + +When creating a pool, you must explicitly specify memory type index. +To find the one suitable for your buffers or images, you can use helper functions +vmaFindMemoryTypeIndexForBufferInfo(), vmaFindMemoryTypeIndexForImageInfo(). +You need to provide structures with example parameters of buffers or images +that you are going to create in that pool. + +\code +VkBufferCreateInfo exampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; +exampleBufCreateInfo.size = 1024; // Doesn't matter +exampleBufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; + +uint32_t memTypeIndex; +vmaFindMemoryTypeIndexForBufferInfo(allocator, &exampleBufCreateInfo, &allocCreateInfo, &memTypeIndex); + +VmaPoolCreateInfo poolCreateInfo = {}; +poolCreateInfo.memoryTypeIndex = memTypeIndex; +// ... +\endcode + +When creating buffers/images allocated in that pool, provide following parameters: + +- `VkBufferCreateInfo`: Prefer to pass same parameters as above. + Otherwise you risk creating resources in a memory type that is not suitable for them, which may result in undefined behavior. + Using different `VK_BUFFER_USAGE_` flags may work, but you shouldn't create images in a pool intended for buffers + or the other way around. +- VmaAllocationCreateInfo: You don't need to pass same parameters. Fill only `pool` member. + Other members are ignored anyway. + + +\section custom_memory_pools_when_not_use When not to use custom pools + +Custom pools are commonly overused by VMA users. +While it may feel natural to keep some logical groups of resources separate in memory, +in most cases it does more harm than good. +Using custom pool shouldn't be your first choice. +Instead, please make all allocations from default pools first and only use custom pools +if you can prove and measure that it is beneficial in some way, +e.g. it results in lower memory usage, better performance, etc. + +Using custom pools has disadvantages: + +- Each pool has its own collection of `VkDeviceMemory` blocks. + Some of them may be partially or even completely empty. + Spreading allocations across multiple pools increases the amount of wasted (allocated but unbound) memory. +- You must manually choose specific memory type to be used by a custom pool (set as VmaPoolCreateInfo::memoryTypeIndex). + When using default pools, best memory type for each of your allocations can be selected automatically + using a carefully design algorithm that works across all kinds of GPUs. +- If an allocation from a custom pool at specific memory type fails, entire allocation operation returns failure. + When using default pools, VMA tries another compatible memory type. +- If you set VmaPoolCreateInfo::blockSize != 0, each memory block has the same size, + while default pools start from small blocks and only allocate next blocks larger and larger + up to the preferred block size. + +Many of the common concerns can be addressed in a different way than using custom pools: + +- If you want to keep your allocations of certain size (small versus large) or certain lifetime (transient versus long lived) + separate, you likely don't need to. + VMA uses a high quality allocation algorithm that manages memory well in various cases. + Please measure and check if using custom pools provides a benefit. +- If you want to keep your images and buffers separate, you don't need to. + VMA respects `bufferImageGranularity` limit automatically. +- If you want to keep your mapped and not mapped allocations separate, you don't need to. + VMA respects `nonCoherentAtomSize` limit automatically. + It also maps only those `VkDeviceMemory` blocks that need to map any allocation. + It even tries to keep mappable and non-mappable allocations in separate blocks to minimize the amount of mapped memory. +- If you want to choose a custom size for the default memory block, you can set it globally instead + using VmaAllocatorCreateInfo::preferredLargeHeapBlockSize. +- If you want to select specific memory type for your allocation, + you can set VmaAllocationCreateInfo::memoryTypeBits to `(1u << myMemoryTypeIndex)` instead. +- If you need to create a buffer with certain minimum alignment, you can still do it + using default pools with dedicated function vmaCreateBufferWithAlignment(). + + +\section linear_algorithm Linear allocation algorithm + +Each Vulkan memory block managed by this library has accompanying metadata that +keeps track of used and unused regions. By default, the metadata structure and +algorithm tries to find best place for new allocations among free regions to +optimize memory usage. This way you can allocate and free objects in any order. + +![Default allocation algorithm](../gfx/Linear_allocator_1_algo_default.png) + +Sometimes there is a need to use simpler, linear allocation algorithm. You can +create custom pool that uses such algorithm by adding flag +#VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT to VmaPoolCreateInfo::flags while creating +#VmaPool object. Then an alternative metadata management is used. It always +creates new allocations after last one and doesn't reuse free regions after +allocations freed in the middle. It results in better allocation performance and +less memory consumed by metadata. + +![Linear allocation algorithm](../gfx/Linear_allocator_2_algo_linear.png) + +With this one flag, you can create a custom pool that can be used in many ways: +free-at-once, stack, double stack, and ring buffer. See below for details. +You don't need to specify explicitly which of these options you are going to use - it is detected automatically. + +\subsection linear_algorithm_free_at_once Free-at-once + +In a pool that uses linear algorithm, you still need to free all the allocations +individually, e.g. by using vmaFreeMemory() or vmaDestroyBuffer(). You can free +them in any order. New allocations are always made after last one - free space +in the middle is not reused. However, when you release all the allocation and +the pool becomes empty, allocation starts from the beginning again. This way you +can use linear algorithm to speed up creation of allocations that you are going +to release all at once. + +![Free-at-once](../gfx/Linear_allocator_3_free_at_once.png) + +This mode is also available for pools created with VmaPoolCreateInfo::maxBlockCount +value that allows multiple memory blocks. + +\subsection linear_algorithm_stack Stack + +When you free an allocation that was created last, its space can be reused. +Thanks to this, if you always release allocations in the order opposite to their +creation (LIFO - Last In First Out), you can achieve behavior of a stack. + +![Stack](../gfx/Linear_allocator_4_stack.png) + +This mode is also available for pools created with VmaPoolCreateInfo::maxBlockCount +value that allows multiple memory blocks. + +\subsection linear_algorithm_double_stack Double stack + +The space reserved by a custom pool with linear algorithm may be used by two +stacks: + +- First, default one, growing up from offset 0. +- Second, "upper" one, growing down from the end towards lower offsets. + +To make allocation from the upper stack, add flag #VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT +to VmaAllocationCreateInfo::flags. + +![Double stack](../gfx/Linear_allocator_7_double_stack.png) + +Double stack is available only in pools with one memory block - +VmaPoolCreateInfo::maxBlockCount must be 1. Otherwise behavior is undefined. + +When the two stacks' ends meet so there is not enough space between them for a +new allocation, such allocation fails with usual +`VK_ERROR_OUT_OF_DEVICE_MEMORY` error. + +\subsection linear_algorithm_ring_buffer Ring buffer + +When you free some allocations from the beginning and there is not enough free space +for a new one at the end of a pool, allocator's "cursor" wraps around to the +beginning and starts allocation there. Thanks to this, if you always release +allocations in the same order as you created them (FIFO - First In First Out), +you can achieve behavior of a ring buffer / queue. + +![Ring buffer](../gfx/Linear_allocator_5_ring_buffer.png) + +Ring buffer is available only in pools with one memory block - +VmaPoolCreateInfo::maxBlockCount must be 1. Otherwise behavior is undefined. + +\note \ref defragmentation is not supported in custom pools created with #VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT. + + +\page defragmentation Defragmentation + +Interleaved allocations and deallocations of many objects of varying size can +cause fragmentation over time, which can lead to a situation where the library is unable +to find a continuous range of free memory for a new allocation despite there is +enough free space, just scattered across many small free ranges between existing +allocations. + +To mitigate this problem, you can use defragmentation feature. +It doesn't happen automatically though and needs your cooperation, +because VMA is a low level library that only allocates memory. +It cannot recreate buffers and images in a new place as it doesn't remember the contents of `VkBufferCreateInfo` / `VkImageCreateInfo` structures. +It cannot copy their contents as it doesn't record any commands to a command buffer. + +Example: + +\code +VmaDefragmentationInfo defragInfo = {}; +defragInfo.pool = myPool; +defragInfo.flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT; + +VmaDefragmentationContext defragCtx; +VkResult res = vmaBeginDefragmentation(allocator, &defragInfo, &defragCtx); +// Check res... + +for(;;) +{ + VmaDefragmentationPassMoveInfo pass; + res = vmaBeginDefragmentationPass(allocator, defragCtx, &pass); + if(res == VK_SUCCESS) + break; + else if(res != VK_INCOMPLETE) + // Handle error... + + for(uint32_t i = 0; i < pass.moveCount; ++i) + { + // Inspect pass.pMoves[i].srcAllocation, identify what buffer/image it represents. + VmaAllocationInfo allocInfo; + vmaGetAllocationInfo(allocator, pass.pMoves[i].srcAllocation, &allocInfo); + MyEngineResourceData* resData = (MyEngineResourceData*)allocInfo.pUserData; + + // Recreate and bind this buffer/image at: pass.pMoves[i].dstMemory, pass.pMoves[i].dstOffset. + VkImageCreateInfo imgCreateInfo = ... + VkImage newImg; + res = vkCreateImage(device, &imgCreateInfo, nullptr, &newImg); + // Check res... + res = vmaBindImageMemory(allocator, pass.pMoves[i].dstTmpAllocation, newImg); + // Check res... + + // Issue a vkCmdCopyBuffer/vkCmdCopyImage to copy its content to the new place. + vkCmdCopyImage(cmdBuf, resData->img, ..., newImg, ...); + } + + // Make sure the copy commands finished executing. + vkWaitForFences(...); + + // Destroy old buffers/images bound with pass.pMoves[i].srcAllocation. + for(uint32_t i = 0; i < pass.moveCount; ++i) + { + // ... + vkDestroyImage(device, resData->img, nullptr); + } + + // Update appropriate descriptors to point to the new places... + + res = vmaEndDefragmentationPass(allocator, defragCtx, &pass); + if(res == VK_SUCCESS) + break; + else if(res != VK_INCOMPLETE) + // Handle error... +} + +vmaEndDefragmentation(allocator, defragCtx, nullptr); +\endcode + +Although functions like vmaCreateBuffer(), vmaCreateImage(), vmaDestroyBuffer(), vmaDestroyImage() +create/destroy an allocation and a buffer/image at once, these are just a shortcut for +creating the resource, allocating memory, and binding them together. +Defragmentation works on memory allocations only. You must handle the rest manually. +Defragmentation is an iterative process that should repreat "passes" as long as related functions +return `VK_INCOMPLETE` not `VK_SUCCESS`. +In each pass: + +1. vmaBeginDefragmentationPass() function call: + - Calculates and returns the list of allocations to be moved in this pass. + Note this can be a time-consuming process. + - Reserves destination memory for them by creating temporary destination allocations + that you can query for their `VkDeviceMemory` + offset using vmaGetAllocationInfo(). +2. Inside the pass, **you should**: + - Inspect the returned list of allocations to be moved. + - Create new buffers/images and bind them at the returned destination temporary allocations. + - Copy data from source to destination resources if necessary. + - Destroy the source buffers/images, but NOT their allocations. +3. vmaEndDefragmentationPass() function call: + - Frees the source memory reserved for the allocations that are moved. + - Modifies source #VmaAllocation objects that are moved to point to the destination reserved memory. + - Frees `VkDeviceMemory` blocks that became empty. + +Unlike in previous iterations of the defragmentation API, there is no list of "movable" allocations passed as a parameter. +Defragmentation algorithm tries to move all suitable allocations. +You can, however, refuse to move some of them inside a defragmentation pass, by setting +`pass.pMoves[i].operation` to #VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE. +This is not recommended and may result in suboptimal packing of the allocations after defragmentation. +If you cannot ensure any allocation can be moved, it is better to keep movable allocations separate in a custom pool. + +Inside a pass, for each allocation that should be moved: + +- You should copy its data from the source to the destination place by calling e.g. `vkCmdCopyBuffer()`, `vkCmdCopyImage()`. + - You need to make sure these commands finished executing before destroying the source buffers/images and before calling vmaEndDefragmentationPass(). +- If a resource doesn't contain any meaningful data, e.g. it is a transient color attachment image to be cleared, + filled, and used temporarily in each rendering frame, you can just recreate this image + without copying its data. +- If the resource is in `HOST_VISIBLE` and `HOST_CACHED` memory, you can copy its data on the CPU + using `memcpy()`. +- If you cannot move the allocation, you can set `pass.pMoves[i].operation` to #VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE. + This will cancel the move. + - vmaEndDefragmentationPass() will then free the destination memory + not the source memory of the allocation, leaving it unchanged. +- If you decide the allocation is unimportant and can be destroyed instead of moved (e.g. it wasn't used for long time), + you can set `pass.pMoves[i].operation` to #VMA_DEFRAGMENTATION_MOVE_OPERATION_DESTROY. + - vmaEndDefragmentationPass() will then free both source and destination memory, and will destroy the source #VmaAllocation object. + +You can defragment a specific custom pool by setting VmaDefragmentationInfo::pool +(like in the example above) or all the default pools by setting this member to null. + +Defragmentation is always performed in each pool separately. +Allocations are never moved between different Vulkan memory types. +The size of the destination memory reserved for a moved allocation is the same as the original one. +Alignment of an allocation as it was determined using `vkGetBufferMemoryRequirements()` etc. is also respected after defragmentation. +Buffers/images should be recreated with the same `VkBufferCreateInfo` / `VkImageCreateInfo` parameters as the original ones. + +You can perform the defragmentation incrementally to limit the number of allocations and bytes to be moved +in each pass, e.g. to call it in sync with render frames and not to experience too big hitches. +See members: VmaDefragmentationInfo::maxBytesPerPass, VmaDefragmentationInfo::maxAllocationsPerPass. + +It is also safe to perform the defragmentation asynchronously to render frames and other Vulkan and VMA +usage, possibly from multiple threads, with the exception that allocations +returned in VmaDefragmentationPassMoveInfo::pMoves shouldn't be destroyed until the defragmentation pass is ended. + +Mapping is preserved on allocations that are moved during defragmentation. +Whether through #VMA_ALLOCATION_CREATE_MAPPED_BIT or vmaMapMemory(), the allocations +are mapped at their new place. Of course, pointer to the mapped data changes, so it needs to be queried +using VmaAllocationInfo::pMappedData. + +\note Defragmentation is not supported in custom pools created with #VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT. + + +\page statistics Statistics + +This library contains several functions that return information about its internal state, +especially the amount of memory allocated from Vulkan. + +\section statistics_numeric_statistics Numeric statistics + +If you need to obtain basic statistics about memory usage per heap, together with current budget, +you can call function vmaGetHeapBudgets() and inspect structure #VmaBudget. +This is useful to keep track of memory usage and stay within budget +(see also \ref staying_within_budget). +Example: + +\code +uint32_t heapIndex = ... + +VmaBudget budgets[VK_MAX_MEMORY_HEAPS]; +vmaGetHeapBudgets(allocator, budgets); + +printf("My heap currently has %u allocations taking %llu B,\n", + budgets[heapIndex].statistics.allocationCount, + budgets[heapIndex].statistics.allocationBytes); +printf("allocated out of %u Vulkan device memory blocks taking %llu B,\n", + budgets[heapIndex].statistics.blockCount, + budgets[heapIndex].statistics.blockBytes); +printf("Vulkan reports total usage %llu B with budget %llu B.\n", + budgets[heapIndex].usage, + budgets[heapIndex].budget); +\endcode + +You can query for more detailed statistics per memory heap, type, and totals, +including minimum and maximum allocation size and unused range size, +by calling function vmaCalculateStatistics() and inspecting structure #VmaTotalStatistics. +This function is slower though, as it has to traverse all the internal data structures, +so it should be used only for debugging purposes. + +You can query for statistics of a custom pool using function vmaGetPoolStatistics() +or vmaCalculatePoolStatistics(). + +You can query for information about a specific allocation using function vmaGetAllocationInfo(). +It fill structure #VmaAllocationInfo. + +\section statistics_json_dump JSON dump + +You can dump internal state of the allocator to a string in JSON format using function vmaBuildStatsString(). +The result is guaranteed to be correct JSON. +It uses ANSI encoding. +Any strings provided by user (see [Allocation names](@ref allocation_names)) +are copied as-is and properly escaped for JSON, so if they use UTF-8, ISO-8859-2 or any other encoding, +this JSON string can be treated as using this encoding. +It must be freed using function vmaFreeStatsString(). + +The format of this JSON string is not part of official documentation of the library, +but it will not change in backward-incompatible way without increasing library major version number +and appropriate mention in changelog. + +The JSON string contains all the data that can be obtained using vmaCalculateStatistics(). +It can also contain detailed map of allocated memory blocks and their regions - +free and occupied by allocations. +This allows e.g. to visualize the memory or assess fragmentation. + + +\page allocation_annotation Allocation names and user data + +\section allocation_user_data Allocation user data + +You can annotate allocations with your own information, e.g. for debugging purposes. +To do that, fill VmaAllocationCreateInfo::pUserData field when creating +an allocation. It is an opaque `void*` pointer. You can use it e.g. as a pointer, +some handle, index, key, ordinal number or any other value that would associate +the allocation with your custom metadata. +It is useful to identify appropriate data structures in your engine given #VmaAllocation, +e.g. when doing \ref defragmentation. + +\code +VkBufferCreateInfo bufCreateInfo = ... + +MyBufferMetadata* pMetadata = CreateBufferMetadata(); + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; +allocCreateInfo.pUserData = pMetadata; + +VkBuffer buffer; +VmaAllocation allocation; +vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buffer, &allocation, nullptr); +\endcode + +The pointer may be later retrieved as VmaAllocationInfo::pUserData: + +\code +VmaAllocationInfo allocInfo; +vmaGetAllocationInfo(allocator, allocation, &allocInfo); +MyBufferMetadata* pMetadata = (MyBufferMetadata*)allocInfo.pUserData; +\endcode + +It can also be changed using function vmaSetAllocationUserData(). + +Values of (non-zero) allocations' `pUserData` are printed in JSON report created by +vmaBuildStatsString() in hexadecimal form. + +\section allocation_names Allocation names + +An allocation can also carry a null-terminated string, giving a name to the allocation. +To set it, call vmaSetAllocationName(). +The library creates internal copy of the string, so the pointer you pass doesn't need +to be valid for whole lifetime of the allocation. You can free it after the call. + +\code +std::string imageName = "Texture: "; +imageName += fileName; +vmaSetAllocationName(allocator, allocation, imageName.c_str()); +\endcode + +The string can be later retrieved by inspecting VmaAllocationInfo::pName. +It is also printed in JSON report created by vmaBuildStatsString(). + +\note Setting string name to VMA allocation doesn't automatically set it to the Vulkan buffer or image created with it. +You must do it manually using an extension like VK_EXT_debug_utils, which is independent of this library. + + +\page virtual_allocator Virtual allocator + +As an extra feature, the core allocation algorithm of the library is exposed through a simple and convenient API of "virtual allocator". +It doesn't allocate any real GPU memory. It just keeps track of used and free regions of a "virtual block". +You can use it to allocate your own memory or other objects, even completely unrelated to Vulkan. +A common use case is sub-allocation of pieces of one large GPU buffer. + +\section virtual_allocator_creating_virtual_block Creating virtual block + +To use this functionality, there is no main "allocator" object. +You don't need to have #VmaAllocator object created. +All you need to do is to create a separate #VmaVirtualBlock object for each block of memory you want to be managed by the allocator: + +-# Fill in #VmaVirtualBlockCreateInfo structure. +-# Call vmaCreateVirtualBlock(). Get new #VmaVirtualBlock object. + +Example: + +\code +VmaVirtualBlockCreateInfo blockCreateInfo = {}; +blockCreateInfo.size = 1048576; // 1 MB + +VmaVirtualBlock block; +VkResult res = vmaCreateVirtualBlock(&blockCreateInfo, &block); +\endcode + +\section virtual_allocator_making_virtual_allocations Making virtual allocations + +#VmaVirtualBlock object contains internal data structure that keeps track of free and occupied regions +using the same code as the main Vulkan memory allocator. +Similarly to #VmaAllocation for standard GPU allocations, there is #VmaVirtualAllocation type +that represents an opaque handle to an allocation within the virtual block. + +In order to make such allocation: + +-# Fill in #VmaVirtualAllocationCreateInfo structure. +-# Call vmaVirtualAllocate(). Get new #VmaVirtualAllocation object that represents the allocation. + You can also receive `VkDeviceSize offset` that was assigned to the allocation. + +Example: + +\code +VmaVirtualAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.size = 4096; // 4 KB + +VmaVirtualAllocation alloc; +VkDeviceSize offset; +res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc, &offset); +if(res == VK_SUCCESS) +{ + // Use the 4 KB of your memory starting at offset. +} +else +{ + // Allocation failed - no space for it could be found. Handle this error! +} +\endcode + +\section virtual_allocator_deallocation Deallocation + +When no longer needed, an allocation can be freed by calling vmaVirtualFree(). +You can only pass to this function an allocation that was previously returned by vmaVirtualAllocate() +called for the same #VmaVirtualBlock. + +When whole block is no longer needed, the block object can be released by calling vmaDestroyVirtualBlock(). +All allocations must be freed before the block is destroyed, which is checked internally by an assert. +However, if you don't want to call vmaVirtualFree() for each allocation, you can use vmaClearVirtualBlock() to free them all at once - +a feature not available in normal Vulkan memory allocator. Example: + +\code +vmaVirtualFree(block, alloc); +vmaDestroyVirtualBlock(block); +\endcode + +\section virtual_allocator_allocation_parameters Allocation parameters + +You can attach a custom pointer to each allocation by using vmaSetVirtualAllocationUserData(). +Its default value is null. +It can be used to store any data that needs to be associated with that allocation - e.g. an index, a handle, or a pointer to some +larger data structure containing more information. Example: + +\code +struct CustomAllocData +{ + std::string m_AllocName; +}; +CustomAllocData* allocData = new CustomAllocData(); +allocData->m_AllocName = "My allocation 1"; +vmaSetVirtualAllocationUserData(block, alloc, allocData); +\endcode + +The pointer can later be fetched, along with allocation offset and size, by passing the allocation handle to function +vmaGetVirtualAllocationInfo() and inspecting returned structure #VmaVirtualAllocationInfo. +If you allocated a new object to be used as the custom pointer, don't forget to delete that object before freeing the allocation! +Example: + +\code +VmaVirtualAllocationInfo allocInfo; +vmaGetVirtualAllocationInfo(block, alloc, &allocInfo); +delete (CustomAllocData*)allocInfo.pUserData; + +vmaVirtualFree(block, alloc); +\endcode + +\section virtual_allocator_alignment_and_units Alignment and units + +It feels natural to express sizes and offsets in bytes. +If an offset of an allocation needs to be aligned to a multiply of some number (e.g. 4 bytes), you can fill optional member +VmaVirtualAllocationCreateInfo::alignment to request it. Example: + +\code +VmaVirtualAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.size = 4096; // 4 KB +allocCreateInfo.alignment = 4; // Returned offset must be a multiply of 4 B + +VmaVirtualAllocation alloc; +res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc, nullptr); +\endcode + +Alignments of different allocations made from one block may vary. +However, if all alignments and sizes are always multiply of some size e.g. 4 B or `sizeof(MyDataStruct)`, +you can express all sizes, alignments, and offsets in multiples of that size instead of individual bytes. +It might be more convenient, but you need to make sure to use this new unit consistently in all the places: + +- VmaVirtualBlockCreateInfo::size +- VmaVirtualAllocationCreateInfo::size and VmaVirtualAllocationCreateInfo::alignment +- Using offset returned by vmaVirtualAllocate() or in VmaVirtualAllocationInfo::offset + +\section virtual_allocator_statistics Statistics + +You can obtain statistics of a virtual block using vmaGetVirtualBlockStatistics() +(to get brief statistics that are fast to calculate) +or vmaCalculateVirtualBlockStatistics() (to get more detailed statistics, slower to calculate). +The functions fill structures #VmaStatistics, #VmaDetailedStatistics respectively - same as used by the normal Vulkan memory allocator. +Example: + +\code +VmaStatistics stats; +vmaGetVirtualBlockStatistics(block, &stats); +printf("My virtual block has %llu bytes used by %u virtual allocations\n", + stats.allocationBytes, stats.allocationCount); +\endcode + +You can also request a full list of allocations and free regions as a string in JSON format by calling +vmaBuildVirtualBlockStatsString(). +Returned string must be later freed using vmaFreeVirtualBlockStatsString(). +The format of this string differs from the one returned by the main Vulkan allocator, but it is similar. + +\section virtual_allocator_additional_considerations Additional considerations + +The "virtual allocator" functionality is implemented on a level of individual memory blocks. +Keeping track of a whole collection of blocks, allocating new ones when out of free space, +deleting empty ones, and deciding which one to try first for a new allocation must be implemented by the user. + +Alternative allocation algorithms are supported, just like in custom pools of the real GPU memory. +See enum #VmaVirtualBlockCreateFlagBits to learn how to specify them (e.g. #VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT). +You can find their description in chapter \ref custom_memory_pools. +Allocation strategies are also supported. +See enum #VmaVirtualAllocationCreateFlagBits to learn how to specify them (e.g. #VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT). + +Following features are supported only by the allocator of the real GPU memory and not by virtual allocations: +buffer-image granularity, `VMA_DEBUG_MARGIN`, `VMA_MIN_ALIGNMENT`. + + +\page debugging_memory_usage Debugging incorrect memory usage + +If you suspect a bug with memory usage, like usage of uninitialized memory or +memory being overwritten out of bounds of an allocation, +you can use debug features of this library to verify this. + +\section debugging_memory_usage_initialization Memory initialization + +If you experience a bug with incorrect and nondeterministic data in your program and you suspect uninitialized memory to be used, +you can enable automatic memory initialization to verify this. +To do it, define macro `VMA_DEBUG_INITIALIZE_ALLOCATIONS` to 1. + +\code +#define VMA_DEBUG_INITIALIZE_ALLOCATIONS 1 +#include "vk_mem_alloc.h" +\endcode + +It makes memory of new allocations initialized to bit pattern `0xDCDCDCDC`. +Before an allocation is destroyed, its memory is filled with bit pattern `0xEFEFEFEF`. +Memory is automatically mapped and unmapped if necessary. + +If you find these values while debugging your program, good chances are that you incorrectly +read Vulkan memory that is allocated but not initialized, or already freed, respectively. + +Memory initialization works only with memory types that are `HOST_VISIBLE` and with allocations that can be mapped. +It works also with dedicated allocations. + +\section debugging_memory_usage_margins Margins + +By default, allocations are laid out in memory blocks next to each other if possible +(considering required alignment, `bufferImageGranularity`, and `nonCoherentAtomSize`). + +![Allocations without margin](../gfx/Margins_1.png) + +Define macro `VMA_DEBUG_MARGIN` to some non-zero value (e.g. 16) to enforce specified +number of bytes as a margin after every allocation. + +\code +#define VMA_DEBUG_MARGIN 16 +#include "vk_mem_alloc.h" +\endcode + +![Allocations with margin](../gfx/Margins_2.png) + +If your bug goes away after enabling margins, it means it may be caused by memory +being overwritten outside of allocation boundaries. It is not 100% certain though. +Change in application behavior may also be caused by different order and distribution +of allocations across memory blocks after margins are applied. + +Margins work with all types of memory. + +Margin is applied only to allocations made out of memory blocks and not to dedicated +allocations, which have their own memory block of specific size. +It is thus not applied to allocations made using #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT flag +or those automatically decided to put into dedicated allocations, e.g. due to its +large size or recommended by VK_KHR_dedicated_allocation extension. + +Margins appear in [JSON dump](@ref statistics_json_dump) as part of free space. + +Note that enabling margins increases memory usage and fragmentation. + +Margins do not apply to \ref virtual_allocator. + +\section debugging_memory_usage_corruption_detection Corruption detection + +You can additionally define macro `VMA_DEBUG_DETECT_CORRUPTION` to 1 to enable validation +of contents of the margins. + +\code +#define VMA_DEBUG_MARGIN 16 +#define VMA_DEBUG_DETECT_CORRUPTION 1 +#include "vk_mem_alloc.h" +\endcode + +When this feature is enabled, number of bytes specified as `VMA_DEBUG_MARGIN` +(it must be multiply of 4) after every allocation is filled with a magic number. +This idea is also know as "canary". +Memory is automatically mapped and unmapped if necessary. + +This number is validated automatically when the allocation is destroyed. +If it is not equal to the expected value, `VMA_ASSERT()` is executed. +It clearly means that either CPU or GPU overwritten the memory outside of boundaries of the allocation, +which indicates a serious bug. + +You can also explicitly request checking margins of all allocations in all memory blocks +that belong to specified memory types by using function vmaCheckCorruption(), +or in memory blocks that belong to specified custom pool, by using function +vmaCheckPoolCorruption(). + +Margin validation (corruption detection) works only for memory types that are +`HOST_VISIBLE` and `HOST_COHERENT`. + + +\section debugging_memory_usage_leak_detection Leak detection features + +At allocation and allocator destruction time VMA checks for unfreed and unmapped blocks using +`VMA_ASSERT_LEAK()`. This macro defaults to an assertion, triggering a typically fatal error in Debug +builds, and doing nothing in Release builds. You can provide your own definition of `VMA_ASSERT_LEAK()` +to change this behavior. + +At memory block destruction time VMA lists out all unfreed allocations using the `VMA_LEAK_LOG_FORMAT()` +macro, which defaults to `VMA_DEBUG_LOG_FORMAT`, which in turn defaults to a no-op. +If you're having trouble with leaks - for example, the aforementioned assertion triggers, but you don't +quite know \em why -, overriding this macro to print out the the leaking blocks, combined with assigning +individual names to allocations using vmaSetAllocationName(), can greatly aid in fixing them. + +\page other_api_interop Interop with other graphics APIs + +VMA provides some features that help with interoperability with other graphics APIs, e.g. OpenGL. + +\section opengl_interop_exporting_memory Exporting memory + +If you want to attach `VkExportMemoryAllocateInfoKHR` or other structure to `pNext` chain of memory allocations made by the library: + +You can create \ref custom_memory_pools for such allocations. +Define and fill in your `VkExportMemoryAllocateInfoKHR` structure and attach it to VmaPoolCreateInfo::pMemoryAllocateNext +while creating the custom pool. +Please note that the structure must remain alive and unchanged for the whole lifetime of the #VmaPool, +not only while creating it, as no copy of the structure is made, +but its original pointer is used for each allocation instead. + +If you want to export all memory allocated by VMA from certain memory types, +also dedicated allocations or other allocations made from default pools, +an alternative solution is to fill in VmaAllocatorCreateInfo::pTypeExternalMemoryHandleTypes. +It should point to an array with `VkExternalMemoryHandleTypeFlagsKHR` to be automatically passed by the library +through `VkExportMemoryAllocateInfoKHR` on each allocation made from a specific memory type. +Please note that new versions of the library also support dedicated allocations created in custom pools. + +You should not mix these two methods in a way that allows to apply both to the same memory type. +Otherwise, `VkExportMemoryAllocateInfoKHR` structure would be attached twice to the `pNext` chain of `VkMemoryAllocateInfo`. + + +\section opengl_interop_custom_alignment Custom alignment + +Buffers or images exported to a different API like OpenGL may require a different alignment, +higher than the one used by the library automatically, queried from functions like `vkGetBufferMemoryRequirements`. +To impose such alignment: + +You can create \ref custom_memory_pools for such allocations. +Set VmaPoolCreateInfo::minAllocationAlignment member to the minimum alignment required for each allocation +to be made out of this pool. +The alignment actually used will be the maximum of this member and the alignment returned for the specific buffer or image +from a function like `vkGetBufferMemoryRequirements`, which is called by VMA automatically. + +If you want to create a buffer with a specific minimum alignment out of default pools, +use special function vmaCreateBufferWithAlignment(), which takes additional parameter `minAlignment`. + +Note the problem of alignment affects only resources placed inside bigger `VkDeviceMemory` blocks and not dedicated +allocations, as these, by definition, always have alignment = 0 because the resource is bound to the beginning of its dedicated block. +You can ensure that an allocation is created as dedicated by using #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. +Contrary to Direct3D 12, Vulkan doesn't have a concept of alignment of the entire memory block passed on its allocation. + +\section opengl_interop_extended_allocation_information Extended allocation information + +If you want to rely on VMA to allocate your buffers and images inside larger memory blocks, +but you need to know the size of the entire block and whether the allocation was made +with its own dedicated memory, use function vmaGetAllocationInfo2() to retrieve +extended allocation information in structure #VmaAllocationInfo2. + + + +\page usage_patterns Recommended usage patterns + +Vulkan gives great flexibility in memory allocation. +This chapter shows the most common patterns. + +See also slides from talk: +[Sawicki, Adam. Advanced Graphics Techniques Tutorial: Memory management in Vulkan and DX12. Game Developers Conference, 2018](https://www.gdcvault.com/play/1025458/Advanced-Graphics-Techniques-Tutorial-New) + + +\section usage_patterns_gpu_only GPU-only resource + +When: +Any resources that you frequently write and read on GPU, +e.g. images used as color attachments (aka "render targets"), depth-stencil attachments, +images/buffers used as storage image/buffer (aka "Unordered Access View (UAV)"). + +What to do: +Let the library select the optimal memory type, which will likely have `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`. + +\code +VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; +imgCreateInfo.imageType = VK_IMAGE_TYPE_2D; +imgCreateInfo.extent.width = 3840; +imgCreateInfo.extent.height = 2160; +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; + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; +allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; +allocCreateInfo.priority = 1.0f; + +VkImage img; +VmaAllocation alloc; +vmaCreateImage(allocator, &imgCreateInfo, &allocCreateInfo, &img, &alloc, nullptr); +\endcode + +Also consider: +Consider creating them as dedicated allocations using #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT, +especially if they are large or if you plan to destroy and recreate them with different sizes +e.g. when display resolution changes. +Prefer to create such resources first and all other GPU resources (like textures and vertex buffers) later. +When VK_EXT_memory_priority extension is enabled, it is also worth setting high priority to such allocation +to decrease chances to be evicted to system memory by the operating system. + +\section usage_patterns_staging_copy_upload Staging copy for upload + +When: +A "staging" buffer than you want to map and fill from CPU code, then use as a source of transfer +to some GPU resource. + +What to do: +Use flag #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT. +Let the library select the optimal memory type, which will always have `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT`. + +\code +VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; +bufCreateInfo.size = 65536; +bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; +allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT; + +VkBuffer buf; +VmaAllocation alloc; +VmaAllocationInfo allocInfo; +vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + +... + +memcpy(allocInfo.pMappedData, myData, myDataSize); +\endcode + +Also consider: +You can map the allocation using vmaMapMemory() or you can create it as persistenly mapped +using #VMA_ALLOCATION_CREATE_MAPPED_BIT, as in the example above. + + +\section usage_patterns_readback Readback + +When: +Buffers for data written by or transferred from the GPU that you want to read back on the CPU, +e.g. results of some computations. + +What to do: +Use flag #VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT. +Let the library select the optimal memory type, which will always have `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` +and `VK_MEMORY_PROPERTY_HOST_CACHED_BIT`. + +\code +VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; +bufCreateInfo.size = 65536; +bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT; + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; +allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT; + +VkBuffer buf; +VmaAllocation alloc; +VmaAllocationInfo allocInfo; +vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); + +... + +const float* downloadedData = (const float*)allocInfo.pMappedData; +\endcode + + +\section usage_patterns_advanced_data_uploading Advanced data uploading + +For resources that you frequently write on CPU via mapped pointer and +frequently read on GPU e.g. as a uniform buffer (also called "dynamic"), multiple options are possible: + +-# Easiest solution is to have one copy of the resource in `HOST_VISIBLE` memory, + even if it means system RAM (not `DEVICE_LOCAL`) on systems with a discrete graphics card, + and make the device reach out to that resource directly. + - Reads performed by the device will then go through PCI Express bus. + The performance of this access may be limited, but it may be fine depending on the size + of this resource (whether it is small enough to quickly end up in GPU cache) and the sparsity + of access. +-# On systems with unified memory (e.g. AMD APU or Intel integrated graphics, mobile chips), + a memory type may be available that is both `HOST_VISIBLE` (available for mapping) and `DEVICE_LOCAL` + (fast to access from the GPU). Then, it is likely the best choice for such type of resource. +-# Systems with a discrete graphics card and separate video memory may or may not expose + a memory type that is both `HOST_VISIBLE` and `DEVICE_LOCAL`, also known as Base Address Register (BAR). + If they do, it represents a piece of VRAM (or entire VRAM, if ReBAR is enabled in the motherboard BIOS) + that is available to CPU for mapping. + - Writes performed by the host to that memory go through PCI Express bus. + The performance of these writes may be limited, but it may be fine, especially on PCIe 4.0, + as long as rules of using uncached and write-combined memory are followed - only sequential writes and no reads. +-# Finally, you may need or prefer to create a separate copy of the resource in `DEVICE_LOCAL` memory, + a separate "staging" copy in `HOST_VISIBLE` memory and perform an explicit transfer command between them. + +Thankfully, VMA offers an aid to create and use such resources in the the way optimal +for the current Vulkan device. To help the library make the best choice, +use flag #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT together with +#VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT. +It will then prefer a memory type that is both `DEVICE_LOCAL` and `HOST_VISIBLE` (integrated memory or BAR), +but if no such memory type is available or allocation from it fails +(PC graphics cards have only 256 MB of BAR by default, unless ReBAR is supported and enabled in BIOS), +it will fall back to `DEVICE_LOCAL` memory for fast GPU access. +It is then up to you to detect that the allocation ended up in a memory type that is not `HOST_VISIBLE`, +so you need to create another "staging" allocation and perform explicit transfers. + +\code +VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; +bufCreateInfo.size = 65536; +bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; +allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | + VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT; + +VkBuffer buf; +VmaAllocation alloc; +VmaAllocationInfo allocInfo; +VkResult result = vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, &allocInfo); +// Check result... + +VkMemoryPropertyFlags memPropFlags; +vmaGetAllocationMemoryProperties(allocator, alloc, &memPropFlags); + +if(memPropFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) +{ + // Allocation ended up in a mappable memory and is already mapped - write to it directly. + + // [Executed in runtime]: + memcpy(allocInfo.pMappedData, myData, myDataSize); + result = vmaFlushAllocation(allocator, alloc, 0, VK_WHOLE_SIZE); + // Check result... + + VkBufferMemoryBarrier bufMemBarrier = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER }; + bufMemBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; + bufMemBarrier.dstAccessMask = VK_ACCESS_UNIFORM_READ_BIT; + bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier.buffer = buf; + bufMemBarrier.offset = 0; + bufMemBarrier.size = VK_WHOLE_SIZE; + + vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, + 0, 0, nullptr, 1, &bufMemBarrier, 0, nullptr); +} +else +{ + // Allocation ended up in a non-mappable memory - a transfer using a staging buffer is required. + VkBufferCreateInfo stagingBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; + stagingBufCreateInfo.size = 65536; + stagingBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + + VmaAllocationCreateInfo stagingAllocCreateInfo = {}; + stagingAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; + stagingAllocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT; + + VkBuffer stagingBuf; + VmaAllocation stagingAlloc; + VmaAllocationInfo stagingAllocInfo; + result = vmaCreateBuffer(allocator, &stagingBufCreateInfo, &stagingAllocCreateInfo, + &stagingBuf, &stagingAlloc, &stagingAllocInfo); + // Check result... + + // [Executed in runtime]: + memcpy(stagingAllocInfo.pMappedData, myData, myDataSize); + result = vmaFlushAllocation(allocator, stagingAlloc, 0, VK_WHOLE_SIZE); + // Check result... + + VkBufferMemoryBarrier bufMemBarrier = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER }; + bufMemBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; + bufMemBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier.buffer = stagingBuf; + bufMemBarrier.offset = 0; + bufMemBarrier.size = VK_WHOLE_SIZE; + + vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, nullptr, 1, &bufMemBarrier, 0, nullptr); + + VkBufferCopy bufCopy = { + 0, // srcOffset + 0, // dstOffset, + myDataSize, // size + }; + + vkCmdCopyBuffer(cmdBuf, stagingBuf, buf, 1, &bufCopy); + + VkBufferMemoryBarrier bufMemBarrier2 = { VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER }; + bufMemBarrier2.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + bufMemBarrier2.dstAccessMask = VK_ACCESS_UNIFORM_READ_BIT; // We created a uniform buffer + bufMemBarrier2.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier2.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier2.buffer = buf; + bufMemBarrier2.offset = 0; + bufMemBarrier2.size = VK_WHOLE_SIZE; + + vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, + 0, 0, nullptr, 1, &bufMemBarrier2, 0, nullptr); +} +\endcode + +\section usage_patterns_other_use_cases Other use cases + +Here are some other, less obvious use cases and their recommended settings: + +- An image that is used only as transfer source and destination, but it should stay on the device, + as it is used to temporarily store a copy of some texture, e.g. from the current to the next frame, + for temporal antialiasing or other temporal effects. + - Use `VkImageCreateInfo::usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT` + - Use VmaAllocationCreateInfo::usage = #VMA_MEMORY_USAGE_AUTO +- An image that is used only as transfer source and destination, but it should be placed + in the system RAM despite it doesn't need to be mapped, because it serves as a "swap" copy to evict + least recently used textures from VRAM. + - Use `VkImageCreateInfo::usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT` + - Use VmaAllocationCreateInfo::usage = #VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + as VMA needs a hint here to differentiate from the previous case. +- A buffer that you want to map and write from the CPU, directly read from the GPU + (e.g. as a uniform or vertex buffer), but you have a clear preference to place it in device or + host memory due to its large size. + - Use `VkBufferCreateInfo::usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT` + - Use VmaAllocationCreateInfo::usage = #VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE or #VMA_MEMORY_USAGE_AUTO_PREFER_HOST + - Use VmaAllocationCreateInfo::flags = #VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT + + +\page configuration Configuration + +Please check "CONFIGURATION SECTION" in the code to find macros that you can define +before each include of this file or change directly in this file to provide +your own implementation of basic facilities like assert, `min()` and `max()` functions, +mutex, atomic etc. + +For example, define `VMA_ASSERT(expr)` before including the library to provide +custom implementation of the assertion, compatible with your project. +By default it is defined to standard C `assert(expr)` in `_DEBUG` configuration +and empty otherwise. + +Similarly, you can define `VMA_LEAK_LOG_FORMAT` macro to enable printing of leaked (unfreed) allocations, +including their names and other parameters. Example: + +\code +#define VMA_LEAK_LOG_FORMAT(format, ...) do { \ + printf((format), __VA_ARGS__); \ + printf("\n"); \ + } while(false) +\endcode + +\section config_Vulkan_functions Pointers to Vulkan functions + +There are multiple ways to import pointers to Vulkan functions in the library. +In the simplest case you don't need to do anything. +If the compilation or linking of your program or the initialization of the #VmaAllocator +doesn't work for you, you can try to reconfigure it. + +First, the allocator tries to fetch pointers to Vulkan functions linked statically, +like this: + +\code +m_VulkanFunctions.vkAllocateMemory = (PFN_vkAllocateMemory)vkAllocateMemory; +\endcode + +If you want to disable this feature, set configuration macro: `#define VMA_STATIC_VULKAN_FUNCTIONS 0`. + +Second, you can provide the pointers yourself by setting member VmaAllocatorCreateInfo::pVulkanFunctions. +You can fetch them e.g. using functions `vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` or +by using a helper library like [volk](https://github.com/zeux/volk). + +Third, VMA tries to fetch remaining pointers that are still null by calling +`vkGetInstanceProcAddr` and `vkGetDeviceProcAddr` on its own. +You need to only fill in VmaVulkanFunctions::vkGetInstanceProcAddr and VmaVulkanFunctions::vkGetDeviceProcAddr. +Other pointers will be fetched automatically. +If you want to disable this feature, set configuration macro: `#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0`. + +Finally, all the function pointers required by the library (considering selected +Vulkan version and enabled extensions) are checked with `VMA_ASSERT` if they are not null. + + +\section custom_memory_allocator Custom host memory allocator + +If you use custom allocator for CPU memory rather than default operator `new` +and `delete` from C++, you can make this library using your allocator as well +by filling optional member VmaAllocatorCreateInfo::pAllocationCallbacks. These +functions will be passed to Vulkan, as well as used by the library itself to +make any CPU-side allocations. + +\section allocation_callbacks Device memory allocation callbacks + +The library makes calls to `vkAllocateMemory()` and `vkFreeMemory()` internally. +You can setup callbacks to be informed about these calls, e.g. for the purpose +of gathering some statistics. To do it, fill optional member +VmaAllocatorCreateInfo::pDeviceMemoryCallbacks. + +\section heap_memory_limit Device heap memory limit + +When device memory of certain heap runs out of free space, new allocations may +fail (returning error code) or they may succeed, silently pushing some existing_ +memory blocks from GPU VRAM to system RAM (which degrades performance). This +behavior is implementation-dependent - it depends on GPU vendor and graphics +driver. + +On AMD cards it can be controlled while creating Vulkan device object by using +VK_AMD_memory_overallocation_behavior extension, if available. + +Alternatively, if you want to test how your program behaves with limited amount of Vulkan device +memory available without switching your graphics card to one that really has +smaller VRAM, you can use a feature of this library intended for this purpose. +To do it, fill optional member VmaAllocatorCreateInfo::pHeapSizeLimit. + + + +\page vk_khr_dedicated_allocation VK_KHR_dedicated_allocation + +VK_KHR_dedicated_allocation is a Vulkan extension which can be used to improve +performance on some GPUs. It augments Vulkan API with possibility to query +driver whether it prefers particular buffer or image to have its own, dedicated +allocation (separate `VkDeviceMemory` block) for better efficiency - to be able +to do some internal optimizations. The extension is supported by this library. +It will be used automatically when enabled. + +It has been promoted to core Vulkan 1.1, so if you use eligible Vulkan version +and inform VMA about it by setting VmaAllocatorCreateInfo::vulkanApiVersion, +you are all set. + +Otherwise, if you want to use it as an extension: + +1 . When creating Vulkan device, check if following 2 device extensions are +supported (call `vkEnumerateDeviceExtensionProperties()`). +If yes, enable them (fill `VkDeviceCreateInfo::ppEnabledExtensionNames`). + +- VK_KHR_get_memory_requirements2 +- VK_KHR_dedicated_allocation + +If you enabled these extensions: + +2 . Use #VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT flag when creating +your #VmaAllocator to inform the library that you enabled required extensions +and you want the library to use them. + +\code +allocatorInfo.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT; + +vmaCreateAllocator(&allocatorInfo, &allocator); +\endcode + +That is all. The extension will be automatically used whenever you create a +buffer using vmaCreateBuffer() or image using vmaCreateImage(). + +When using the extension together with Vulkan Validation Layer, you will receive +warnings like this: + +_vkBindBufferMemory(): Binding memory to buffer 0x33 but vkGetBufferMemoryRequirements() has not been called on that buffer._ + +It is OK, you should just ignore it. It happens because you use function +`vkGetBufferMemoryRequirements2KHR()` instead of standard +`vkGetBufferMemoryRequirements()`, while the validation layer seems to be +unaware of it. + +To learn more about this extension, see: + +- [VK_KHR_dedicated_allocation in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap50.html#VK_KHR_dedicated_allocation) +- [VK_KHR_dedicated_allocation unofficial manual](http://asawicki.info/articles/VK_KHR_dedicated_allocation.php5) + + + +\page vk_ext_memory_priority VK_EXT_memory_priority + +VK_EXT_memory_priority is a device extension that allows to pass additional "priority" +value to Vulkan memory allocations that the implementation may use prefer certain +buffers and images that are critical for performance to stay in device-local memory +in cases when the memory is over-subscribed, while some others may be moved to the system memory. + +VMA offers convenient usage of this extension. +If you enable it, you can pass "priority" parameter when creating allocations or custom pools +and the library automatically passes the value to Vulkan using this extension. + +If you want to use this extension in connection with VMA, follow these steps: + +\section vk_ext_memory_priority_initialization Initialization + +1) Call `vkEnumerateDeviceExtensionProperties` for the physical device. +Check if the extension is supported - if returned array of `VkExtensionProperties` contains "VK_EXT_memory_priority". + +2) Call `vkGetPhysicalDeviceFeatures2` for the physical device instead of old `vkGetPhysicalDeviceFeatures`. +Attach additional structure `VkPhysicalDeviceMemoryPriorityFeaturesEXT` to `VkPhysicalDeviceFeatures2::pNext` to be returned. +Check if the device feature is really supported - check if `VkPhysicalDeviceMemoryPriorityFeaturesEXT::memoryPriority` is true. + +3) While creating device with `vkCreateDevice`, enable this extension - add "VK_EXT_memory_priority" +to the list passed as `VkDeviceCreateInfo::ppEnabledExtensionNames`. + +4) While creating the device, also don't set `VkDeviceCreateInfo::pEnabledFeatures`. +Fill in `VkPhysicalDeviceFeatures2` structure instead and pass it as `VkDeviceCreateInfo::pNext`. +Enable this device feature - attach additional structure `VkPhysicalDeviceMemoryPriorityFeaturesEXT` to +`VkPhysicalDeviceFeatures2::pNext` chain and set its member `memoryPriority` to `VK_TRUE`. + +5) While creating #VmaAllocator with vmaCreateAllocator() inform VMA that you +have enabled this extension and feature - add #VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT +to VmaAllocatorCreateInfo::flags. + +\section vk_ext_memory_priority_usage Usage + +When using this extension, you should initialize following member: + +- VmaAllocationCreateInfo::priority when creating a dedicated allocation with #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. +- VmaPoolCreateInfo::priority when creating a custom pool. + +It should be a floating-point value between `0.0f` and `1.0f`, where recommended default is `0.5f`. +Memory allocated with higher value can be treated by the Vulkan implementation as higher priority +and so it can have lower chances of being pushed out to system memory, experiencing degraded performance. + +It might be a good idea to create performance-critical resources like color-attachment or depth-stencil images +as dedicated and set high priority to them. For example: + +\code +VkImageCreateInfo imgCreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; +imgCreateInfo.imageType = VK_IMAGE_TYPE_2D; +imgCreateInfo.extent.width = 3840; +imgCreateInfo.extent.height = 2160; +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; + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; +allocCreateInfo.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; +allocCreateInfo.priority = 1.0f; + +VkImage img; +VmaAllocation alloc; +vmaCreateImage(allocator, &imgCreateInfo, &allocCreateInfo, &img, &alloc, nullptr); +\endcode + +`priority` member is ignored in the following situations: + +- Allocations created in custom pools: They inherit the priority, along with all other allocation parameters + from the parameters passed in #VmaPoolCreateInfo when the pool was created. +- Allocations created in default pools: They inherit the priority from the parameters + VMA used when creating default pools, which means `priority == 0.5f`. + + +\page vk_amd_device_coherent_memory VK_AMD_device_coherent_memory + +VK_AMD_device_coherent_memory is a device extension that enables access to +additional memory types with `VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD` and +`VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD` flag. It is useful mostly for +allocation of buffers intended for writing "breadcrumb markers" in between passes +or draw calls, which in turn are useful for debugging GPU crash/hang/TDR cases. + +When the extension is available but has not been enabled, Vulkan physical device +still exposes those memory types, but their usage is forbidden. VMA automatically +takes care of that - it returns `VK_ERROR_FEATURE_NOT_PRESENT` when an attempt +to allocate memory of such type is made. + +If you want to use this extension in connection with VMA, follow these steps: + +\section vk_amd_device_coherent_memory_initialization Initialization + +1) Call `vkEnumerateDeviceExtensionProperties` for the physical device. +Check if the extension is supported - if returned array of `VkExtensionProperties` contains "VK_AMD_device_coherent_memory". + +2) Call `vkGetPhysicalDeviceFeatures2` for the physical device instead of old `vkGetPhysicalDeviceFeatures`. +Attach additional structure `VkPhysicalDeviceCoherentMemoryFeaturesAMD` to `VkPhysicalDeviceFeatures2::pNext` to be returned. +Check if the device feature is really supported - check if `VkPhysicalDeviceCoherentMemoryFeaturesAMD::deviceCoherentMemory` is true. + +3) While creating device with `vkCreateDevice`, enable this extension - add "VK_AMD_device_coherent_memory" +to the list passed as `VkDeviceCreateInfo::ppEnabledExtensionNames`. + +4) While creating the device, also don't set `VkDeviceCreateInfo::pEnabledFeatures`. +Fill in `VkPhysicalDeviceFeatures2` structure instead and pass it as `VkDeviceCreateInfo::pNext`. +Enable this device feature - attach additional structure `VkPhysicalDeviceCoherentMemoryFeaturesAMD` to +`VkPhysicalDeviceFeatures2::pNext` and set its member `deviceCoherentMemory` to `VK_TRUE`. + +5) While creating #VmaAllocator with vmaCreateAllocator() inform VMA that you +have enabled this extension and feature - add #VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT +to VmaAllocatorCreateInfo::flags. + +\section vk_amd_device_coherent_memory_usage Usage + +After following steps described above, you can create VMA allocations and custom pools +out of the special `DEVICE_COHERENT` and `DEVICE_UNCACHED` memory types on eligible +devices. There are multiple ways to do it, for example: + +- You can request or prefer to allocate out of such memory types by adding + `VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD` to VmaAllocationCreateInfo::requiredFlags + or VmaAllocationCreateInfo::preferredFlags. Those flags can be freely mixed with + other ways of \ref choosing_memory_type, like setting VmaAllocationCreateInfo::usage. +- If you manually found memory type index to use for this purpose, force allocation + from this specific index by setting VmaAllocationCreateInfo::memoryTypeBits `= 1u << index`. + +\section vk_amd_device_coherent_memory_more_information More information + +To learn more about this extension, see [VK_AMD_device_coherent_memory in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_AMD_device_coherent_memory.html) + +Example use of this extension can be found in the code of the sample and test suite +accompanying this library. + + +\page vk_khr_external_memory_win32 VK_KHR_external_memory_win32 + +On Windows, the VK_KHR_external_memory_win32 device extension allows exporting a Win32 `HANDLE` +of a `VkDeviceMemory` block, to be able to reference the memory on other Vulkan logical devices or instances, +in multiple processes, and/or in multiple APIs. +VMA offers support for it. + +\section vk_khr_external_memory_win32_initialization Initialization + +1) Make sure the extension is defined in the code by including following header before including VMA: + +\code +#include +\endcode + +2) Check if "VK_KHR_external_memory_win32" is available among device extensions. +Enable it when creating the `VkDevice` object. + +3) Enable the usage of this extension in VMA by setting flag #VMA_ALLOCATOR_CREATE_KHR_EXTERNAL_MEMORY_WIN32_BIT +when calling vmaCreateAllocator(). + +4) Make sure that VMA has access to the `vkGetMemoryWin32HandleKHR` function by either enabling `VMA_DYNAMIC_VULKAN_FUNCTIONS` macro +or setting VmaVulkanFunctions::vkGetMemoryWin32HandleKHR explicitly. +For more information, see \ref quick_start_initialization_importing_vulkan_functions. + +\section vk_khr_external_memory_win32_preparations Preparations + +You can find example usage among tests, in file "Tests.cpp", function `TestWin32Handles()`. + +To use the extenion, buffers need to be created with `VkExternalMemoryBufferCreateInfoKHR` attached to their `pNext` chain, +and memory allocations need to be made with `VkExportMemoryAllocateInfoKHR` attached to their `pNext` chain. +To make use of them, you need to use \ref custom_memory_pools. Example: + +\code +// Define an example buffer and allocation parameters. +VkExternalMemoryBufferCreateInfoKHR externalMemBufCreateInfo = { + VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO_KHR, + nullptr, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT +}; +VkBufferCreateInfo exampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; +exampleBufCreateInfo.size = 0x10000; // Doesn't matter here. +exampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; +exampleBufCreateInfo.pNext = &externalMemBufCreateInfo; + +VmaAllocationCreateInfo exampleAllocCreateInfo = {}; +exampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; + +// Find memory type index to use for the custom pool. +uint32_t memTypeIndex; +VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_Allocator, + &exampleBufCreateInfo, &exampleAllocCreateInfo, &memTypeIndex); +// Check res... + +// Create a custom pool. +constexpr static VkExportMemoryAllocateInfoKHR exportMemAllocInfo = { + VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO_KHR, + nullptr, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT +}; +VmaPoolCreateInfo poolCreateInfo = {}; +poolCreateInfo.memoryTypeIndex = memTypeIndex; +poolCreateInfo.pMemoryAllocateNext = (void*)&exportMemAllocInfo; + +VmaPool pool; +res = vmaCreatePool(g_Allocator, &poolCreateInfo, &pool); +// Check res... + +// YOUR OTHER CODE COMES HERE.... + +// At the end, don't forget to destroy it! +vmaDestroyPool(g_Allocator, pool); +\endcode + +Note that the structure passed as VmaPoolCreateInfo::pMemoryAllocateNext must remain alive and unchanged +for the whole lifetime of the custom pool, because it will be used when the pool allocates a new device memory block. +No copy is made internally. This is why variable `exportMemAllocInfo` is defined as `static`. + +\section vk_khr_external_memory_win32_memory_allocation Memory allocation + +Finally, you can create a buffer with an allocation out of the custom pool. +The buffer should use same flags as the sample buffer used to find the memory type. +It should also specify `VkExternalMemoryBufferCreateInfoKHR` in its `pNext` chain. + +\code +VkExternalMemoryBufferCreateInfoKHR externalMemBufCreateInfo = { + VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO_KHR, + nullptr, + VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT +}; +VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO }; +bufCreateInfo.size = // Your desired buffer size. +bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; +bufCreateInfo.pNext = &externalMemBufCreateInfo; + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.pool = pool; // It is enough to set this one member. + +VkBuffer buf; +VmaAllocation alloc; +res = vmaCreateBuffer(g_Allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, nullptr); +// Check res... + +// YOUR OTHER CODE COMES HERE.... + +// At the end, don't forget to destroy it! +vmaDestroyBuffer(g_Allocator, buf, alloc); +\endcode + +If you need each allocation to have its own device memory block and start at offset 0, you can still do +by using #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT flag. It works also with custom pools. + +\section vk_khr_external_memory_win32_exporting_win32_handle Exporting Win32 handle + +After the allocation is created, you can acquire a Win32 `HANDLE` to the `VkDeviceMemory` block it belongs to. +VMA function vmaGetMemoryWin32Handle() is a replacement of the Vulkan function `vkGetMemoryWin32HandleKHR`. + +\code +HANDLE handle; +res = vmaGetMemoryWin32Handle(g_Allocator, alloc, nullptr, &handle); +// Check res... + +// YOUR OTHER CODE COMES HERE.... + +// At the end, you must close the handle. +CloseHandle(handle); +\endcode + +Documentation of the VK_KHR_external_memory_win32 extension states that: + +> If handleType is defined as an NT handle, vkGetMemoryWin32HandleKHR must be called no more than once for each valid unique combination of memory and handleType. + +This is ensured automatically inside VMA. +The library fetches the handle on first use, remembers it internally, and closes it when the memory block or dedicated allocation is destroyed. +Every time you call vmaGetMemoryWin32Handle(), VMA calls `DuplicateHandle` and returns a new handle that you need to close. + +For further information, please check documentation of the vmaGetMemoryWin32Handle() function. + + +\page enabling_buffer_device_address Enabling buffer device address + +Device extension VK_KHR_buffer_device_address +allow to fetch raw GPU pointer to a buffer and pass it for usage in a shader code. +It has been promoted to core Vulkan 1.2. + +If you want to use this feature in connection with VMA, follow these steps: + +\section enabling_buffer_device_address_initialization Initialization + +1) (For Vulkan version < 1.2) Call `vkEnumerateDeviceExtensionProperties` for the physical device. +Check if the extension is supported - if returned array of `VkExtensionProperties` contains +"VK_KHR_buffer_device_address". + +2) Call `vkGetPhysicalDeviceFeatures2` for the physical device instead of old `vkGetPhysicalDeviceFeatures`. +Attach additional structure `VkPhysicalDeviceBufferDeviceAddressFeatures*` to `VkPhysicalDeviceFeatures2::pNext` to be returned. +Check if the device feature is really supported - check if `VkPhysicalDeviceBufferDeviceAddressFeatures::bufferDeviceAddress` is true. + +3) (For Vulkan version < 1.2) While creating device with `vkCreateDevice`, enable this extension - add +"VK_KHR_buffer_device_address" to the list passed as `VkDeviceCreateInfo::ppEnabledExtensionNames`. + +4) While creating the device, also don't set `VkDeviceCreateInfo::pEnabledFeatures`. +Fill in `VkPhysicalDeviceFeatures2` structure instead and pass it as `VkDeviceCreateInfo::pNext`. +Enable this device feature - attach additional structure `VkPhysicalDeviceBufferDeviceAddressFeatures*` to +`VkPhysicalDeviceFeatures2::pNext` and set its member `bufferDeviceAddress` to `VK_TRUE`. + +5) While creating #VmaAllocator with vmaCreateAllocator() inform VMA that you +have enabled this feature - add #VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT +to VmaAllocatorCreateInfo::flags. + +\section enabling_buffer_device_address_usage Usage + +After following steps described above, you can create buffers with `VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT*` using VMA. +The library automatically adds `VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT*` to +allocated memory blocks wherever it might be needed. + +Please note that the library supports only `VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT*`. +The second part of this functionality related to "capture and replay" is not supported, +as it is intended for usage in debugging tools like RenderDoc, not in everyday Vulkan usage. + +\section enabling_buffer_device_address_more_information More information + +To learn more about this extension, see [VK_KHR_buffer_device_address in Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap46.html#VK_KHR_buffer_device_address) + +Example use of this extension can be found in the code of the sample and test suite +accompanying this library. + +\page general_considerations General considerations + +\section general_considerations_thread_safety Thread safety + +- The library has no global state, so separate #VmaAllocator objects can be used + independently. + There should be no need to create multiple such objects though - one per `VkDevice` is enough. +- By default, all calls to functions that take #VmaAllocator as first parameter + are safe to call from multiple threads simultaneously because they are + synchronized internally when needed. + This includes allocation and deallocation from default memory pool, as well as custom #VmaPool. +- When the allocator is created with #VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT + flag, calls to functions that take such #VmaAllocator object must be + synchronized externally. +- Access to a #VmaAllocation object must be externally synchronized. For example, + you must not call vmaGetAllocationInfo() and vmaMapMemory() from different + threads at the same time if you pass the same #VmaAllocation object to these + functions. +- #VmaVirtualBlock is not safe to be used from multiple threads simultaneously. + +\section general_considerations_versioning_and_compatibility Versioning and compatibility + +The library uses [**Semantic Versioning**](https://semver.org/), +which means version numbers follow convention: Major.Minor.Patch (e.g. 2.3.0), where: + +- Incremented Patch version means a release is backward- and forward-compatible, + introducing only some internal improvements, bug fixes, optimizations etc. + or changes that are out of scope of the official API described in this documentation. +- Incremented Minor version means a release is backward-compatible, + so existing code that uses the library should continue to work, while some new + symbols could have been added: new structures, functions, new values in existing + enums and bit flags, new structure members, but not new function parameters. +- Incrementing Major version means a release could break some backward compatibility. + +All changes between official releases are documented in file "CHANGELOG.md". + +\warning Backward compatibility is considered on the level of C++ source code, not binary linkage. +Adding new members to existing structures is treated as backward compatible if initializing +the new members to binary zero results in the old behavior. +You should always fully initialize all library structures to zeros and not rely on their +exact binary size. + +\section general_considerations_validation_layer_warnings Validation layer warnings + +When using this library, you can meet following types of warnings issued by +Vulkan validation layer. They don't necessarily indicate a bug, so you may need +to just ignore them. + +- *vkBindBufferMemory(): Binding memory to buffer 0xeb8e4 but vkGetBufferMemoryRequirements() has not been called on that buffer.* + - It happens when VK_KHR_dedicated_allocation extension is enabled. + `vkGetBufferMemoryRequirements2KHR` function is used instead, while validation layer seems to be unaware of it. +- *Mapping an image with layout VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL can result in undefined behavior if this memory is used by the device. Only GENERAL or PREINITIALIZED should be used.* + - It happens when you map a buffer or image, because the library maps entire + `VkDeviceMemory` block, where different types of images and buffers may end + up together, especially on GPUs with unified memory like Intel. +- *Non-linear image 0xebc91 is aliased with linear buffer 0xeb8e4 which may indicate a bug.* + - It may happen when you use [defragmentation](@ref defragmentation). + +\section general_considerations_allocation_algorithm Allocation algorithm + +The library uses following algorithm for allocation, in order: + +-# Try to find free range of memory in existing blocks. +-# If failed, try to create a new block of `VkDeviceMemory`, with preferred block size. +-# If failed, try to create such block with size / 2, size / 4, size / 8. +-# If failed, try to allocate separate `VkDeviceMemory` for this allocation, + just like when you use #VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT. +-# If failed, choose other memory type that meets the requirements specified in + VmaAllocationCreateInfo and go to point 1. +-# If failed, return `VK_ERROR_OUT_OF_DEVICE_MEMORY`. + +\section general_considerations_features_not_supported Features not supported + +Features deliberately excluded from the scope of this library: + +-# **Data transfer.** Uploading (streaming) and downloading data of buffers and images + between CPU and GPU memory and related synchronization is responsibility of the user. + Defining some "texture" object that would automatically stream its data from a + staging copy in CPU memory to GPU memory would rather be a feature of another, + higher-level library implemented on top of VMA. + VMA doesn't record any commands to a `VkCommandBuffer`. It just allocates memory. +-# **Recreation of buffers and images.** Although the library has functions for + buffer and image creation: vmaCreateBuffer(), vmaCreateImage(), you need to + recreate these objects yourself after defragmentation. That is because the big + structures `VkBufferCreateInfo`, `VkImageCreateInfo` are not stored in + #VmaAllocation object. +-# **Handling CPU memory allocation failures.** When dynamically creating small C++ + objects in CPU memory (not Vulkan memory), allocation failures are not checked + and handled gracefully, because that would complicate code significantly and + is usually not needed in desktop PC applications anyway. + Success of an allocation is just checked with an assert. +-# **Code free of any compiler warnings.** Maintaining the library to compile and + work correctly on so many different platforms is hard enough. Being free of + any warnings, on any version of any compiler, is simply not feasible. + There are many preprocessor macros that make some variables unused, function parameters unreferenced, + or conditional expressions constant in some configurations. + The code of this library should not be bigger or more complicated just to silence these warnings. + It is recommended to disable such warnings instead. +-# This is a C++ library with C interface. **Bindings or ports to any other programming languages** are welcome as external projects but + are not going to be included into this repository. +*/ + diff --git a/client/main.go b/client/main.go deleted file mode 100644 index a04b34d..0000000 --- a/client/main.go +++ /dev/null @@ -1,221 +0,0 @@ -package main - -import ( - "time" - - "github.com/gen2brain/raylib-go/raylib" -) - -type ClientAuth struct { - -} - -type Entity struct { - -} - -type LoginState int - -const ( - LOGIN_STATE_CREDENTIALS LoginState = iota - LOGIN_STATE_ATTEMPTING - LOGIN_STATE_ERROR -) - -type ButtonState int - -const ( - BUTTON_STATE_IDLE ButtonState = iota - BUTTON_STATE_HOVER - BUTTON_STATE_PRESSED -) - -type Scale struct { - window rl.Vector2 - ui uint -} - -type ClientState struct { - auth *ClientAuth - login LoginState - login_button ButtonState - scale Scale - screen rl.Rectangle - camera rl.Camera - - command_queue chan interface{} - network_queue chan interface{} - - context *Entity - context_pos rl.Vector2 -} - -const ( - WIDTH uint = 1600 - HEIGHT uint = 1000 -) - -func scale(original rl.Rectangle, scale Scale) rl.Rectangle { - return rl.Rectangle{ - X: original.X, - Y: original.Y, - Width: original.Width*float32(scale.ui)/scale.window.X, - Height: original.Height*float32(scale.ui)/scale.window.Y, - } -} - -func offset(original rl.Rectangle, off_x, off_y int, scale Scale) rl.Rectangle { - return rl.Rectangle{ - X: original.X + float32(off_x)/scale.window.X, - Y: original.Y + float32(off_y)/scale.window.Y, - Width: original.Width, - Height: original.Height, - } -} - -func center(container, original rl.Rectangle) rl.Rectangle { - return rl.Rectangle{ - X: (container.Width - original.Width )/2 + container.X, - Y: (container.Height - original.Height)/2 + container.Y, - Width: original.Width, - Height: original.Height, - } -} - -func button(rectangle rl.Rectangle, idle, hover, pressed rl.Color, text string, font_size int32, text_color rl.Color, last_state ButtonState) (action bool, next_state ButtonState) { - over := rl.CheckCollisionPointRec(rl.GetMousePosition(), rectangle) - action = false - next_state = last_state - - if(rl.IsMouseButtonPressed(rl.MouseButtonLeft) && over) { - next_state = BUTTON_STATE_PRESSED - } else if(rl.IsMouseButtonReleased(rl.MouseButtonLeft) && last_state == BUTTON_STATE_PRESSED) { - next_state = BUTTON_STATE_IDLE - if(over) { - action = true - } - } else if(rl.IsMouseButtonUp(rl.MouseButtonLeft)) { - if(last_state == BUTTON_STATE_IDLE && over) { - next_state = BUTTON_STATE_HOVER - } else if (last_state == BUTTON_STATE_HOVER && !over) { - next_state = BUTTON_STATE_IDLE - } - } - - var color rl.Color - switch(next_state) { - case BUTTON_STATE_HOVER: - color = hover - case BUTTON_STATE_PRESSED: - color = pressed - default: - color = idle - } - rl.DrawRectangleRec(rectangle, color) - text_size := rl.MeasureTextEx(rl.GetFontDefault(), text, float32(font_size), 1.0) - rl.DrawText(text, int32(rectangle.X + rectangle.Width/2 - text_size.X/2), int32(rectangle.Y + rectangle.Height/2 - text_size.Y/2), font_size, text_color) - - return action, next_state -} - -func NetworkThread(network_queue chan interface{}) { - for(true) { - // TODO: remove - time.Sleep(time.Millisecond) - } -} - -func LogicThread(state *ClientState) { - for(true) { - select { - case _ = <- state.command_queue: - case _ = <- state.network_queue: - } - time.Sleep(time.Millisecond) - } -} - -func main() { - state := ClientState{ - auth: nil, - login: LOGIN_STATE_CREDENTIALS, - login_button: BUTTON_STATE_IDLE, - scale: Scale{ - ui: 1, - }, - camera: rl.NewCamera3D(rl.Vector3{}, rl.Vector3{}, rl.Vector3{}, 90, rl.CameraOrthographic), - context: nil, - } - - go LogicThread(&state) - - rl.SetConfigFlags(rl.FlagWindowHighdpi) - rl.InitWindow(0, 0, "roleplay") - rl.SetExitKey(0) - rl.SetTargetFPS(60) - - state.scale.window = rl.GetWindowScaleDPI() - rl.SetWindowSize(int(float32(WIDTH)/state.scale.window.X), int(float32(HEIGHT)/state.scale.window.Y)) - - state.screen.Width = float32(WIDTH) /state.scale.window.X - state.screen.Height = float32(HEIGHT)/state.scale.window.Y - - for(rl.WindowShouldClose() == false) { - if(state.auth == nil) { - // Draw login - rl.BeginDrawing() - rl.ClearBackground(rl.Black) - - logo_rect := scale(rl.Rectangle{Width: 1200, Height: 300}, state.scale) - logo_rect = center(state.screen, logo_rect) - logo_rect = offset(logo_rect, 0, -250, state.scale) - rl.DrawRectangleRec(logo_rect, rl.Gray) - - form_rect := scale(rl.Rectangle{Width: 600, Height: 400}, state.scale) - form_rect = center(state.screen, form_rect) - form_rect = offset(form_rect, 0, 200, state.scale) - rl.DrawRectangleRec(form_rect, rl.Gray) - - switch(state.login) { - case LOGIN_STATE_CREDENTIALS: - submit_rect := scale(rl.Rectangle{Width: 100, Height: 50}, state.scale) - submit_rect = center(form_rect, submit_rect) - submit_rect = offset(submit_rect, 0, 125, state.scale) - var submit_action bool - submit_action, state.login_button = button(submit_rect, rl.Black, rl.Brown, rl.Red, "Submit", 12, rl.White, state.login_button) - if submit_action { - // TODO: real auth via network thread - state.auth = &ClientAuth{} - } - case LOGIN_STATE_ATTEMPTING: - text := "Logging in..." - text_size := rl.MeasureTextEx(rl.GetFontDefault(), text, 20, 1.0) - text_rect := center(form_rect, rl.Rectangle{Width: text_size.X, Height: text_size.Y}) - rl.DrawText(text, int32(text_rect.X), int32(text_rect.Y), 20, rl.Black) - case LOGIN_STATE_ERROR: - text := "Error: {TODO}" - text_size := rl.MeasureTextEx(rl.GetFontDefault(), text, 20, 1.0) - text_rect := center(form_rect, rl.Rectangle{Width: text_size.X, Height: text_size.Y}) - rl.DrawText(text, int32(text_rect.X), int32(text_rect.Y), 20, rl.Black) - } - - rl.EndDrawing() - } else { - rl.BeginDrawing() - rl.ClearBackground(rl.Black) - rl.BeginMode3D(state.camera) - - if rl.IsMouseButtonPressed(rl.MouseButtonRight) { - state.context_pos = rl.GetMousePosition() - // TODO: Cast a ray into the bounding boxes - } - - if state.context != nil { - - } - - rl.EndMode3D() - rl.EndDrawing() - } - } -} diff --git a/client/src/main.c b/client/src/main.c new file mode 100644 index 0000000..9e55750 --- /dev/null +++ b/client/src/main.c @@ -0,0 +1,34 @@ +#include "render.h" + +int render_thread(GLFWwindow* window, RenderContext* render_context) { + while(glfwWindowShouldClose(window) == 0) { + glfwPollEvents(); + } + return 0; +} + +int logic_thread() { + return 0; +} + +int network_thread() { + return 0; +} + +int main() { + GLFWwindow* window = init_window(); + if(window == NULL) { + return 1; + } + + RenderContext render_context = {}; + if(init_vulkan(window, &render_context) != VK_SUCCESS) { + return 2; + } + + if(render_thread(window, &render_context) != 0) { + return 3; + } + + return 0; +} diff --git a/client/src/main.o b/client/src/main.o new file mode 100644 index 0000000000000000000000000000000000000000..aa6985af0d4eb992be6a930b21269bf6f525044c GIT binary patch literal 24944 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 SJW4M=rzE$)kO4#*FaQ888^ZSh literal 0 HcmV?d00001 diff --git a/client/src/render.c b/client/src/render.c new file mode 100644 index 0000000..33312ff --- /dev/null +++ b/client/src/render.c @@ -0,0 +1,1050 @@ +#include "render.h" +#include "vk_mem_alloc.h" + +const uint32_t MAX_FRAMES_IN_FLIGHT = 2; + +const char * validation_layers[] = { + "VK_LAYER_KHRONOS_validation", + //"VK_LAYER_LUNARG_api_dump", + "VK_LAYER_KHRONOS_synchronization2", + "VK_LAYER_KHRONOS_shader_object", +}; +uint32_t validation_layer_count = sizeof(validation_layers) / sizeof(const char *); + +const char * instance_extensions[] = { + VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, + VK_EXT_DEBUG_UTILS_EXTENSION_NAME, + "VK_EXT_metal_surface", + VK_KHR_SURFACE_EXTENSION_NAME, +}; +uint32_t instance_extension_count = sizeof(instance_extensions) / sizeof(const char *); + +const char * device_extensions[] = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME, + "VK_KHR_portability_subset", +}; +uint32_t device_extension_count = sizeof(device_extensions) / sizeof(const char *); + +VkFormat depth_formats[] = { + VK_FORMAT_D32_SFLOAT, + VK_FORMAT_D32_SFLOAT_S8_UINT, + VK_FORMAT_D24_UNORM_S8_UINT +}; +uint32_t depth_format_count = sizeof(depth_formats) / sizeof(VkFormat); + + + +void glfw_error(int error, const char* description) { + fprintf(stderr, "GLFW_ERR: 0x%02x - %s\n", error, description); +} + +GLFWwindow* init_window() { + glfwInit(); + glfwSetErrorCallback(glfw_error); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); + GLFWwindow* window = glfwCreateWindow(800, 500, "roleplay", 0, 0); + + return window; +} + +bool check_validation_layers(const char ** layers, uint32_t num_layers) { + uint32_t layer_count; + VkResult result; + + result = vkEnumerateInstanceLayerProperties(&layer_count, 0); + if(result != VK_SUCCESS) { + return false; + } + + VkLayerProperties* available_layers = malloc(sizeof(VkLayerProperties)*layer_count); + result = vkEnumerateInstanceLayerProperties(&layer_count, available_layers); + + for(uint32_t i = 0; i < num_layers; i++) { + bool found = false; + for(uint32_t j = 0; j < layer_count; j++) { + if(strcmp(layers[i], available_layers[j].layerName) == 0) { + found = true; + } + } + if(found == false) { + free(available_layers); + return false; + } + } + + free(available_layers); + return true; +} + +VkResult create_instance(VkInstance* instance) { + if(check_validation_layers(validation_layers, validation_layer_count) == false) { + fprintf(stderr, "requested validation layers not supported\n"); + return VK_ERROR_VALIDATION_FAILED_EXT; + } + + VkApplicationInfo app_info = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "roleplay", + .applicationVersion = VK_MAKE_VERSION(0, 0, 1), + .pEngineName = "roleplay", + .engineVersion = VK_MAKE_VERSION(0, 0, 1), + .apiVersion = VK_API_VERSION_1_2, + }; + + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions; + + glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + const char** requested_extensions = malloc(sizeof(char*)*(glfwExtensionCount + instance_extension_count)); + for (uint32_t i = 0; i < glfwExtensionCount; i++) { + requested_extensions[i] = glfwExtensions[i]; + } + + for (uint32_t i = 0; i < instance_extension_count; i++) { + requested_extensions[glfwExtensionCount + i] = instance_extensions[i]; + } + + VkInstanceCreateInfo instance_info = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &app_info, + .enabledLayerCount = validation_layer_count, + .ppEnabledLayerNames = validation_layers, + .enabledExtensionCount = glfwExtensionCount + instance_extension_count, + .ppEnabledExtensionNames = requested_extensions, + .flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR, + }; + + + + VkResult result = vkCreateInstance(&instance_info, 0, instance); + if(result != VK_SUCCESS) { + fprintf(stderr, "vkCreateInstance: %s\n", string_VkResult(result)); + return result; + } + + free(requested_extensions); + return VK_SUCCESS; +} + +static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT* callback_data, + void* user_data) { + + (void)severity; + (void)type; + (void)user_data; + + fprintf(stderr, "Validation layer: %s\n", callback_data->pMessage); + + return VK_FALSE; +} + +VkResult create_debug_messenger(VkInstance instance, VkDebugUtilsMessengerEXT* debug_messenger) { + VkDebugUtilsMessengerCreateInfoEXT messenger_info = { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT, + .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_DEVICE_ADDRESS_BINDING_BIT_EXT, + .pfnUserCallback = debug_callback, + .pUserData = 0, + }; + + PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + + VkResult result; + result = func(instance, &messenger_info, 0, debug_messenger); + if(result != VK_SUCCESS) { + fprintf(stderr, "failed to create debug messenger\n"); + return result; + } + + return VK_SUCCESS; +} + +VkResult get_best_physical_device(VkInstance instance, VkPhysicalDevice* device) { + uint32_t device_count = 0; + VkResult result; + result = vkEnumeratePhysicalDevices(instance, &device_count, 0); + if(result != VK_SUCCESS) { + return result; + } + + VkPhysicalDevice* devices = malloc(sizeof(VkPhysicalDevice)*device_count); + result = vkEnumeratePhysicalDevices(instance, &device_count, devices); + if(result != VK_SUCCESS) { + free(devices); + return result; + } + + int top_score = -1; + for(uint32_t i = 0; i < device_count; i++) { + int score = 0; + + VkPhysicalDeviceProperties properties; + vkGetPhysicalDeviceProperties(devices[i], &properties); + + VkPhysicalDeviceFeatures features; + vkGetPhysicalDeviceFeatures(devices[i], &features); + + switch(properties.deviceType) { + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: + score += 100; + break; + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: + score += 50; + break; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: + score += 25; + break; + case VK_PHYSICAL_DEVICE_TYPE_CPU: + score += 0; + break; + default: + continue; + } + + if(score > top_score) { + top_score = score; + *device = devices[i]; + } + } + + free(devices); + + return VK_SUCCESS; +} + +VkResult create_logical_device(VkPhysicalDevice physical_device, VkSurfaceKHR surface, Queue* graphics_queue, Queue* present_queue, Queue* transfer_queue, VkDevice* device) { + if(graphics_queue == NULL || present_queue == NULL || transfer_queue == NULL || device == NULL) { + return VK_ERROR_VALIDATION_FAILED_EXT; + } + + uint32_t queue_family_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, NULL); + + VkQueueFamilyProperties* queue_families = malloc(sizeof(VkQueueFamilyProperties)*queue_family_count); + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_families); + + graphics_queue->family = 0xFFFFFFFF; + present_queue->family = 0xFFFFFFFF; + for(uint32_t idx = 0; idx < queue_family_count; idx++) { + VkBool32 present_support = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, idx, surface, &present_support); + VkBool32 graphics_support = (queue_families[idx].queueFlags & VK_QUEUE_GRAPHICS_BIT); + + if(graphics_support && present_support) { + graphics_queue->family = idx; + graphics_queue->index = 0; + + present_queue->family = idx; + present_queue->index = 0; + break; + } else if (graphics_support && (graphics_queue->family == 0xFFFFFFFF)) { + graphics_queue->family = idx; + graphics_queue->index = 0; + } else if (present_support && (present_queue->family == 0xFFFFFFFF)) { + graphics_queue->family = idx; + present_queue->index = 0; + } + } + + transfer_queue->family = 0xFFFFFFFF; + for(uint32_t idx = 0; idx < queue_family_count; idx++) { + VkBool32 graphics_support = (queue_families[idx].queueFlags & VK_QUEUE_GRAPHICS_BIT); + VkBool32 compute_support = (queue_families[idx].queueFlags & VK_QUEUE_COMPUTE_BIT); + VkBool32 is_graphics_family = (graphics_queue->family == idx); + VkBool32 is_present_family = (present_queue->family == idx); + uint32_t queue_count = queue_families[idx].queueCount; + + if(is_graphics_family && (queue_count == 1)) { + continue; + } else if (is_present_family && (queue_count == 1)) { + continue; + } + + if(graphics_support && compute_support) { + transfer_queue->family = idx; + if(is_graphics_family || is_present_family) { + transfer_queue->index = 1; + } else { + transfer_queue->index = 0; + } + } + } + + if(graphics_queue->family == 0xFFFFFFFF || present_queue->family == 0xFFFFFFFF || transfer_queue->family == 0xFFFFFFFF) { + return VK_ERROR_INITIALIZATION_FAILED; + } + + VkDeviceQueueCreateInfo queue_create_info[3] = {}; + uint32_t queue_count = 0; + float default_queue_priority = 1.0f; + if(graphics_queue->family == present_queue->family && graphics_queue->family == transfer_queue->family) { + queue_count = 1; + + queue_create_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info[0].queueFamilyIndex = graphics_queue->family; + queue_create_info[0].queueCount = 2; + queue_create_info[0].pQueuePriorities = &default_queue_priority; + } else if (graphics_queue->family == present_queue->family) { + queue_count = 2; + + queue_create_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info[0].queueFamilyIndex = graphics_queue->family; + queue_create_info[0].queueCount = 1; + queue_create_info[0].pQueuePriorities = &default_queue_priority; + + queue_create_info[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info[1].queueFamilyIndex = transfer_queue->family; + queue_create_info[1].queueCount = 1; + queue_create_info[1].pQueuePriorities = &default_queue_priority; + } else if (graphics_queue->family == transfer_queue->family) { + queue_count = 2; + + queue_create_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info[0].queueFamilyIndex = graphics_queue->family; + queue_create_info[0].queueCount = 2; + queue_create_info[0].pQueuePriorities = &default_queue_priority; + + queue_create_info[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info[1].queueFamilyIndex = present_queue->family; + queue_create_info[1].queueCount = 1; + queue_create_info[1].pQueuePriorities = &default_queue_priority; + } else { + queue_count = 3; + + queue_create_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info[0].queueFamilyIndex = graphics_queue->family; + queue_create_info[0].queueCount = 1; + queue_create_info[0].pQueuePriorities = &default_queue_priority; + + queue_create_info[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info[1].queueFamilyIndex = present_queue->family; + queue_create_info[1].queueCount = 1; + queue_create_info[1].pQueuePriorities = &default_queue_priority; + + queue_create_info[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info[1].queueFamilyIndex = transfer_queue->family; + queue_create_info[1].queueCount = 1; + queue_create_info[1].pQueuePriorities = &default_queue_priority; + } + + VkPhysicalDeviceVulkan12Features features_12 = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, + .bufferDeviceAddress = VK_TRUE, + .descriptorIndexing = VK_TRUE, + .descriptorBindingPartiallyBound = VK_TRUE, + .descriptorBindingVariableDescriptorCount = VK_TRUE, + .descriptorBindingUniformBufferUpdateAfterBind = VK_TRUE, + .descriptorBindingStorageBufferUpdateAfterBind = VK_TRUE, + .descriptorBindingSampledImageUpdateAfterBind = VK_TRUE, + }; + + VkPhysicalDeviceFeatures device_features = { + .samplerAnisotropy = VK_TRUE, + }; + + VkDeviceCreateInfo device_create_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pQueueCreateInfos = queue_create_info, + .queueCreateInfoCount = queue_count, + .pEnabledFeatures = &device_features, + .enabledExtensionCount = device_extension_count, + .ppEnabledExtensionNames = device_extensions, + .enabledLayerCount = validation_layer_count, + .ppEnabledLayerNames = validation_layers, + .pNext = &features_12, + }; + + VkResult result = vkCreateDevice(physical_device, &device_create_info, 0, device); + if(result != VK_SUCCESS) { + return result; + } + + vkGetDeviceQueue(*device, graphics_queue->family, graphics_queue->index, &graphics_queue->handle); + vkGetDeviceQueue(*device, present_queue->family, present_queue->index, &present_queue->handle); + vkGetDeviceQueue(*device, transfer_queue->family, transfer_queue->index, &transfer_queue->handle); + + return VK_SUCCESS; +} + +VkResult create_memory_allocator(VkInstance instance, VkPhysicalDevice physical_device, VkDevice device, VmaAllocator* allocator) { + VmaAllocatorCreateInfo allocator_create_info = { + .vulkanApiVersion = VK_API_VERSION_1_2, + .instance = instance, + .physicalDevice = physical_device, + .device = device, + }; + + VkResult result = vmaCreateAllocator(&allocator_create_info, allocator); + if(result != VK_SUCCESS) { + return result; + } + + return VK_SUCCESS; +} + +VkResult get_swapchain_details(VkPhysicalDevice physical_device, VkSurfaceKHR surface, SwapchainDetails* details) { + details->formats = 0; + details->present_modes = 0; + + VkResult result; + + result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &details->capabilities); + if(result != VK_SUCCESS) { + return result; + } + + result = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &details->formats_count, 0); + if(result != VK_SUCCESS) { + return result; + } + details->formats = malloc(sizeof(VkSurfaceFormatKHR)*details->formats_count); + result = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &details->formats_count, details->formats); + if(result != VK_SUCCESS) { + free(details->formats); + return result; + } + + result = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &details->present_modes_count, 0); + if(result != VK_SUCCESS) { + free(details->formats); + return result; + } + details->present_modes = malloc(sizeof(VkPresentModeKHR)*details->present_modes_count); + result = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &details->present_modes_count, details->present_modes); + if(result != VK_SUCCESS) { + free(details->formats); + free(details->present_modes); + return result; + } + + return VK_SUCCESS; +} + +VkSurfaceFormatKHR choose_swapchain_format(SwapchainDetails swapchain_details) { + for(uint32_t i = 0; i < swapchain_details.formats_count; i++) { + VkSurfaceFormatKHR format = swapchain_details.formats[i]; + if(format.format == VK_FORMAT_B8G8R8A8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return format; + } + } + return swapchain_details.formats[0]; +} + +VkPresentModeKHR choose_present_mode(SwapchainDetails swapchain_details) { + for(uint32_t i = 0; i < swapchain_details.present_modes_count; i++) { + if(swapchain_details.present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) { + return VK_PRESENT_MODE_MAILBOX_KHR; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; +} + +VkExtent2D choose_swapchain_extent(SwapchainDetails swapchain_details) { + return swapchain_details.capabilities.currentExtent; +} + +VkResult create_swapchain(VkDevice device, VkSurfaceFormatKHR format, VkPresentModeKHR present_mode, VkExtent2D extent, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR capabilities, uint32_t graphics_family_index, uint32_t present_family_index, VkSwapchainKHR* swapchain) { + uint32_t image_count = capabilities.minImageCount + 1; + uint32_t max_images = capabilities.maxImageCount; + if((max_images > 0) && (image_count > max_images)) { + image_count = max_images; + } + + VkSwapchainCreateInfoKHR swapchain_info = { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = surface, + .minImageCount = image_count, + .imageFormat = format.format, + .imageColorSpace = format.colorSpace, + .imageExtent = extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .preTransform = capabilities.currentTransform, + .compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, + .presentMode = present_mode, + .clipped = VK_TRUE, + .oldSwapchain = *swapchain, + }; + + uint32_t queue_families[2] = {graphics_family_index, present_family_index}; + if(graphics_family_index != present_family_index) { + swapchain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + swapchain_info.queueFamilyIndexCount = 2; + swapchain_info.pQueueFamilyIndices = queue_families; + } else { + swapchain_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + swapchain_info.queueFamilyIndexCount = 0; + swapchain_info.pQueueFamilyIndices = 0; + } + + + VkResult result; + result = vkCreateSwapchainKHR(device, &swapchain_info, 0, swapchain); + if(result != VK_SUCCESS) { + return result; + } + + return VK_SUCCESS; +} + +VkResult get_swapchain_images(VkDevice device, VkSwapchainKHR swapchain, VkImage** images, uint32_t* image_count) { + VkResult result; + result = vkGetSwapchainImagesKHR(device, swapchain, image_count, 0); + if(result != VK_SUCCESS) { + return result; + } + + *images = malloc(sizeof(VkImage)*(*image_count)); + if(*images == 0) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + + result = vkGetSwapchainImagesKHR(device, swapchain, image_count, *images); + if(result != VK_SUCCESS) { + free(*images); + return result; + } + + return VK_SUCCESS; +} + +VkResult create_image_views(VkDevice device, uint32_t image_count, VkImage* images, VkSurfaceFormatKHR format, VkImageView** image_views) { + *image_views = malloc(sizeof(VkImageView)*image_count); + if(*image_views == 0) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + + for(uint32_t i = 0; i < image_count; i++) { + VkImageViewCreateInfo view_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = images[i], + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = format.format, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + VkResult result = vkCreateImageView(device, &view_info, 0, &(*image_views)[i]); + if(result != VK_SUCCESS) { + free(*image_views); + return result; + } + } + + return VK_SUCCESS; +} + +VkResult find_depth_format(VkPhysicalDevice physical_device, VkImageTiling tiling, VkFormatFeatureFlags features, VkFormat* format) { + for(uint32_t i = 0; i < depth_format_count; i++) { + VkFormatProperties properties; + vkGetPhysicalDeviceFormatProperties(physical_device, depth_formats[i], &properties); + + if(tiling == VK_IMAGE_TILING_LINEAR && (properties.linearTilingFeatures & features) == features) { + *format = depth_formats[i]; + return VK_SUCCESS; + } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (properties.optimalTilingFeatures & features) == features) { + *format = depth_formats[i]; + return VK_SUCCESS; + } + } + return VK_ERROR_UNKNOWN; +} + +VkResult create_render_pass(VkDevice device, VkSurfaceFormatKHR format, VkFormat depth_format, VkRenderPass* render_pass) { + VkAttachmentDescription attachments[] = { + { + .format = format.format, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + }, + { + .format = depth_format, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + }, + }; + + VkAttachmentReference color_attachment_refs[] = { + { + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }, + }; + + VkAttachmentReference depth_attachment_ref = { + .attachment = 1, + .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + }; + + // Create a subpass with the color and depth attachments + VkSubpassDescription subpasses[] = { + { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .colorAttachmentCount = sizeof(color_attachment_refs)/sizeof(VkAttachmentReference), + .pColorAttachments = color_attachment_refs, + .pDepthStencilAttachment = &depth_attachment_ref, + }, + }; + + // This basically says "make sure nothing else is writing to the depth_stencil or the color attachment during the pipeline + VkSubpassDependency dependencies[] = { + { + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + .srcAccessMask = 0, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT, + } + }; + + VkRenderPassCreateInfo render_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .attachmentCount = sizeof(attachments)/sizeof(VkAttachmentDescription), + .pAttachments = attachments, + .subpassCount = sizeof(subpasses)/sizeof(VkSubpassDescription), + .pSubpasses = subpasses, + .dependencyCount = sizeof(dependencies)/sizeof(VkSubpassDependency), + .pDependencies = dependencies, + }; + + VkResult result = vkCreateRenderPass(device, &render_info, 0, render_pass); + if(result != VK_SUCCESS) { + return result; + } + + return VK_SUCCESS; +} + +VkCommandBuffer command_begin_single(VkDevice device, VkCommandPool transfer_pool) { + VkCommandBufferAllocateInfo command_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandPool = transfer_pool, + .commandBufferCount = 1, + }; + + VkCommandBuffer command_buffer; + VkResult result = vkAllocateCommandBuffers(device, &command_info, &command_buffer); + if(result != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + + VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }; + + result = vkBeginCommandBuffer(command_buffer, &begin_info); + if(result != VK_SUCCESS) { + vkFreeCommandBuffers(device, transfer_pool, 1, &command_buffer); + return VK_NULL_HANDLE; + } + + return command_buffer; +} + +VkResult command_end_single(VkDevice device, VkCommandBuffer command_buffer, VkCommandPool transfer_pool, Queue transfer_queue) { + VkResult result = vkEndCommandBuffer(command_buffer); + if(result != VK_SUCCESS) { + vkFreeCommandBuffers(device, transfer_pool, 1, &command_buffer); + return result; + } + + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &command_buffer, + }; + + result = vkQueueSubmit(transfer_queue.handle, 1, &submit_info, 0); + if(result != VK_SUCCESS) { + vkFreeCommandBuffers(device, transfer_pool, 1, &command_buffer); + return result; + } + + result = vkQueueWaitIdle(transfer_queue.handle); + vkFreeCommandBuffers(device, transfer_pool, 1, &command_buffer); + return result; +} + +VkResult command_transition_image_layout(VkDevice device, VkCommandPool transfer_pool, Queue transfer_queue, VkImageLayout old_layout, VkImageLayout new_layout, VkImage image, VkAccessFlags src_mask, VkAccessFlags dst_mask, VkPipelineStageFlags source, VkPipelineStageFlags dest, uint32_t source_family, uint32_t dest_family, VkImageAspectFlags aspect_flags) { + VkCommandBuffer command_buffer = command_begin_single(device, transfer_pool); + + VkImageMemoryBarrier barrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = source_family, + .dstQueueFamilyIndex = dest_family, + .image = image, + .subresourceRange = { + .aspectMask = aspect_flags, + .levelCount = 1, + .layerCount = 1, + .baseMipLevel = 0, + .baseArrayLayer = 0, + }, + .srcAccessMask = src_mask, + .dstAccessMask = dst_mask, + }; + vkCmdPipelineBarrier(command_buffer, source, dest, 0, 0, 0, 0, 0, 1, &barrier); + + return command_end_single(device, command_buffer, transfer_pool, transfer_queue); +} + +VkResult command_copy_buffer_to_image(VkDevice device, VkCommandPool transfer_pool, Queue transfer_queue, VkExtent3D image_size, VkBuffer source, VkImage dest) { + VkCommandBuffer command_buffer = command_begin_single(device, transfer_pool); + + VkBufferImageCopy region = { + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = { + .baseArrayLayer = 0, + .layerCount = 1, + .mipLevel = 0, + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + }, + .imageOffset = { + .x = 0, + .y = 0, + .z = 0, + }, + .imageExtent = image_size, + }; + + vkCmdCopyBufferToImage(command_buffer, source, dest, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + + return command_end_single(device, command_buffer, transfer_pool, transfer_queue); +} + +VkResult create_swapchain_framebuffers(VkDevice device, uint32_t image_count, VkImageView* image_views, VkImageView depth_image_view, VkRenderPass render_pass, VkExtent2D extent, VkFramebuffer** framebuffers) { + *framebuffers = malloc(sizeof(VkFramebuffer)*image_count); + if(*framebuffers == 0) { + return 0; + } + + for(uint32_t i = 0; i < image_count; i++) { + VkImageView attachments[] = { + image_views[i], + depth_image_view, + }; + + VkFramebufferCreateInfo framebuffer_info = { + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .renderPass = render_pass, + .attachmentCount = 2, + .pAttachments = attachments, + .width = extent.width, + .height = extent.height, + .layers = 1, + }; + + VkResult result = vkCreateFramebuffer(device, &framebuffer_info, 0, *framebuffers + sizeof(VkFramebuffer)*i); + if(result != VK_SUCCESS) { + free(*framebuffers); + return result; + } + } + + return VK_SUCCESS; +} + +VkSemaphore* create_semaphores(VkDevice device, VkSemaphoreCreateFlags flags, uint32_t count) { + VkSemaphore* semaphores = malloc(sizeof(VkSemaphore)*count); + if(semaphores == 0) { + return 0; + } + + VkSemaphoreCreateInfo semaphore_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + .flags = flags, + }; + + for(uint32_t i = 0; i < count; i++) { + VkResult result = vkCreateSemaphore(device, &semaphore_info, 0, &semaphores[i]); + if(result != VK_SUCCESS) { + free(semaphores); + return 0; + } + } + return semaphores; +} + +VkFence* create_fences(VkDevice device, VkFenceCreateFlags flags, uint32_t count) { + VkFence* fences = malloc(sizeof(VkFence)*count); + if(fences == 0) { + return 0; + } + + VkFenceCreateInfo fence_info = { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .flags = flags, + }; + + for(uint32_t i = 0; i < count; i++) { + VkResult result = vkCreateFence(device, &fence_info, 0, &fences[i]); + if(result != VK_SUCCESS) { + free(fences); + return 0; + } + } + return fences; +} + +VkCommandBuffer* create_command_buffers(VkDevice device, VkCommandPool command_pool, uint32_t image_count) { + VkCommandBufferAllocateInfo alloc_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = command_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = image_count, + }; + + VkCommandBuffer* command_buffers = malloc(sizeof(VkCommandBuffer)*image_count); + if(command_buffers == 0) { + return 0; + } + + VkResult result = vkAllocateCommandBuffers(device, &alloc_info, command_buffers); + if(result != VK_SUCCESS) { + return VK_NULL_HANDLE; + } + + return command_buffers; +} + +VkResult create_depth_image(VkDevice device, VkFormat depth_format, VkExtent2D swapchain_extent, VmaAllocator allocator, VkCommandPool extra_graphics_pool, Queue graphics_queue, VkImage* depth_image, VmaAllocation* depth_image_memory, VkImageView* depth_image_view) { + + VkExtent3D depth_extent = { + .width = swapchain_extent.width, + .height = swapchain_extent.height, + .depth = 1, + }; + + VkImageCreateInfo depth_image_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .extent = depth_extent, + .mipLevels = 1, + .arrayLayers = 1, + .format = depth_format, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .samples = VK_SAMPLE_COUNT_1_BIT, + .flags = 0, + }; + + VmaAllocationCreateInfo allocation_info = { + .usage = VMA_MEMORY_USAGE_GPU_ONLY, + }; + + VkResult result = vmaCreateImage(allocator, &depth_image_info, &allocation_info, depth_image, depth_image_memory, NULL); + if(result != VK_SUCCESS) { + return result; + } + + VkImageViewCreateInfo depth_view_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = *depth_image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = depth_format, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + result = vkCreateImageView(device, &depth_view_info, 0, depth_image_view); + if(result != VK_SUCCESS) { + return result; + } + + result = command_transition_image_layout(device, extra_graphics_pool, graphics_queue, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL, *depth_image, 0, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, VK_IMAGE_ASPECT_DEPTH_BIT); + if(result != VK_SUCCESS) { + return result; + } + + return VK_SUCCESS; +} + +VkResult init_vulkan(GLFWwindow* window, RenderContext* context) { + VkResult result; + if(context == NULL) { + return VK_ERROR_VALIDATION_FAILED_EXT; + } + + result = create_instance(&context->instance); + if(result != VK_SUCCESS) { + return result; + } + + result = create_debug_messenger(context->instance, &context->debug_messenger); + if(result != VK_SUCCESS) { + return result; + } + + result = get_best_physical_device(context->instance, &context->physical_device); + if(result != VK_SUCCESS) { + return result; + } + + vkGetPhysicalDeviceMemoryProperties(context->physical_device, &context->memories); + + result = glfwCreateWindowSurface(context->instance, window, 0, &context->surface); + if(result != VK_SUCCESS) { + return result; + } + + result = create_logical_device(context->physical_device, context->surface, &context->graphics_queue, &context->present_queue, &context->transfer_queue, &context->device); + if(result != VK_SUCCESS) { + return result; + } + + result = create_memory_allocator(context->instance, context->physical_device, context->device, &context->allocator); + if(result != VK_SUCCESS) { + return result; + } + + VkCommandPoolCreateInfo extra_pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .queueFamilyIndex = context->graphics_queue.family, + .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, + }; + result = vkCreateCommandPool(context->device, &extra_pool_info, 0, &context->extra_graphics_pool); + if(result != VK_SUCCESS) { + return result; + } + + VkCommandPoolCreateInfo graphics_pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, + .queueFamilyIndex = context->graphics_queue.family, + }; + result = vkCreateCommandPool(context->device, &graphics_pool_info, 0, &context->graphics_pool); + if(result != VK_SUCCESS) { + return result; + } + + VkCommandPoolCreateInfo transfer_pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, + .queueFamilyIndex = context->transfer_queue.family, + }; + result = vkCreateCommandPool(context->device, &transfer_pool_info, 0, &context->transfer_pool); + if(result != VK_SUCCESS) { + return result; + } + + context->swapchain_command_buffers = create_command_buffers(context->device, context->graphics_pool, MAX_FRAMES_IN_FLIGHT); + if(context->swapchain_command_buffers == NULL) { + return VK_ERROR_UNKNOWN; + } + + context->image_available_semaphores = create_semaphores(context->device, 0, MAX_FRAMES_IN_FLIGHT); + if(context->image_available_semaphores == NULL) { + return VK_ERROR_UNKNOWN; + } + + context->render_finished_semaphores = create_semaphores(context->device, 0, MAX_FRAMES_IN_FLIGHT); + if(context->render_finished_semaphores == NULL) { + return VK_ERROR_UNKNOWN; + } + + context->in_flight_fences = create_fences(context->device, VK_FENCE_CREATE_SIGNALED_BIT, MAX_FRAMES_IN_FLIGHT); + if(context->in_flight_fences == NULL) { + return VK_ERROR_UNKNOWN; + } + + result = get_swapchain_details(context->physical_device, context->surface, &context->swapchain_details); + if(result != VK_SUCCESS) { + return result; + } + + context->swapchain_format = choose_swapchain_format(context->swapchain_details); + context->swapchain_present_mode = choose_present_mode(context->swapchain_details); + context->swapchain_extent = choose_swapchain_extent(context->swapchain_details); + context->swapchain = VK_NULL_HANDLE; + result = create_swapchain(context->device, context->swapchain_format, context->swapchain_present_mode, context->swapchain_extent, context->surface, context->swapchain_details.capabilities, context->graphics_queue.family, context->present_queue.family, &context->swapchain); + if(result != VK_SUCCESS) { + return result; + } + + result = get_swapchain_images(context->device, context->swapchain, &context->swapchain_images, &context->swapchain_image_count); + if(result != VK_SUCCESS) { + return result; + } + + result = create_image_views(context->device, context->swapchain_image_count, context->swapchain_images, context->swapchain_format, &context->swapchain_image_views); + if(result != VK_SUCCESS) { + return result; + } + + result = find_depth_format(context->physical_device, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT, &context->depth_format); + if(result != VK_SUCCESS) { + return result; + } + + result = create_render_pass(context->device, context->swapchain_format, context->depth_format, &context->render_pass); + if(result != VK_SUCCESS) { + return result; + } + + // TODO: create and allocate the depth image + result = create_depth_image(context->device, context->depth_format, context->swapchain_extent, context->allocator, context->extra_graphics_pool, context->graphics_queue, &context->depth_image, &context->depth_image_memory, &context->depth_image_view); + if(result != VK_SUCCESS) { + return result; + } + + result = create_swapchain_framebuffers(context->device, context->swapchain_image_count, context->swapchain_image_views, context->depth_image_view, context->render_pass, context->swapchain_extent, &context->swapchain_framebuffers); + if(result != VK_SUCCESS) { + return result; + } + + // TODO: create pipelines + + + return VK_SUCCESS; +} + diff --git a/client/src/render.o b/client/src/render.o new file mode 100644 index 0000000000000000000000000000000000000000..d924f7cee91deddd0b0f24cfb4464b37609358a6 GIT binary patch literal 188296 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 literal 0 HcmV?d00001 diff --git a/client/src/vma.cpp b/client/src/vma.cpp new file mode 100644 index 0000000..a2023d3 --- /dev/null +++ b/client/src/vma.cpp @@ -0,0 +1,2 @@ +#define VMA_IMPLEMENTATION +#include "vk_mem_alloc.h" diff --git a/client/src/vma.o b/client/src/vma.o new file mode 100644 index 0000000000000000000000000000000000000000..41ba09ae445420a6b71a0e995d809c910f1b5c9c GIT binary patch literal 915456 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

p)O8}OYs&fuHj8D=m(f+}vD#K;gQbPBR1 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�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