Values of (non-zero) allocations' pUserData are printed in JSON report created by vmaBuildStatsString(), in hexadecimal form.
Allocation names
-
There is alternative mode available where pUserData pointer is used to point to a null-terminated string, giving a name to the allocation. To use this mode, set VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT flag in VmaAllocationCreateInfo::flags. Then pUserData passed as VmaAllocationCreateInfo::pUserData or argument to vmaSetAllocationUserData() must be either null or pointer to a null-terminated string. 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.
+
There is alternative mode available where pUserData pointer is used to point to a null-terminated string, giving a name to the allocation. To use this mode, set VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT flag in VmaAllocationCreateInfo::flags. Then pUserData passed as VmaAllocationCreateInfo::pUserData or argument to vmaSetAllocationUserData() must be either null or pointer to a null-terminated string. 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.
The value of pUserData pointer of the allocation will be different than the one you passed when setting allocation's name - pointing to a buffer managed internally that holds copy of the string.
Passing 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.
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 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 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.
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 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.
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. For more details, see description of this enum.
+
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. For more details, see description of this enum.
For example, if you want to create a uniform buffer that will be filled using transfer only once or infrequently and used for rendering every frame, you can do it using following code:
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:
Bitmask containing one bit set for every memory type acceptable for this allocation.
Definition: vk_mem_alloc.h:1160
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 Custom memory pools.
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.:
+
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 extension is enabled and it reports that dedicated allocation is required or recommended for the resource.
When making an allocation, set VmaAllocationCreateInfo::pool to this handle. You don't need to specify any other parameters of this structure, like usage.
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.
+
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.
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.
@@ -150,12 +150,12 @@ Choosing memory type index
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.
-
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.
+
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.
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.
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.
+
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.
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.
@@ -184,7 +184,7 @@ Buddy allocation algorithm
There is another allocation algorithm that can be used with custom pools, called "buddy". Its internal data structure is based on a binary tree of blocks, each having size that is a power of two and a half of its parent's size. When you want to allocate memory of certain size, a free node in the tree is located. If it is too large, it is recursively split into two halves (called "buddies"). However, if requested allocation size is not a power of two, the size of the allocation is aligned up to the nearest power of two and the remaining space is wasted. When two buddy nodes become free, they are merged back into one larger node.
The advantage of buddy allocation algorithm over default algorithm is faster allocation and deallocation, as well as smaller external fragmentation. The disadvantage is more wasted space (internal fragmentation). For more information, please search the Internet for "Buddy memory allocation" - sources that describe this concept in general.
Several limitations apply to pools that use buddy algorithm:
It is recommended to use VmaPoolCreateInfo::blockSize that is a power of two. Otherwise, only largest power of two smaller than the size is used for allocations. The remaining space always stays unused.
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.
The margin is applied also before first and after last allocation in a block. It may occur only once between two adjacent allocations.
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 are also not active in custom pools created with VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT flag.
+
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 are also not active in custom pools created with VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT flag.
Margins appear in JSON dump as part of free space.
Note that enabling margins increases memory usage and fragmentation.
@@ -100,7 +100,7 @@ Corruption detection
#include "vk_mem_alloc.h"
When this feature is enabled, number of bytes specified as VMA_DEBUG_MARGIN (it must be multiply of 4) before and 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().
+
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.
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: structure VmaDefragmentationInfo2, function vmaDefragmentationBegin(), vmaDefragmentationEnd(). Given set of allocations, this function can move them to compact used memory, ensure more continuous free space and possibly also free some VkDeviceMemory blocks.
+
To mitigate this problem, you can use defragmentation feature: structure VmaDefragmentationInfo2, function vmaDefragmentationBegin(), vmaDefragmentationEnd(). Given set of allocations, this function can move them to compact used memory, ensure more continuous free space and possibly also free some VkDeviceMemory blocks.
Setting VmaDefragmentationInfo2::pAllocationsChanged is optional. This output array tells whether particular allocation in VmaDefragmentationInfo2::pAllocations at the same index has been modified during defragmentation. You can pass null, but you then need to query every allocation passed to defragmentation for new parameters using vmaGetAllocationInfo() if you might need to recreate and rebind a buffer or image associated with it.
Maximum total numbers of bytes that can be copied while moving allocations to different places using ...
Definition: vk_mem_alloc.h:1375
+
Setting VmaDefragmentationInfo2::pAllocationsChanged is optional. This output array tells whether particular allocation in VmaDefragmentationInfo2::pAllocations at the same index has been modified during defragmentation. You can pass null, but you then need to query every allocation passed to defragmentation for new parameters using vmaGetAllocationInfo() if you might need to recreate and rebind a buffer or image associated with it.
Optional. Command buffer where GPU copy commands will be posted.
Definition: vk_mem_alloc.h:1399
You can combine these two methods by specifying non-zero maxGpu* as well as maxCpu* parameters. The library automatically chooses best method to defragment each memory pool.
-
You may try not to block your entire program to wait until defragmentation finishes, but do it in the background, as long as you carefully fullfill requirements described in function vmaDefragmentationBegin().
+
You may try not to block your entire program to wait until defragmentation finishes, but do it in the background, as long as you carefully fullfill requirements described in function vmaDefragmentationBegin().
Additional notes
It is only legal to defragment allocations bound to:
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.
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.
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.
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.
@@ -111,7 +111,7 @@ 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.
-
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.
+
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.
API elements related to the allocation, deallocation, and management of Vulkan memory, buffers, images. Most basic ones being: vmaCreateBuffer(), vmaCreateImage().
+More...
Destroys Vulkan image and frees allocated memory. More...
+
+
+
Detailed Description
+
API elements related to the allocation, deallocation, and management of Vulkan memory, buffers, images. Most basic ones being: vmaCreateBuffer(), vmaCreateImage().
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).
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 pUserData. The string is automatically freed together with the allocation. It is also used in vmaBuildStatsString().
+
+
VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT
Allocation will be created from upper stack in a double stack pool.
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.
+
+
VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT
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_CAN_ALIAS_BIT
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_STRATEGY_BEST_FIT_BIT
Allocation strategy that chooses smallest possible free range for the allocation.
+
+
VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT
Allocation strategy that chooses biggest possible free range for the allocation.
+
+
VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT
Allocation strategy that chooses first suitable free range for the allocation.
+
"First" doesn't necessarily means the one with smallest offset in memory, but rather the one that is easiest and fastest to find.
+
+
VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT
Allocation strategy that tries to minimize memory usage.
+
+
VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT
Allocation strategy that tries to minimize allocation time.
No intended memory usage specified. Use other members of VmaAllocationCreateInfo to specify your requirements.
+
+
VMA_MEMORY_USAGE_GPU_ONLY
Memory will be used on device only, so fast access from the device is preferred. It usually means device-local GPU (video) memory. No need to be mappable on host. It is roughly equivalent of D3D12_HEAP_TYPE_DEFAULT.
+
Usage:
+
+
Resources written and read by device, e.g. images used as attachments.
+
Resources transferred from host once (immutable) or infrequently and read by device multiple times, e.g. textures to be sampled, vertex buffers, uniform (constant) buffers, and majority of other types of resources used on GPU.
+
+
Allocation may still end up in HOST_VISIBLE memory on some implementations. In such case, you are free to map it. You can use VMA_ALLOCATION_CREATE_MAPPED_BIT with this usage type.
+
+
VMA_MEMORY_USAGE_CPU_ONLY
Memory will be mappable on host. It usually means CPU (system) memory. Guarantees to be HOST_VISIBLE and HOST_COHERENT. CPU access is typically uncached. Writes may be write-combined. Resources created in this pool may still be accessible to the device, but access to them can be slow. It is roughly equivalent of D3D12_HEAP_TYPE_UPLOAD.
+
Usage: Staging copy of resources used as transfer source.
+
+
VMA_MEMORY_USAGE_CPU_TO_GPU
Memory that is both mappable on host (guarantees to be HOST_VISIBLE) and preferably fast to access by GPU. CPU access is typically uncached. Writes may be write-combined.
+
Usage: Resources written frequently by host (dynamic), read by device. E.g. textures (with LINEAR layout), vertex buffers, uniform buffers updated every frame or every draw call.
+
+
VMA_MEMORY_USAGE_GPU_TO_CPU
Memory mappable on host (guarantees to be HOST_VISIBLE) and cached. It is roughly equivalent of D3D12_HEAP_TYPE_READBACK.
+
Usage:
+
+
Resources written by device, read by host - results of some computations, e.g. screen capture, average scene luminance for HDR tone mapping.
+
Any resources read or accessed randomly on host, e.g. CPU-side copy of vertex buffer used as source of transfer, but also used for collision detection.
+
+
+
VMA_MEMORY_USAGE_CPU_COPY
CPU memory - memory that is preferably not DEVICE_LOCAL, but also not guaranteed to be HOST_VISIBLE.
+
Usage: Staging copy of resources moved from GPU memory to CPU memory as part of custom paging/residency mechanism, to be moved back to GPU memory when needed.
+
+
VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED
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.
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.
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_LINEAR_ALGORITHM_BIT
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 Linear allocation algorithm.
Enables alternative, buddy allocation algorithm in this pool.
+
It operates on a tree of blocks, each having size that is a power of two and a half of its parent's size. Comparing to default algorithm, this one provides faster allocation and deallocation and decreased external fragmentation, at the expense of more memory wasted (internal fragmentation). For details, see documentation chapter Buddy allocation algorithm.
+
+
VMA_POOL_CREATE_TLSF_ALGORITHM_BIT
Enables alternative, Two-Level Segregated Fit (TLSF) allocation algorithm in this pool.
+
This algorithm is based on 2-level lists dividing address space into smaller chunks. The first level is aligned to power of two which serves as buckets for requested memory to fall into, and the second level is lineary subdivided into lists of free memory. This algorithm aims to achieve bounded response time even in the worst case scenario. Allocation time can be sometimes slightly longer than compared to other algorithms but in return the application can avoid stalls in case of fragmentation, giving predictable results, suitable for real-time use cases.
+
+
VMA_POOL_CREATE_ALGORITHM_MASK
Bit mask to extract only ALGORITHM bits from entire set of flags.
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.
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.
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.
Checks magic number in margins around all allocations in given memory types (in both default and custom pools) in search for corruptions.
+
Parameters
+
+
allocator
+
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.
+
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.
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.
+
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.
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().
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.
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.
Deprecated. Compacts memory by moving allocations.
+
Parameters
+
+
allocator
+
pAllocations
Array of allocations that can be moved during this compation.
+
allocationCount
Number of elements in pAllocations and pAllocationsChanged arrays.
+
[out]
pAllocationsChanged
Array of boolean values that will indicate whether matching allocation in pAllocations array has been moved. This parameter is optional. Pass null if you don't need this information.
+
pDefragmentationInfo
Configuration parameters. Optional - pass null to use default values.
+
[out]
pDefragmentationStats
Statistics returned by the function. Optional - pass null if you don't need this information.
+
+
+
+
Returns
VK_SUCCESS if completed, negative error code in case of error.
This function works by moving allocations to different places (different VkDeviceMemory objects and/or different offsets) in order to optimize memory usage. Only allocations that are in pAllocations array can be moved. All other allocations are considered nonmovable in this call. Basic rules:
+
+
Only allocations made in memory types that have VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT and VK_MEMORY_PROPERTY_HOST_COHERENT_BIT flags can be compacted. You may pass other allocations but it makes no sense - these will never be moved.
Both allocations made with or without VMA_ALLOCATION_CREATE_MAPPED_BIT flag can be compacted. If not persistently mapped, memory will be mapped temporarily inside this function if needed.
+
You must not pass same VmaAllocation object multiple times in pAllocations array.
+
+
The function also frees empty VkDeviceMemory blocks.
+
Warning: This function may be time-consuming, so you shouldn't call it too often (like after every resource creation/destruction). You can call it on special occasions (like when reloading a game level or when you just destroyed a lot of objects). Calling it every frame may be OK, but you should measure that on your platform.
VK_SUCCESS and *pContext == null if defragmentation finished within this function call. VK_NOT_READY and *pContext != null if defragmentation has been started and you need to call vmaDefragmentationEnd() to finish it. Negative value in case of error.
+
Use this function instead of old, deprecated vmaDefragment().
You should not use any of allocations passed as pInfo->pAllocations or any allocations that belong to pools passed as pInfo->pPools, including calling vmaGetAllocationInfo(), or access their data.
+
Some mutexes protecting internal data structures may be locked, so trying to make or free any allocations, bind buffers or images, map memory, or launch another simultaneous defragmentation in between may cause stall (when done on another thread) or deadlock (when done on the same thread), unless you are 100% sure that defragmented allocations are in different pools.
+
Information returned via pStats and pInfo->pAllocationsChanged are undefined. They become valid after call to vmaDefragmentationEnd().
+
If pInfo->commandBuffer is not null, you must submit that command buffer and make sure it finished execution before calling vmaDefragmentationEnd().
+
+
For more information and important limitations regarding defragmentation, see documentation chapter: Defragmentation.
Contains all the flags from pAllocationCreateInfo->requiredFlags.
+
Matches intended usage.
+
Has as many flags from pAllocationCreateInfo->preferredFlags as possible.
+
+
Returns
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.
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. It is just a convenience function, equivalent to calling:
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. It is just a convenience function, equivalent to calling:
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.
Calls vkFlushMappedMemoryRanges() for memory associated with given ranges of given allocations. For more information, see documentation of vmaFlushAllocation().
+
Parameters
+
+
allocator
+
allocationCount
+
allocations
+
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 ofsets are zero.
+
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.
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.
Returns current information about specified allocation.
+
Current paramteres 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).
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().
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.
Calls vkInvalidateMappedMemoryRanges() for memory associated with given ranges of given allocations. For more information, see documentation of vmaInvalidateAllocation().
+
Parameters
+
+
allocator
+
allocationCount
+
allocations
+
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 ofsets are zero.
+
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.
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().
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.
If the allocation was created with VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT, pUserData 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 pUserData. String passed as pUserData 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 pUserData is freed from memory.
+
If the flag was not used, the value of pointer pUserData is just 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.
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.
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.
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_KHR_DEDICATED_ALLOCATION_BIT
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:
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_BIND_MEMORY2_BIT
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_EXT_MEMORY_BUDGET_BIT
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.
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_BUFFER_DEVICE_ADDRESS_BIT
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:
+
+
(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.
+
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.
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.
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.
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.
Builds and returns a null-terminated string in JSON format with information about given VmaVirtualBlock.
+
Parameters
+
+
virtualBlock
Virtual block.
+
[out]
ppStatsString
Returned string.
+
detailedMap
Pass VK_FALSE to only obtain statistics as returned by vmaCalculateVirtualBlockStats(). Pass VK_TRUE to also obtain full list of allocations and free spaces.
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. 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.
Retrieves information about current memory budget for all memory heaps.
+
Parameters
+
+
allocator
+
[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 vmaCalculateStats().
+
Note that when using allocator from multiple threads, returned information may immediately become outdated.
API elements related to the mechanism of Virtual allocator - using the core allocation algorithm for user-defined purpose without allocating any real GPU memory.
+More...
Calculates and returns statistics about virtual allocations and memory usage in given VmaVirtualBlock. More...
+
+
+
Detailed Description
+
API elements related to the mechanism of Virtual allocator - using the core allocation algorithm for user-defined purpose without allocating any real GPU memory.
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 Linear allocation algorithm.
+
+
VMA_VIRTUAL_BLOCK_CREATE_BUDDY_ALGORITHM_BIT
Enables alternative, buddy allocation algorithm in this virtual block.
+
It operates on a tree of blocks, each having size that is a power of two and a half of its parent's size. Comparing to default algorithm, this one provides faster allocation and deallocation and decreased external fragmentation, at the expense of more memory wasted (internal fragmentation). For details, see documentation chapter Buddy allocation algorithm.
+
+
VMA_VIRTUAL_BLOCK_CREATE_TLSF_ALGORITHM_BIT
Enables alternative, TLSF allocation algorithm in virtual block.
+
This algorithm is based on 2-level lists dividing address space into smaller chunks. The first level is aligned to power of two which serves as buckets for requested memory to fall into, and the second level is lineary subdivided into lists of free memory. This algorithm aims to achieve bounded response time even in the worst case scenario. Allocation time can be sometimes slightly longer than compared to other algorithms but in return the application can avoid stalls in case of fragmentation, giving predictable results, suitable for real-time use cases.
+
+
VMA_VIRTUAL_BLOCK_CREATE_ALGORITHM_MASK
Bit mask to extract only ALGORITHM bits from entire set of flags.
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.
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.
Allocates new virtual allocation inside given VmaVirtualBlock.
+
There is no handle type for a virtual allocation. Virtual allocations within a specific virtual block are uniquely identified by their offsets.
+
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).
+
Parameters
+
+
virtualBlock
Virtual block
+
pCreateInfo
Parameters for the allocation
+
[out]
pAllocation
Returned handle of the new allocation
+
[out]
pOffset
Returned offset of the new allocation. Optional, can be null.
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. Because of this, Vulkan Memory Allocator provides following facilities:
Mapping functions
-
The library provides following functions for mapping of a specific VmaAllocation: 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:
+
The library provides following functions for mapping of a specific VmaAllocation: 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:
// Having these objects initialized:
struct ConstantBuffer
@@ -88,100 +88,100 @@ Mapping functions
// You can map and fill your buffer using following code:
Maps memory represented by given allocation and returns pointer to it.
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.
Persistently mapped memory
-
Kepping 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:
+
Kepping 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:
Pointer to the beginning of this allocation as mapped data.
Definition: vk_mem_alloc.h:1318
There are some exceptions though, when you should consider mapping memory only for a short period of time:
-
When operating system is Windows 7 or 8.x (Windows 10 is not affected because it uses WDDM2), device is discrete AMD GPU, and memory type is the special 256 MiB pool of DEVICE_LOCAL + HOST_VISIBLE memory (selected when you use VMA_MEMORY_USAGE_CPU_TO_GPU), then whenever a memory block allocated from this memory type stays mapped for the time of any call to vkQueueSubmit() or vkQueuePresentKHR(), this block is migrated by WDDM to system RAM, which degrades performance. It doesn't matter if that particular memory block is actually used by the command buffer being submitted.
+
When operating system is Windows 7 or 8.x (Windows 10 is not affected because it uses WDDM2), device is discrete AMD GPU, and memory type is the special 256 MiB pool of DEVICE_LOCAL + HOST_VISIBLE memory (selected when you use VMA_MEMORY_USAGE_CPU_TO_GPU), then whenever a memory block allocated from this memory type stays mapped for the time of any call to vkQueueSubmit() or vkQueuePresentKHR(), this block is migrated by WDDM to system RAM, which degrades performance. It doesn't matter if that particular memory block is actually used by the command buffer being submitted.
Keeping many large memory blocks mapped may impact performance or stability of some debugging tools.
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().
+
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 this platform you may not need to bother.
Finding out if memory is mappable
It may happen that your allocation ends up in memory that is HOST_VISIBLE (available for mapping) despite it wasn't explicitly requested. For example, application may work on integrated graphics with unified memory (like Intel) or allocation from video memory might have failed, so the library chose system memory as fallback.
-
You can detect this case and map such allocation to access its memory on CPU directly, instead of launching a transfer operation. In order to do that: call vmaGetAllocationMemoryProperties() and look for VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT flag.
+
You can detect this case and map such allocation to access its memory on CPU directly, instead of launching a transfer operation. In order to do that: call vmaGetAllocationMemoryProperties() and look for VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT flag.
You can even use VMA_ALLOCATION_CREATE_MAPPED_BIT flag while creating allocations that are not necessarily HOST_VISIBLE (e.g. using VMA_MEMORY_USAGE_GPU_ONLY). If the allocation ends up in memory type that is HOST_VISIBLE, it will be persistently mapped and you can use it directly. If not, the flag is just ignored. Example:
Flags that preferably should be set in a memory type chosen for an allocation.
Definition: vk_mem_alloc.h:1152
+
You can even use VMA_ALLOCATION_CREATE_MAPPED_BIT flag while creating allocations that are not necessarily HOST_VISIBLE (e.g. using VMA_MEMORY_USAGE_GPU_ONLY). If the allocation ends up in memory type that is HOST_VISIBLE, it will be persistently mapped and you can use it directly. If not, the flag is just ignored. Example:
API elements related to the allocation, deallocation, and management of Vulkan memory, buffers, images. Most basic ones being: vmaCreateBuffer(), vmaCreateImage()
API elements related to the mechanism of Virtual allocator - using the core allocation algorithm for user-defined purpose without allocating any real GPU memory
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:
It is recommended to create 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.
+
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. Contrary to Direct3D 12, Vulkan doesn't have a concept of alignment of the entire memory block passed on its allocation.
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 of an allocation, use vmaBindBufferMemory2() / vmaBindImageMemory2().
+
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 of 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.
This library contains functions that return information about its internal state, especially the amount of memory allocated from Vulkan. Please keep in mind that these functions need to traverse all internal data structures to gather these information, so they may be quite time-consuming. Don't call them too often.
Numeric statistics
-
You can query for overall statistics of the allocator using function vmaCalculateStats(). Information are returned using structure VmaStats. It contains VmaStatInfo - number of allocated blocks, number of allocations (occupied ranges in these blocks), number of unused (free) ranges in these blocks, number of bytes used and unused (but still allocated from Vulkan) and other information. They are summed across memory heaps, memory types and total for whole allocator.
-
You can query for statistics of a custom pool using function vmaGetPoolStats(). Information are returned using structure VmaPoolStats.
You can query for overall statistics of the allocator using function vmaCalculateStats(). Information are returned using structure VmaStats. It contains VmaStatInfo - number of allocated blocks, number of allocations (occupied ranges in these blocks), number of unused (free) ranges in these blocks, number of bytes used and unused (but still allocated from Vulkan) and other information. They are summed across memory heaps, memory types and total for whole allocator.
+
You can query for statistics of a custom pool using function vmaGetPoolStats(). Information are returned using structure VmaPoolStats.
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) 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().
+
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) 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 vmaCalculateStats(). 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.
+
The JSON string contains all the data that can be obtained using vmaCalculateStats(). 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.
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 vmaCalculateStats(). vmaGetHeapBudgets() can be called every frame or even before every allocation, while vmaCalculateStats() is intended to be used rarely, only to obtain statistical information, e.g. for debugging purposes.
+
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 vmaCalculateStats(). vmaGetHeapBudgets() can be called every frame or even before every allocation, while vmaCalculateStats() 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:
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!
Make sure to call vmaSetCurrentFrameIndex() every frame. Budget is queried from Vulkan inside of it to avoid overhead of querying it with every allocation.
Make sure to call vmaSetCurrentFrameIndex() every frame. Budget is queried from Vulkan inside of it to avoid overhead of querying it with every allocation.
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. The allocation 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).
-
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.
+
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. The allocation 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).
+
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.
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.
+
The object also remembers its size and some other information. To retrieve this information, use function vmaGetAllocationInfo() and inspect returned structure VmaAllocationInfo.
The documentation for this struct was generated from the following file:
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.
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.
@@ -227,13 +227,13 @@ If pool is not null, this member is ignored.
Same memory object can be shared by multiple allocations.
-
It can change after call to vmaDefragment() if this allocation is passed to the function.
+
It can change after call to vmaDefragment() if this allocation is passed to the function.
@@ -146,8 +146,8 @@ Public Attributes
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 call to vmaDefragment() if this allocation is passed to the function.
+
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 call to vmaDefragment() if this allocation is passed to the function.
@@ -164,8 +164,8 @@ Public Attributes
Pointer to the beginning of this allocation as mapped data.
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.
+
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.
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.
The documentation for this struct was generated from the following file:
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().
+
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.
Optional. Command buffer where GPU copy commands will be posted.
-
If not null, it must be a valid command buffer handle that supports Transfer queue type. It must be in the recording state and outside of a render pass instance. You need to submit it and make sure it finished execution before calling vmaDefragmentationEnd().
+
If not null, it must be a valid command buffer handle that supports Transfer queue type. It must be in the recording state and outside of a render pass instance. You need to submit it and make sure it finished execution before calling vmaDefragmentationEnd().
Passing null means that only CPU defragmentation will be performed.
Either null or pointer to array of pools to be defragmented.
-
All the allocations in the specified pools can be moved during defragmentation and there is no way to check if they were really moved as in pAllocationsChanged, so you must query all the allocations in all these pools for new VkDeviceMemory and offset using vmaGetAllocationInfo() if you might need to recreate buffers and images bound to them.
+
All the allocations in the specified pools can be moved during defragmentation and there is no way to check if they were really moved as in pAllocationsChanged, so you must query all the allocations in all these pools for new VkDeviceMemory and offset using vmaGetAllocationInfo() if you might need to recreate buffers and images bound to them.
The array should have poolCount elements. The array should not contain nulls. Elements in the array should be unique - same pool cannot occur twice.
Using this array is equivalent to specifying all allocations from the pools in pAllocations. It might be more efficient.
VMA_MEMORY_USAGE_CPU_TO_GPU is recommended only for resources that will be mapped and written by the CPU, as well as read directly by the GPU - like some buffers or textures updated every frame (dynamic). If you create a staging copy of a resource to be written by CPU and then used as a source of transfer to another resource placed in the GPU memory, that staging resource should be created with VMA_MEMORY_USAGE_CPU_ONLY. Please read the descriptions of these enums carefully for details.
+
VMA_MEMORY_USAGE_CPU_TO_GPU is recommended only for resources that will be mapped and written by the CPU, as well as read directly by the GPU - like some buffers or textures updated every frame (dynamic). If you create a staging copy of a resource to be written by CPU and then used as a source of transfer to another resource placed in the GPU memory, that staging resource should be created with VMA_MEMORY_USAGE_CPU_ONLY. Please read the descriptions of these enums carefully for details.
Unnecessary use of custom pools
Custom memory pools may be useful for special purposes - when you want to keep certain type of resources separate e.g. to reserve minimum amount of memory for them or limit maximum amount of memory they can occupy. For most resources this is not needed and so it is not recommended to create VmaPool objects and allocations out of them. Allocating from the default pool is sufficient.
@@ -81,23 +81,23 @@ Simple patterns
Render targets
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: Create them in video memory that is fastest to access from GPU using VMA_MEMORY_USAGE_GPU_ONLY.
-
Consider using VK_KHR_dedicated_allocation extension and/or manually 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 e.g. when display resolution changes. Prefer to create such resources first and all other GPU resources (like textures and vertex buffers) later.
+
What to do: Create them in video memory that is fastest to access from GPU using VMA_MEMORY_USAGE_GPU_ONLY.
+
Consider using VK_KHR_dedicated_allocation extension and/or manually 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 e.g. when display resolution changes. Prefer to create such resources first and all other GPU resources (like textures and vertex buffers) later.
Immutable resources
When: Any resources that you fill on CPU only once (aka "immutable") or infrequently and then read frequently on GPU, e.g. textures, vertex and index buffers, constant buffers that don't change often.
-
What to do: Create them in video memory that is fastest to access from GPU using VMA_MEMORY_USAGE_GPU_ONLY.
-
To initialize content of such resource, create a CPU-side (aka "staging") copy of it in system memory - VMA_MEMORY_USAGE_CPU_ONLY, map it, fill it, and submit a transfer from it to the GPU resource. You can keep the staging copy if you need it for another upload transfer in the future. If you don't, you can destroy it or reuse this buffer for uploading different resource after the transfer finishes.
+
What to do: Create them in video memory that is fastest to access from GPU using VMA_MEMORY_USAGE_GPU_ONLY.
+
To initialize content of such resource, create a CPU-side (aka "staging") copy of it in system memory - VMA_MEMORY_USAGE_CPU_ONLY, map it, fill it, and submit a transfer from it to the GPU resource. You can keep the staging copy if you need it for another upload transfer in the future. If you don't, you can destroy it or reuse this buffer for uploading different resource after the transfer finishes.
Prefer to create just buffers in system memory rather than images, even for uploading textures. Use vkCmdCopyBufferToImage(). Dont use images with VK_IMAGE_TILING_LINEAR.
Dynamic resources
When: Any resources that change frequently (aka "dynamic"), e.g. every frame or every draw call, written on CPU, read on GPU.
-
What to do: Create them using VMA_MEMORY_USAGE_CPU_TO_GPU. You can map it and write to it directly on CPU, as well as read from it on GPU.
+
What to do: Create them using VMA_MEMORY_USAGE_CPU_TO_GPU. You can map it and write to it directly on CPU, as well as read from it on GPU.
This is a more complex situation. Different solutions are possible, and the best one depends on specific GPU type, but you can use this simple approach for the start. Prefer to write to such resource sequentially (e.g. using memcpy). Don't perform random access or any reads from it on CPU, as it may be very slow. Also note that textures written directly from the host through a mapped pointer need to be in LINEAR not OPTIMAL layout.
Readback
When: Resources that contain data written by GPU that you want to read back on CPU, e.g. results of some computations.
-
What to do: Create them using VMA_MEMORY_USAGE_GPU_TO_CPU. You can write to them directly on GPU, as well as map and read them on CPU.
+
What to do: Create them using VMA_MEMORY_USAGE_GPU_TO_CPU. You can write to them directly on GPU, as well as map and read them on CPU.
Advanced patterns
@@ -108,9 +108,9 @@ Detecting integrated graphics
Direct access versus transfer
For resources that you frequently write on CPU and read on GPU, many solutions are possible:
Create just a single copy using VMA_MEMORY_USAGE_CPU_TO_GPU, map it and fill it on CPU, read it directly on GPU.
+
Create just a single copy using VMA_MEMORY_USAGE_CPU_ONLY, map it and fill it on CPU, read it directly on GPU.
Which solution is the most efficient depends on your resource and especially on the GPU. It is best to measure it and then make the decision. Some general recommendations:
@@ -120,8 +120,8 @@ Direct access versus transfer
Similarly, for resources that you frequently write on GPU and read on CPU, multiple solutions are possible:
Create just single copy using VMA_MEMORY_USAGE_GPU_TO_CPU, write to it directly on GPU, map it and read it on CPU.
You should take some measurements to decide which option is faster in case of your specific resource.
Note that textures accessed directly from the host through a mapped pointer need to be in LINEAR layout, which may slow down their usage on the device. Textures accessed only by the device and transfer operations can use OPTIMAL layout.
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:
VmaVirtualBlock object contains internal data structure that keeps track of free and occupied regions using the same code as the main Vulkan memory allocator. However, there is no "virtual allocation" object. When you request a new allocation, a VkDeviceSize number is returned. It is an offset inside the block where the allocation has been placed, but it also uniquely identifies the allocation within this block.
-
In order to make an allocation:
+
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 withing the virtual block.
Call vmaVirtualAllocate(). Get new VkDeviceSize offset that identifies the allocation.
+
Call vmaVirtualAllocate(). Get new VmaVirtualAllocation object that represents the allocation. You can also receive VkDeviceSize offset that was assigned to the allocation.
Represents single memory allocation done inside VmaVirtualBlock.
Deallocation
-
When no longer needed, an allocation can be freed by calling vmaVirtualFree(). You can only pass to this function the exact offset that was previously returned by vmaVirtualAllocate() and not any other location within the memory.
-
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:
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:
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:
+
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:
struct CustomAllocData
{
std::string m_AllocName;
};
CustomAllocData* allocData = new CustomAllocData();
Changes custom pointer associated with given virtual allocation.
-
The pointer can later be fetched, along with allocation size, by passing the allocation offset 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:
Changes custom pointer associated with given virtual allocation.
+
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:
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:
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:
You can obtain statistics of a virtual block using vmaCalculateVirtualBlockStats(). The function fills structure VmaStatInfo - same as used by the normal Vulkan memory allocator. Example:
+
You can obtain statistics of a virtual block using vmaCalculateVirtualBlockStats(). The function fills structure VmaStatInfo - same as used by the normal Vulkan memory allocator. Example:
Calculates and returns statistics about virtual allocations and memory usage in given VmaVirtualBlock...
-
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.
Total number of bytes occupied by all allocations.
Definition: vk_mem_alloc.h:1074
+
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.
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.
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.
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).
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 pUserData. The string is automatically freed together with the allocation. It is also used in vmaBuildStatsString().
-
-
VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT
Allocation will be created from upper stack in a double stack pool.
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.
-
-
VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT
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_CAN_ALIAS_BIT
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_STRATEGY_BEST_FIT_BIT
Allocation strategy that chooses smallest possible free range for the allocation.
-
-
VMA_ALLOCATION_CREATE_STRATEGY_WORST_FIT_BIT
Allocation strategy that chooses biggest possible free range for the allocation.
-
-
VMA_ALLOCATION_CREATE_STRATEGY_FIRST_FIT_BIT
Allocation strategy that chooses first suitable free range for the allocation.
-
"First" doesn't necessarily means the one with smallest offset in memory, but rather the one that is easiest and fastest to find.
-
-
VMA_ALLOCATION_CREATE_STRATEGY_MIN_MEMORY_BIT
Allocation strategy that tries to minimize memory usage.
-
-
VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT
Allocation strategy that tries to minimize allocation time.
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_KHR_DEDICATED_ALLOCATION_BIT
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:
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_BIND_MEMORY2_BIT
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_EXT_MEMORY_BUDGET_BIT
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.
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_BUFFER_DEVICE_ADDRESS_BIT
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:
-
-
(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.
-
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.
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.
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.
No intended memory usage specified. Use other members of VmaAllocationCreateInfo to specify your requirements.
-
-
VMA_MEMORY_USAGE_GPU_ONLY
Memory will be used on device only, so fast access from the device is preferred. It usually means device-local GPU (video) memory. No need to be mappable on host. It is roughly equivalent of D3D12_HEAP_TYPE_DEFAULT.
-
Usage:
-
-
Resources written and read by device, e.g. images used as attachments.
-
Resources transferred from host once (immutable) or infrequently and read by device multiple times, e.g. textures to be sampled, vertex buffers, uniform (constant) buffers, and majority of other types of resources used on GPU.
-
-
Allocation may still end up in HOST_VISIBLE memory on some implementations. In such case, you are free to map it. You can use VMA_ALLOCATION_CREATE_MAPPED_BIT with this usage type.
-
-
VMA_MEMORY_USAGE_CPU_ONLY
Memory will be mappable on host. It usually means CPU (system) memory. Guarantees to be HOST_VISIBLE and HOST_COHERENT. CPU access is typically uncached. Writes may be write-combined. Resources created in this pool may still be accessible to the device, but access to them can be slow. It is roughly equivalent of D3D12_HEAP_TYPE_UPLOAD.
-
Usage: Staging copy of resources used as transfer source.
-
-
VMA_MEMORY_USAGE_CPU_TO_GPU
Memory that is both mappable on host (guarantees to be HOST_VISIBLE) and preferably fast to access by GPU. CPU access is typically uncached. Writes may be write-combined.
-
Usage: Resources written frequently by host (dynamic), read by device. E.g. textures (with LINEAR layout), vertex buffers, uniform buffers updated every frame or every draw call.
-
-
VMA_MEMORY_USAGE_GPU_TO_CPU
Memory mappable on host (guarantees to be HOST_VISIBLE) and cached. It is roughly equivalent of D3D12_HEAP_TYPE_READBACK.
-
Usage:
-
-
Resources written by device, read by host - results of some computations, e.g. screen capture, average scene luminance for HDR tone mapping.
-
Any resources read or accessed randomly on host, e.g. CPU-side copy of vertex buffer used as source of transfer, but also used for collision detection.
-
-
-
VMA_MEMORY_USAGE_CPU_COPY
CPU memory - memory that is preferably not DEVICE_LOCAL, but also not guaranteed to be HOST_VISIBLE.
-
Usage: Staging copy of resources moved from GPU memory to CPU memory as part of custom paging/residency mechanism, to be moved back to GPU memory when needed.
-
-
VMA_MEMORY_USAGE_GPU_LAZILY_ALLOCATED
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.
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.
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_LINEAR_ALGORITHM_BIT
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 Linear allocation algorithm.
Enables alternative, buddy allocation algorithm in this pool.
-
It operates on a tree of blocks, each having size that is a power of two and a half of its parent's size. Comparing to default algorithm, this one provides faster allocation and deallocation and decreased external fragmentation, at the expense of more memory wasted (internal fragmentation). For details, see documentation chapter Buddy allocation algorithm.
-
-
VMA_POOL_CREATE_ALGORITHM_MASK
Bit mask to extract only ALGORITHM bits from entire set of flags.
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 Linear allocation algorithm.
-
-
VMA_VIRTUAL_BLOCK_CREATE_BUDDY_ALGORITHM_BIT
Enables alternative, buddy allocation algorithm in this virtual block.
-
It operates on a tree of blocks, each having size that is a power of two and a half of its parent's size. Comparing to default algorithm, this one provides faster allocation and deallocation and decreased external fragmentation, at the expense of more memory wasted (internal fragmentation). For details, see documentation chapter Buddy allocation algorithm.
-
-
VMA_VIRTUAL_BLOCK_CREATE_ALGORITHM_MASK
Bit mask to extract only ALGORITHM bits from entire set of flags.
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.
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.
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.
Builds and returns a null-terminated string in JSON format with information about given VmaVirtualBlock.
-
Parameters
-
-
virtualBlock
Virtual block.
-
[out]
ppStatsString
Returned string.
-
detailedMap
Pass VK_FALSE to only obtain statistics as returned by vmaCalculateVirtualBlockStats(). Pass VK_TRUE to also obtain full list of allocations and free spaces.
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. 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.
Checks magic number in margins around all allocations in given memory types (in both default and custom pools) in search for corruptions.
-
Parameters
-
-
allocator
-
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.
-
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.
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.
-
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.
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.
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().
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.
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.
Deprecated. Compacts memory by moving allocations.
-
Parameters
-
-
allocator
-
pAllocations
Array of allocations that can be moved during this compation.
-
allocationCount
Number of elements in pAllocations and pAllocationsChanged arrays.
-
[out]
pAllocationsChanged
Array of boolean values that will indicate whether matching allocation in pAllocations array has been moved. This parameter is optional. Pass null if you don't need this information.
-
pDefragmentationInfo
Configuration parameters. Optional - pass null to use default values.
-
[out]
pDefragmentationStats
Statistics returned by the function. Optional - pass null if you don't need this information.
-
-
-
-
Returns
VK_SUCCESS if completed, negative error code in case of error.
This function works by moving allocations to different places (different VkDeviceMemory objects and/or different offsets) in order to optimize memory usage. Only allocations that are in pAllocations array can be moved. All other allocations are considered nonmovable in this call. Basic rules:
-
-
Only allocations made in memory types that have VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT and VK_MEMORY_PROPERTY_HOST_COHERENT_BIT flags can be compacted. You may pass other allocations but it makes no sense - these will never be moved.
Both allocations made with or without VMA_ALLOCATION_CREATE_MAPPED_BIT flag can be compacted. If not persistently mapped, memory will be mapped temporarily inside this function if needed.
-
You must not pass same VmaAllocation object multiple times in pAllocations array.
-
-
The function also frees empty VkDeviceMemory blocks.
-
Warning: This function may be time-consuming, so you shouldn't call it too often (like after every resource creation/destruction). You can call it on special occasions (like when reloading a game level or when you just destroyed a lot of objects). Calling it every frame may be OK, but you should measure that on your platform.
VK_SUCCESS and *pContext == null if defragmentation finished within this function call. VK_NOT_READY and *pContext != null if defragmentation has been started and you need to call vmaDefragmentationEnd() to finish it. Negative value in case of error.
-
Use this function instead of old, deprecated vmaDefragment().
You should not use any of allocations passed as pInfo->pAllocations or any allocations that belong to pools passed as pInfo->pPools, including calling vmaGetAllocationInfo(), or access their data.
-
Some mutexes protecting internal data structures may be locked, so trying to make or free any allocations, bind buffers or images, map memory, or launch another simultaneous defragmentation in between may cause stall (when done on another thread) or deadlock (when done on the same thread), unless you are 100% sure that defragmented allocations are in different pools.
-
Information returned via pStats and pInfo->pAllocationsChanged are undefined. They become valid after call to vmaDefragmentationEnd().
-
If pInfo->commandBuffer is not null, you must submit that command buffer and make sure it finished execution before calling vmaDefragmentationEnd().
-
-
For more information and important limitations regarding defragmentation, see documentation chapter: Defragmentation.
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.
Contains all the flags from pAllocationCreateInfo->requiredFlags.
-
Matches intended usage.
-
Has as many flags from pAllocationCreateInfo->preferredFlags as possible.
-
-
Returns
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.
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. It is just a convenience function, equivalent to calling:
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. It is just a convenience function, equivalent to calling:
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.
Calls vkFlushMappedMemoryRanges() for memory associated with given ranges of given allocations. For more information, see documentation of vmaFlushAllocation().
-
Parameters
-
-
allocator
-
allocationCount
-
allocations
-
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 ofsets are zero.
-
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.
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.
Returns current information about specified allocation.
-
Current paramteres 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).
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.
Retrieves information about current memory budget for all memory heaps.
-
Parameters
-
-
allocator
-
[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 vmaCalculateStats().
-
Note that when using allocator from multiple threads, returned information may immediately become outdated.
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().
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.
Calls vkInvalidateMappedMemoryRanges() for memory associated with given ranges of given allocations. For more information, see documentation of vmaInvalidateAllocation().
-
Parameters
-
-
allocator
-
allocationCount
-
allocations
-
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 ofsets are zero.
-
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.
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().
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.
If the allocation was created with VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT, pUserData 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 pUserData. String passed as pUserData 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 pUserData is freed from memory.
-
If the flag was not used, the value of pointer pUserData is just 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.
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.
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.
Allocates new virtual allocation inside given VmaVirtualBlock.
-
There is no handle type for a virtual allocation. Virtual allocations within a specific virtual block are uniquely identified by their offsets.
-
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).
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.
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:
Enables usage of VK_KHR_dedicated_allocation extension.
Definition: vk_mem_alloc.h:350
+
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:
diff --git a/include/vk_mem_alloc.h b/include/vk_mem_alloc.h
index ac1701a..3d6e42d 100644
--- a/include/vk_mem_alloc.h
+++ b/include/vk_mem_alloc.h
@@ -30,7 +30,7 @@
Copyright (c) 2017-2021 Advanced Micro Devices, Inc. All rights reserved. \n
License: MIT
-Documentation of all members: vk_mem_alloc.h
+API documentation divided into groups: [Modules](modules.html)
\section main_table_of_contents Table of contents
@@ -101,8 +101,27 @@ Documentation of all members: vk_mem_alloc.h
- [Product page on GPUOpen](https://gpuopen.com/gaming-product/vulkan-memory-allocator/)
- [Source repository on GitHub](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator)
+
+\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.
*/
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -291,6 +310,11 @@ extern "C" {
// 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
{
@@ -409,6 +433,14 @@ typedef enum VmaAllocatorCreateFlagBits
} VmaAllocatorCreateFlagBits;
typedef VkFlags VmaAllocatorCreateFlags;
+/** @} */
+
+/**
+\addtogroup group_alloc
+@{
+*/
+
+/// \brief Intended usage of the allocated memory.
typedef enum VmaMemoryUsage
{
/** No intended memory usage specified.
@@ -622,11 +654,24 @@ typedef enum VmaPoolCreateFlagBits
*/
VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT = 0x00000008,
+ /** \brief Enables alternative, Two-Level Segregated Fit (TLSF) allocation algorithm in this pool.
+
+ This algorithm is based on 2-level lists dividing address space into smaller
+ chunks. The first level is aligned to power of two which serves as buckets for requested
+ memory to fall into, and the second level is lineary subdivided into lists of free memory.
+ This algorithm aims to achieve bounded response time even in the worst case scenario.
+ Allocation time can be sometimes slightly longer than compared to other algorithms
+ but in return the application can avoid stalls in case of fragmentation, giving
+ predictable results, suitable for real-time use cases.
+ */
+ VMA_POOL_CREATE_TLSF_ALGORITHM_BIT = 0x00000010,
+
/** 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_BUDDY_ALGORITHM_BIT,
+ VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT |
+ VMA_POOL_CREATE_TLSF_ALGORITHM_BIT,
VMA_POOL_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VmaPoolCreateFlagBits;
@@ -641,6 +686,13 @@ typedef enum VmaDefragmentationFlagBits
} VmaDefragmentationFlagBits;
typedef VkFlags VmaDefragmentationFlags;
+/** @} */
+
+/**
+\addtogroup group_virtual
+@{
+*/
+
/// Flags to be passed as VmaVirtualBlockCreateInfo::flags.
typedef enum VmaVirtualBlockCreateFlagBits
{
@@ -667,11 +719,24 @@ typedef enum VmaVirtualBlockCreateFlagBits
*/
VMA_VIRTUAL_BLOCK_CREATE_BUDDY_ALGORITHM_BIT = 0x00000002,
+ /** \brief Enables alternative, TLSF allocation algorithm in virtual block.
+
+ This algorithm is based on 2-level lists dividing address space into smaller
+ chunks. The first level is aligned to power of two which serves as buckets for requested
+ memory to fall into, and the second level is lineary subdivided into lists of free memory.
+ This algorithm aims to achieve bounded response time even in the worst case scenario.
+ Allocation time can be sometimes slightly longer than compared to other algorithms
+ but in return the application can avoid stalls in case of fragmentation, giving
+ predictable results, suitable for real-time use cases.
+ */
+ VMA_VIRTUAL_BLOCK_CREATE_TLSF_ALGORITHM_BIT = 0x00000004,
+
/** \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_BUDDY_ALGORITHM_BIT,
+ VMA_VIRTUAL_BLOCK_CREATE_BUDDY_ALGORITHM_BIT |
+ VMA_VIRTUAL_BLOCK_CREATE_TLSF_ALGORITHM_BIT,
VMA_VIRTUAL_BLOCK_CREATE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VmaVirtualBlockCreateFlagBits;
@@ -706,10 +771,16 @@ typedef enum 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.
@@ -721,6 +792,13 @@ right after Vulkan is initialized and keep it alive until before Vulkan device i
*/
VK_DEFINE_HANDLE(VmaAllocator)
+/** @} */
+
+/**
+\addtogroup group_alloc
+@{
+*/
+
/** \struct VmaPool
\brief Represents custom memory pool
@@ -762,6 +840,27 @@ Call function vmaDefragmentationEnd() 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.
+*/
+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.
@@ -772,6 +871,13 @@ This object is not thread-safe - should not be used from multiple threads simult
*/
VK_DEFINE_HANDLE(VmaVirtualBlock)
+/** @} */
+
+/**
+\addtogroup group_init
+@{
+*/
+
/// Callback function called after successful vkAllocateMemory.
typedef void (VKAPI_PTR* PFN_vmaAllocateDeviceMemoryFunction)(
VmaAllocator VMA_NOT_NULL allocator,
@@ -948,6 +1054,13 @@ typedef struct VmaAllocatorInfo
VkDevice VMA_NOT_NULL device;
} VmaAllocatorInfo;
+/** @} */
+
+/**
+\addtogroup group_stats
+@{
+*/
+
/// Calculated statistics of memory usage in entire allocator.
typedef struct VmaStatInfo
{
@@ -1010,6 +1123,13 @@ typedef struct VmaBudget
VkDeviceSize budget;
} VmaBudget;
+/** @} */
+
+/**
+\addtogroup group_alloc
+@{
+*/
+
typedef struct VmaAllocationCreateInfo
{
/// Use #VmaAllocationCreateFlagBits enum.
@@ -1117,6 +1237,13 @@ typedef struct VmaPoolCreateInfo
void* VMA_NULLABLE pMemoryAllocateNext;
} VmaPoolCreateInfo;
+/** @} */
+
+/**
+\addtogroup group_stats
+@{
+*/
+
/// Describes parameter of existing #VmaPool.
typedef struct VmaPoolStats
{
@@ -1137,6 +1264,13 @@ typedef struct VmaPoolStats
size_t blockCount;
} VmaPoolStats;
+/** @} */
+
+/**
+\addtogroup group_alloc
+@{
+*/
+
/// Parameters of #VmaAllocation objects, that can be retrieved using function vmaGetAllocationInfo().
typedef struct VmaAllocationInfo
{
@@ -1313,6 +1447,13 @@ typedef struct VmaDefragmentationStats
uint32_t deviceMemoryBlocksFreed;
} VmaDefragmentationStats;
+/** @} */
+
+/**
+\addtogroup group_virtual
+@{
+*/
+
/// Parameters of created #VmaVirtualBlock object to be passed to vmaCreateVirtualBlock().
typedef struct VmaVirtualBlockCreateInfo
{
@@ -1360,6 +1501,11 @@ typedef struct 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.
@@ -1372,11 +1518,18 @@ typedef struct VmaVirtualAllocationInfo
void* VMA_NULLABLE pUserData;
} VmaVirtualAllocationInfo;
+/** @} */
+
#endif // _VMA_DATA_TYPES_DECLARATIONS
#ifndef _VMA_FUNCTION_HEADERS
-/// Creates Allocator object.
+/**
+\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);
@@ -1427,6 +1580,13 @@ 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
@@ -1455,6 +1615,13 @@ VMA_CALL_PRE void VMA_CALL_POST vmaGetHeapBudgets(
VmaAllocator VMA_NOT_NULL allocator,
VmaBudget* VMA_NOT_NULL pBudgets);
+/** @} */
+
+/**
+\addtogroup group_alloc
+@{
+*/
+
/**
\brief Helps to find memoryTypeIndex, given memoryTypeBits and VmaAllocationCreateInfo.
@@ -1530,6 +1697,13 @@ 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.
@@ -1541,6 +1715,13 @@ VMA_CALL_PRE void VMA_CALL_POST vmaGetPoolStats(
VmaPool VMA_NOT_NULL pool,
VmaPoolStats* 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,
@@ -2162,6 +2343,13 @@ VMA_CALL_PRE void VMA_CALL_POST vmaDestroyImage(
VkImage VMA_NULLABLE_NON_DISPATCHABLE image,
VmaAllocation VMA_NULLABLE allocation);
+/** @} */
+
+/**
+\addtogroup group_virtual
+@{
+*/
+
/** \brief Creates new #VmaVirtualBlock object.
\param pCreateInfo Parameters for creation.
@@ -2192,7 +2380,7 @@ VMA_CALL_PRE VkBool32 VMA_CALL_POST vmaIsVirtualBlockEmpty(
*/
VMA_CALL_PRE void VMA_CALL_POST vmaGetVirtualAllocationInfo(
VmaVirtualBlock VMA_NOT_NULL virtualBlock,
- VkDeviceSize offset, VmaVirtualAllocationInfo* VMA_NOT_NULL pVirtualAllocInfo);
+ VmaVirtualAllocation VMA_NOT_NULL allocation, VmaVirtualAllocationInfo* VMA_NOT_NULL pVirtualAllocInfo);
/** \brief Allocates new virtual allocation inside given #VmaVirtualBlock.
@@ -2201,17 +2389,23 @@ Virtual allocations within a specific virtual block are uniquely identified by t
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).
+
+\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,
- VkDeviceSize* VMA_NOT_NULL pOffset);
+ VmaVirtualAllocation* VMA_NOT_NULL pAllocation,
+ VkDeviceSize* VMA_NULLABLE pOffset);
/** \brief Frees virtual allocation inside given #VmaVirtualBlock.
*/
VMA_CALL_PRE void VMA_CALL_POST vmaVirtualFree(
VmaVirtualBlock VMA_NOT_NULL virtualBlock,
- VkDeviceSize offset);
+ VmaVirtualAllocation VMA_NULLABLE allocation);
/** \brief Frees all virtual allocations inside given #VmaVirtualBlock.
@@ -2228,7 +2422,7 @@ VMA_CALL_PRE void VMA_CALL_POST vmaClearVirtualBlock(
*/
VMA_CALL_PRE void VMA_CALL_POST vmaSetVirtualAllocationUserData(
VmaVirtualBlock VMA_NOT_NULL virtualBlock,
- VkDeviceSize offset,
+ VmaVirtualAllocation VMA_NOT_NULL allocation,
void* VMA_NULLABLE pUserData);
/** \brief Calculates and returns statistics about virtual allocations and memory usage in given #VmaVirtualBlock.
@@ -2237,6 +2431,13 @@ VMA_CALL_PRE void VMA_CALL_POST vmaCalculateVirtualBlockStats(
VmaVirtualBlock VMA_NOT_NULL virtualBlock,
VmaStatInfo* VMA_NOT_NULL pStatInfo);
+/** @} */
+
+/**
+\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.
@@ -2270,6 +2471,8 @@ VMA_CALL_PRE void VMA_CALL_POST vmaFreeStatsString(
char* VMA_NULLABLE pStatsString);
#endif // VMA_STATS_STRING_ENABLED
+/** @} */
+
#endif // _VMA_FUNCTION_HEADERS
#ifdef __cplusplus
@@ -2469,6 +2672,16 @@ static void vma_aligned_free(void* VMA_NULLABLE ptr)
#endif
#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
@@ -2740,6 +2953,7 @@ enum VMA_CACHE_OPERATION
enum class VmaAllocationRequestType
{
Normal,
+ TLSF,
// Used by "Linear" algorithm.
UpperAddress,
EndOf1st,
@@ -2749,6 +2963,9 @@ enum class VmaAllocationRequestType
#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;
@@ -2814,6 +3031,7 @@ class VmaBlockMetadata;
class VmaBlockMetadata_Generic;
class VmaBlockMetadata_Linear;
class VmaBlockMetadata_Buddy;
+class VmaBlockMetadata_TLSF;
class VmaBlockVector;
@@ -2846,6 +3064,74 @@ static inline uint32_t VmaCountBitsSet(uint32_t v)
return c;
}
+static inline uint8_t VmaBitScanLSB(uint64_t mask)
+{
+#ifdef _MSC_VER
+ DWORD pos;
+ if (_BitScanForward64(&pos, mask))
+ return static_cast(pos);
+#else
+ uint8_t pos = 0;
+ do
+ {
+ if (mask & (1ULL << pos))
+ return pos;
+ } while (pos++ < 63);
+#endif
+ return UINT8_MAX;
+}
+
+static inline uint8_t VmaBitScanLSB(uint32_t mask)
+{
+#ifdef _MSC_VER
+ DWORD pos;
+ if (_BitScanForward(&pos, mask))
+ return static_cast(pos);
+#else
+ uint8_t pos = 0;
+ do
+ {
+ if (mask & (1UL << pos))
+ return pos;
+ } while (pos++ < 31);
+#endif
+ return UINT8_MAX;
+}
+
+static inline uint8_t VmaBitScanMSB(uint64_t mask)
+{
+#ifdef _MSC_VER
+ DWORD pos;
+ if (_BitScanReverse64(&pos, mask))
+ return static_cast(pos);
+#else
+ uint8_t pos = 63;
+ do
+ {
+ if (mask & (1ULL << pos))
+ return pos;
+ } while (pos-- > 0);
+#endif
+ return UINT8_MAX;
+}
+
+static inline uint8_t VmaBitScanMSB(uint32_t mask)
+{
+#ifdef _MSC_VER
+ DWORD pos;
+ if (_BitScanReverse(&pos, mask))
+ return static_cast(pos);
+#else
+ uint8_t pos = 31;
+ do
+ {
+ if (mask & (1UL << pos))
+ return pos;
+ } 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.
@@ -2882,6 +3168,13 @@ 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)
{
@@ -2946,6 +3239,8 @@ static const char* VmaAlgorithmToStr(uint32_t algorithm)
return "Linear";
case VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT:
return "Buddy";
+ case VMA_POOL_CREATE_TLSF_ALGORITHM_BIT:
+ return "TLSF";
case 0:
return "Default";
default:
@@ -5249,7 +5544,8 @@ public:
VkDeviceMemory newMemory,
VkDeviceSize newSize,
uint32_t id,
- uint32_t algorithm);
+ uint32_t algorithm,
+ VkDeviceSize bufferImageGranularity);
// Always call before destruction.
void Destroy(VmaAllocator allocator);
@@ -5323,7 +5619,7 @@ public:
void InitBlockAllocation(
VmaDeviceMemoryBlock* block,
- VkDeviceSize offset,
+ VmaAllocHandle allocHandle,
VkDeviceSize alignment,
VkDeviceSize size,
uint32_t memoryTypeIndex,
@@ -5350,8 +5646,9 @@ public:
bool IsPersistentMap() const { return (m_MapCount & MAP_COUNT_FLAG_PERSISTENT_MAP) != 0; }
void SetUserData(VmaAllocator hAllocator, void* pUserData);
- void ChangeBlockAllocation(VmaAllocator hAllocator, VmaDeviceMemoryBlock* block, VkDeviceSize offset);
- void ChangeOffset(VkDeviceSize newOffset);
+ void ChangeBlockAllocation(VmaAllocator hAllocator, VmaDeviceMemoryBlock* block, VmaAllocHandle allocHandle);
+ void ChangeAllocHandle(VmaAllocHandle newAllocHandle);
+ VmaAllocHandle GetAllocHandle() const;
VkDeviceSize GetOffset() const;
VmaPool GetParentPool() const;
VkDeviceMemory GetMemory() const;
@@ -5376,7 +5673,7 @@ private:
struct BlockAllocation
{
VmaDeviceMemoryBlock* m_Block;
- VkDeviceSize m_Offset;
+ VmaAllocHandle m_AllocHandle;
};
// Allocation for an object that has its own private VkDeviceMemory.
struct DedicatedAllocation
@@ -5618,10 +5915,11 @@ item points to a FREE suballocation.
*/
struct VmaAllocationRequest
{
- VkDeviceSize offset;
+ VmaAllocHandle allocHandle;
VkDeviceSize size;
VmaSuballocationList::iterator item;
void* customData;
+ uint64_t algorithmData;
VmaAllocationRequestType type;
};
#endif // _VMA_ALLOCATION_REQUEST
@@ -5635,7 +5933,8 @@ class VmaBlockMetadata
{
public:
// pAllocationCallbacks, if not null, must be owned externally - alive and unchanged for the whole lifetime of this object.
- VmaBlockMetadata(const VkAllocationCallbacks* pAllocationCallbacks, bool isVirtual);
+ VmaBlockMetadata(const VkAllocationCallbacks* pAllocationCallbacks,
+ VkDeviceSize bufferImageGranularity, bool isVirtual);
virtual ~VmaBlockMetadata() = default;
virtual void Init(VkDeviceSize size) { m_Size = size; }
@@ -5648,7 +5947,8 @@ public:
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(VkDeviceSize offset, VmaVirtualAllocationInfo& outInfo) = 0;
+ virtual void GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) = 0;
+ virtual VkDeviceSize GetAllocationOffset(VmaAllocHandle allocHandle) const = 0;
// Must set blockCount to 1.
virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const = 0;
@@ -5663,7 +5963,6 @@ public:
// If succeeded, fills pAllocationRequest and returns true.
// If failed, returns false.
virtual bool CreateAllocationRequest(
- VkDeviceSize bufferImageGranularity,
VkDeviceSize allocSize,
VkDeviceSize allocAlignment,
bool upperAddress,
@@ -5681,16 +5980,17 @@ public:
void* userData) = 0;
// Frees suballocation assigned to given memory region.
- virtual void FreeAtOffset(VkDeviceSize offset) = 0;
+ 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(VkDeviceSize offset, void* userData) = 0;
+ virtual void SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) = 0;
protected:
const VkAllocationCallbacks* GetAllocationCallbacks() const { return m_pAllocationCallbacks; }
+ VkDeviceSize GetBufferImageGranularity() const { return m_BufferImageGranularity; }
VkDeviceSize GetDebugMargin() const { return IsVirtual() ? 0 : VMA_DEBUG_MARGIN; }
#if VMA_STATS_STRING_ENABLED
@@ -5709,13 +6009,16 @@ protected:
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, bool isVirtual)
+VmaBlockMetadata::VmaBlockMetadata(const VkAllocationCallbacks* pAllocationCallbacks,
+ VkDeviceSize bufferImageGranularity, bool isVirtual)
: m_Size(0),
m_pAllocationCallbacks(pAllocationCallbacks),
+ m_BufferImageGranularity(bufferImageGranularity),
m_IsVirtual(isVirtual) {}
#if VMA_STATS_STRING_ENABLED
@@ -5798,6 +6101,241 @@ void VmaBlockMetadata::PrintDetailedMap_End(class VmaJsonWriter& json) const
#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_IMAGE_BUFFER_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 IsConflict(VkDeviceSize allocSize,
+ VkDeviceSize allocOffset,
+ VkDeviceSize blockSize,
+ VkDeviceSize blockOffset,
+ 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_IMAGE_BUFFER_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 PageToIndex(offset & ~(m_BufferImageGranularity - 1)); }
+ uint32_t GetEndPage(VkDeviceSize offset, VkDeviceSize size) const { return PageToIndex((offset + size - 1) & ~(m_BufferImageGranularity - 1)); }
+
+ uint32_t PageToIndex(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_IMAGE_BUFFER_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::IsConflict(VkDeviceSize allocSize,
+ VkDeviceSize allocOffset,
+ VkDeviceSize blockSize,
+ VkDeviceSize blockOffset,
+ VmaSuballocationType allocType) const
+{
+ if (IsEnabled())
+ {
+ uint32_t startPage = GetStartPage(allocOffset);
+ if (m_RegionInfo[startPage].allocCount > 0 &&
+ VmaIsBufferImageGranularityConflict(static_cast(m_RegionInfo[startPage].allocType), allocType))
+ {
+ allocOffset = VmaAlignUp(allocOffset, m_BufferImageGranularity);
+ if (blockSize < allocSize + allocOffset - blockOffset)
+ return true;
+ ++startPage;
+ }
+ uint32_t endPage = GetEndPage(allocOffset, 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::PageToIndex(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 overriden 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_GENERIC
class VmaBlockMetadata_Generic : public VmaBlockMetadata
{
@@ -5805,43 +6343,44 @@ class VmaBlockMetadata_Generic : public VmaBlockMetadata
friend class VmaDefragmentationAlgorithm_Fast;
VMA_CLASS_NO_COPY(VmaBlockMetadata_Generic)
public:
- VmaBlockMetadata_Generic(const VkAllocationCallbacks* pAllocationCallbacks, bool isVirtual);
+ VmaBlockMetadata_Generic(const VkAllocationCallbacks* pAllocationCallbacks,
+ VkDeviceSize bufferImageGranularity, bool isVirtual);
virtual ~VmaBlockMetadata_Generic() = default;
- virtual size_t GetAllocationCount() const { return m_Suballocations.size() - m_FreeCount; }
- virtual VkDeviceSize GetSumFreeSize() const { return m_SumFreeSize; }
- virtual bool IsEmpty() const { return (m_Suballocations.size() == 1) && (m_FreeCount == 1); }
- virtual void FreeAtOffset(VkDeviceSize offset) { FreeSuballocation(FindAtOffset(offset)); }
+ size_t GetAllocationCount() const override { return m_Suballocations.size() - m_FreeCount; }
+ VkDeviceSize GetSumFreeSize() const override { return m_SumFreeSize; }
+ bool IsEmpty() const override { return (m_Suballocations.size() == 1) && (m_FreeCount == 1); }
+ void Free(VmaAllocHandle allocHandle) override { FreeSuballocation(FindAtOffset((VkDeviceSize)allocHandle)); }
+ VkDeviceSize GetAllocationOffset(VmaAllocHandle allocHandle) const override { return (VkDeviceSize)allocHandle; };
- virtual void Init(VkDeviceSize size);
- virtual bool Validate() const;
+ void Init(VkDeviceSize size) override;
+ bool Validate() const override;
- virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const;
- virtual void AddPoolStats(VmaPoolStats& inoutStats) const;
+ void CalcAllocationStatInfo(VmaStatInfo& outInfo) const override;
+ void AddPoolStats(VmaPoolStats& inoutStats) const override;
#if VMA_STATS_STRING_ENABLED
- virtual void PrintDetailedMap(class VmaJsonWriter& json) const;
+ void PrintDetailedMap(class VmaJsonWriter& json) const override;
#endif
- virtual bool CreateAllocationRequest(
- VkDeviceSize bufferImageGranularity,
+ bool CreateAllocationRequest(
VkDeviceSize allocSize,
VkDeviceSize allocAlignment,
bool upperAddress,
VmaSuballocationType allocType,
uint32_t strategy,
- VmaAllocationRequest* pAllocationRequest);
+ VmaAllocationRequest* pAllocationRequest) override;
- virtual VkResult CheckCorruption(const void* pBlockData);
+ VkResult CheckCorruption(const void* pBlockData) override;
- virtual void Alloc(
+ void Alloc(
const VmaAllocationRequest& request,
VmaSuballocationType type,
- void* userData);
+ void* userData) override;
- virtual void GetAllocationInfo(VkDeviceSize offset, VmaVirtualAllocationInfo& outInfo);
- virtual void Clear();
- virtual void SetAllocationUserData(VkDeviceSize offset, void* userData);
+ void GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) override;
+ void Clear() override;
+ void SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) override;
// For defragmentation
bool IsBufferImageGranularityConflictPossible(
@@ -5863,12 +6402,11 @@ private:
// Checks if requested suballocation with given parameters can be placed in given pFreeSuballocItem.
// If yes, fills pOffset and returns true. If no, returns false.
bool CheckAllocation(
- VkDeviceSize bufferImageGranularity,
VkDeviceSize allocSize,
VkDeviceSize allocAlignment,
VmaSuballocationType allocType,
VmaSuballocationList::const_iterator suballocItem,
- VkDeviceSize* pOffset) const;
+ VmaAllocHandle* pAllocHandle) const;
// Given free suballocation, it merges it with following one, which must also be free.
void MergeFreeWithNext(VmaSuballocationList::iterator item);
@@ -5885,8 +6423,9 @@ private:
};
#ifndef _VMA_BLOCK_METADATA_GENERIC_FUNCTIONS
-VmaBlockMetadata_Generic::VmaBlockMetadata_Generic(const VkAllocationCallbacks* pAllocationCallbacks, bool isVirtual)
- : VmaBlockMetadata(pAllocationCallbacks, isVirtual),
+VmaBlockMetadata_Generic::VmaBlockMetadata_Generic(const VkAllocationCallbacks* pAllocationCallbacks,
+ VkDeviceSize bufferImageGranularity, bool isVirtual)
+ : VmaBlockMetadata(pAllocationCallbacks, bufferImageGranularity, isVirtual),
m_FreeCount(0),
m_SumFreeSize(0),
m_Suballocations(VmaStlAllocator(pAllocationCallbacks)),
@@ -5954,7 +6493,7 @@ bool VmaBlockMetadata_Generic::Validate() const
{
if (!IsVirtual())
{
- VMA_VALIDATE(alloc->GetOffset() == subAlloc.offset);
+ VMA_VALIDATE((VkDeviceSize)alloc->GetAllocHandle() == subAlloc.offset);
VMA_VALIDATE(alloc->GetSize() == subAlloc.size);
}
@@ -6046,7 +6585,6 @@ void VmaBlockMetadata_Generic::PrintDetailedMap(class VmaJsonWriter& json) const
#endif // VMA_STATS_STRING_ENABLED
bool VmaBlockMetadata_Generic::CreateAllocationRequest(
- VkDeviceSize bufferImageGranularity,
VkDeviceSize allocSize,
VkDeviceSize allocAlignment,
bool upperAddress,
@@ -6089,12 +6627,11 @@ bool VmaBlockMetadata_Generic::CreateAllocationRequest(
for (; index < freeSuballocCount; ++index)
{
if (CheckAllocation(
- bufferImageGranularity,
allocSize,
allocAlignment,
allocType,
m_FreeSuballocationsBySize[index],
- &pAllocationRequest->offset))
+ &pAllocationRequest->allocHandle))
{
pAllocationRequest->item = m_FreeSuballocationsBySize[index];
return true;
@@ -6108,12 +6645,11 @@ bool VmaBlockMetadata_Generic::CreateAllocationRequest(
++it)
{
if (it->type == VMA_SUBALLOCATION_TYPE_FREE && CheckAllocation(
- bufferImageGranularity,
allocSize,
allocAlignment,
allocType,
it,
- &pAllocationRequest->offset))
+ &pAllocationRequest->allocHandle))
{
pAllocationRequest->item = it;
return true;
@@ -6126,12 +6662,11 @@ bool VmaBlockMetadata_Generic::CreateAllocationRequest(
for (size_t index = freeSuballocCount; index--; )
{
if (CheckAllocation(
- bufferImageGranularity,
allocSize,
allocAlignment,
allocType,
m_FreeSuballocationsBySize[index],
- &pAllocationRequest->offset))
+ &pAllocationRequest->allocHandle))
{
pAllocationRequest->item = m_FreeSuballocationsBySize[index];
return true;
@@ -6175,9 +6710,10 @@ void VmaBlockMetadata_Generic::Alloc(
VmaSuballocation& suballoc = *request.item;
// Given suballocation is a free block.
VMA_ASSERT(suballoc.type == VMA_SUBALLOCATION_TYPE_FREE);
+
// Given offset is inside this suballocation.
- VMA_ASSERT(request.offset >= suballoc.offset);
- const VkDeviceSize paddingBegin = request.offset - suballoc.offset;
+ VMA_ASSERT((VkDeviceSize)request.allocHandle >= suballoc.offset);
+ const VkDeviceSize paddingBegin = (VkDeviceSize)request.allocHandle - suballoc.offset;
VMA_ASSERT(suballoc.size >= paddingBegin + request.size);
const VkDeviceSize paddingEnd = suballoc.size - paddingBegin - request.size;
@@ -6185,7 +6721,7 @@ void VmaBlockMetadata_Generic::Alloc(
// it to become used.
UnregisterFreeSuballocation(request.item);
- suballoc.offset = request.offset;
+ suballoc.offset = (VkDeviceSize)request.allocHandle;
suballoc.size = request.size;
suballoc.type = type;
suballoc.userData = userData;
@@ -6194,7 +6730,7 @@ void VmaBlockMetadata_Generic::Alloc(
if (paddingEnd)
{
VmaSuballocation paddingSuballoc = {};
- paddingSuballoc.offset = request.offset + request.size;
+ paddingSuballoc.offset = suballoc.offset + suballoc.size;
paddingSuballoc.size = paddingEnd;
paddingSuballoc.type = VMA_SUBALLOCATION_TYPE_FREE;
VmaSuballocationList::iterator next = request.item;
@@ -6208,7 +6744,7 @@ void VmaBlockMetadata_Generic::Alloc(
if (paddingBegin)
{
VmaSuballocation paddingSuballoc = {};
- paddingSuballoc.offset = request.offset - paddingBegin;
+ paddingSuballoc.offset = suballoc.offset - paddingBegin;
paddingSuballoc.size = paddingBegin;
paddingSuballoc.type = VMA_SUBALLOCATION_TYPE_FREE;
const VmaSuballocationList::iterator paddingBeginItem =
@@ -6229,9 +6765,10 @@ void VmaBlockMetadata_Generic::Alloc(
m_SumFreeSize -= request.size;
}
-void VmaBlockMetadata_Generic::GetAllocationInfo(VkDeviceSize offset, VmaVirtualAllocationInfo& outInfo)
+void VmaBlockMetadata_Generic::GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo)
{
- const VmaSuballocation& suballoc = *FindAtOffset(offset);
+ const VmaSuballocation& suballoc = *FindAtOffset((VkDeviceSize)allocHandle);
+ outInfo.offset = (VkDeviceSize)allocHandle;
outInfo.size = suballoc.size;
outInfo.pUserData = suballoc.userData;
}
@@ -6255,9 +6792,9 @@ void VmaBlockMetadata_Generic::Clear()
m_FreeSuballocationsBySize.push_back(m_Suballocations.begin());
}
-void VmaBlockMetadata_Generic::SetAllocationUserData(VkDeviceSize offset, void* userData)
+void VmaBlockMetadata_Generic::SetAllocationUserData(VmaAllocHandle allocHandle, void* userData)
{
- VmaSuballocation& suballoc = *FindAtOffset(offset);
+ VmaSuballocation& suballoc = *FindAtOffset((VkDeviceSize)allocHandle);
suballoc.userData = userData;
}
@@ -6309,19 +6846,19 @@ bool VmaBlockMetadata_Generic::ValidateFreeSuballocationList() const
}
bool VmaBlockMetadata_Generic::CheckAllocation(
- VkDeviceSize bufferImageGranularity,
VkDeviceSize allocSize,
VkDeviceSize allocAlignment,
VmaSuballocationType allocType,
VmaSuballocationList::const_iterator suballocItem,
- VkDeviceSize* pOffset) const
+ VmaAllocHandle* pAllocHandle) const
{
VMA_ASSERT(allocSize > 0);
VMA_ASSERT(allocType != VMA_SUBALLOCATION_TYPE_FREE);
VMA_ASSERT(suballocItem != m_Suballocations.cend());
- VMA_ASSERT(pOffset != VMA_NULL);
+ VMA_ASSERT(pAllocHandle != VMA_NULL);
const VkDeviceSize debugMargin = GetDebugMargin();
+ const VkDeviceSize bufferImageGranularity = GetBufferImageGranularity();
const VmaSuballocation& suballoc = *suballocItem;
VMA_ASSERT(suballoc.type == VMA_SUBALLOCATION_TYPE_FREE);
@@ -6333,16 +6870,16 @@ bool VmaBlockMetadata_Generic::CheckAllocation(
}
// Start from offset equal to beginning of this suballocation.
- *pOffset = suballoc.offset;
+ VkDeviceSize offset = suballoc.offset;
// Apply debugMargin at the beginning.
if (debugMargin > 0)
{
- *pOffset += debugMargin;
+ offset += debugMargin;
}
// Apply alignment.
- *pOffset = VmaAlignUp(*pOffset, allocAlignment);
+ offset = VmaAlignUp(offset, allocAlignment);
// Check previous suballocations for BufferImageGranularity conflicts.
// Make bigger alignment if necessary.
@@ -6354,7 +6891,7 @@ bool VmaBlockMetadata_Generic::CheckAllocation(
{
--prevSuballocItem;
const VmaSuballocation& prevSuballoc = *prevSuballocItem;
- if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, *pOffset, bufferImageGranularity))
+ if (VmaBlocksOnSamePage(prevSuballoc.offset, prevSuballoc.size, offset, bufferImageGranularity))
{
if (VmaIsBufferImageGranularityConflict(prevSuballoc.type, allocType))
{
@@ -6368,12 +6905,12 @@ bool VmaBlockMetadata_Generic::CheckAllocation(
}
if (bufferImageGranularityConflict)
{
- *pOffset = VmaAlignUp(*pOffset, bufferImageGranularity);
+ offset = VmaAlignUp(offset, bufferImageGranularity);
}
}
// Calculate padding at the beginning based on current offset.
- const VkDeviceSize paddingBegin = *pOffset - suballoc.offset;
+ const VkDeviceSize paddingBegin = offset - suballoc.offset;
// Calculate required margin at the end.
const VkDeviceSize requiredEndMargin = debugMargin;
@@ -6386,14 +6923,14 @@ bool VmaBlockMetadata_Generic::CheckAllocation(
// Check next suballocations for BufferImageGranularity conflicts.
// If conflict exists, allocation cannot be made here.
- if (allocSize % bufferImageGranularity || *pOffset % bufferImageGranularity)
+ if (allocSize % bufferImageGranularity || offset % bufferImageGranularity)
{
VmaSuballocationList::const_iterator nextSuballocItem = suballocItem;
++nextSuballocItem;
while (nextSuballocItem != m_Suballocations.cend())
{
const VmaSuballocation& nextSuballoc = *nextSuballocItem;
- if (VmaBlocksOnSamePage(*pOffset, allocSize, nextSuballoc.offset, bufferImageGranularity))
+ if (VmaBlocksOnSamePage(offset, allocSize, nextSuballoc.offset, bufferImageGranularity))
{
if (VmaIsBufferImageGranularityConflict(allocType, nextSuballoc.type))
{
@@ -6409,7 +6946,8 @@ bool VmaBlockMetadata_Generic::CheckAllocation(
}
}
- // All tests passed: Success. pOffset is already filled.
+ *pAllocHandle = (VmaAllocHandle)offset;
+ // All tests passed: Success. pAllocHandle is already filled.
return true;
}
@@ -6645,43 +7183,44 @@ class VmaBlockMetadata_Linear : public VmaBlockMetadata
{
VMA_CLASS_NO_COPY(VmaBlockMetadata_Linear)
public:
- VmaBlockMetadata_Linear(const VkAllocationCallbacks* pAllocationCallbacks, bool isVirtual);
+ VmaBlockMetadata_Linear(const VkAllocationCallbacks* pAllocationCallbacks,
+ VkDeviceSize bufferImageGranularity, bool isVirtual);
virtual ~VmaBlockMetadata_Linear() = default;
- virtual VkDeviceSize GetSumFreeSize() const { return m_SumFreeSize; }
- virtual bool IsEmpty() const { return GetAllocationCount() == 0; }
+ VkDeviceSize GetSumFreeSize() const override { return m_SumFreeSize; }
+ bool IsEmpty() const override { return GetAllocationCount() == 0; }
+ VkDeviceSize GetAllocationOffset(VmaAllocHandle allocHandle) const override { return (VkDeviceSize)allocHandle; };
- virtual void Init(VkDeviceSize size);
- virtual bool Validate() const;
- virtual size_t GetAllocationCount() const;
+ void Init(VkDeviceSize size) override;
+ bool Validate() const override;
+ size_t GetAllocationCount() const override;
- virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const;
- virtual void AddPoolStats(VmaPoolStats& inoutStats) const;
+ void CalcAllocationStatInfo(VmaStatInfo& outInfo) const override;
+ void AddPoolStats(VmaPoolStats& inoutStats) const override;
#if VMA_STATS_STRING_ENABLED
- virtual void PrintDetailedMap(class VmaJsonWriter& json) const;
+ void PrintDetailedMap(class VmaJsonWriter& json) const override;
#endif
- virtual bool CreateAllocationRequest(
- VkDeviceSize bufferImageGranularity,
+ bool CreateAllocationRequest(
VkDeviceSize allocSize,
VkDeviceSize allocAlignment,
bool upperAddress,
VmaSuballocationType allocType,
uint32_t strategy,
- VmaAllocationRequest* pAllocationRequest);
+ VmaAllocationRequest* pAllocationRequest) override;
- virtual VkResult CheckCorruption(const void* pBlockData);
+ VkResult CheckCorruption(const void* pBlockData) override;
- virtual void Alloc(
+ void Alloc(
const VmaAllocationRequest& request,
VmaSuballocationType type,
- void* userData);
+ void* userData) override;
- virtual void FreeAtOffset(VkDeviceSize offset);
- virtual void GetAllocationInfo(VkDeviceSize offset, VmaVirtualAllocationInfo& outInfo);
- virtual void Clear();
- virtual void SetAllocationUserData(VkDeviceSize offset, void* userData);
+ void Free(VmaAllocHandle allocHandle) override;
+ void GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) override;
+ void Clear() override;
+ void SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) override;
private:
/*
@@ -6730,14 +7269,12 @@ private:
void CleanupAfterFree();
bool CreateAllocationRequest_LowerAddress(
- VkDeviceSize bufferImageGranularity,
VkDeviceSize allocSize,
VkDeviceSize allocAlignment,
VmaSuballocationType allocType,
uint32_t strategy,
VmaAllocationRequest* pAllocationRequest);
bool CreateAllocationRequest_UpperAddress(
- VkDeviceSize bufferImageGranularity,
VkDeviceSize allocSize,
VkDeviceSize allocAlignment,
VmaSuballocationType allocType,
@@ -6746,8 +7283,9 @@ private:
};
#ifndef _VMA_BLOCK_METADATA_LINEAR_FUNCTIONS
-VmaBlockMetadata_Linear::VmaBlockMetadata_Linear(const VkAllocationCallbacks* pAllocationCallbacks, bool isVirtual)
- : VmaBlockMetadata(pAllocationCallbacks, isVirtual),
+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)),
@@ -6814,7 +7352,7 @@ bool VmaBlockMetadata_Linear::Validate() const
{
if (!IsVirtual())
{
- VMA_VALIDATE(alloc->GetOffset() == suballoc.offset);
+ VMA_VALIDATE((VkDeviceSize)alloc->GetAllocHandle() == suballoc.offset);
VMA_VALIDATE(alloc->GetSize() == suballoc.size);
}
sumUsedSize += suballoc.size;
@@ -6856,7 +7394,7 @@ bool VmaBlockMetadata_Linear::Validate() const
{
if (!IsVirtual())
{
- VMA_VALIDATE(alloc->GetOffset() == suballoc.offset);
+ VMA_VALIDATE((VkDeviceSize)alloc->GetAllocHandle() == suballoc.offset);
VMA_VALIDATE(alloc->GetSize() == suballoc.size);
}
sumUsedSize += suballoc.size;
@@ -6890,7 +7428,7 @@ bool VmaBlockMetadata_Linear::Validate() const
{
if (!IsVirtual())
{
- VMA_VALIDATE(alloc->GetOffset() == suballoc.offset);
+ VMA_VALIDATE((VkDeviceSize)alloc->GetAllocHandle() == suballoc.offset);
VMA_VALIDATE(alloc->GetSize() == suballoc.size);
}
sumUsedSize += suballoc.size;
@@ -7565,7 +8103,6 @@ void VmaBlockMetadata_Linear::PrintDetailedMap(class VmaJsonWriter& json) const
#endif // VMA_STATS_STRING_ENABLED
bool VmaBlockMetadata_Linear::CreateAllocationRequest(
- VkDeviceSize bufferImageGranularity,
VkDeviceSize allocSize,
VkDeviceSize allocAlignment,
bool upperAddress,
@@ -7580,10 +8117,8 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest(
pAllocationRequest->size = allocSize;
return upperAddress ?
CreateAllocationRequest_UpperAddress(
- bufferImageGranularity,
allocSize, allocAlignment, allocType, strategy, pAllocationRequest) :
CreateAllocationRequest_LowerAddress(
- bufferImageGranularity,
allocSize, allocAlignment, allocType, strategy, pAllocationRequest);
}
@@ -7636,7 +8171,7 @@ void VmaBlockMetadata_Linear::Alloc(
VmaSuballocationType type,
void* userData)
{
- const VmaSuballocation newSuballoc = { request.offset, request.size, userData, type };
+ const VmaSuballocation newSuballoc = { (VkDeviceSize)request.allocHandle, request.size, userData, type };
switch (request.type)
{
@@ -7654,9 +8189,9 @@ void VmaBlockMetadata_Linear::Alloc(
SuballocationVectorType& suballocations1st = AccessSuballocations1st();
VMA_ASSERT(suballocations1st.empty() ||
- request.offset >= suballocations1st.back().offset + suballocations1st.back().size);
+ (VkDeviceSize)request.allocHandle >= suballocations1st.back().offset + suballocations1st.back().size);
// Check if it fits before the end of the block.
- VMA_ASSERT(request.offset + request.size <= GetSize());
+ VMA_ASSERT((VkDeviceSize)request.allocHandle + request.size <= GetSize());
suballocations1st.push_back(newSuballoc);
}
@@ -7666,7 +8201,7 @@ void VmaBlockMetadata_Linear::Alloc(
SuballocationVectorType& suballocations1st = AccessSuballocations1st();
// New allocation at the end of 2-part ring buffer, so before first allocation from 1st vector.
VMA_ASSERT(!suballocations1st.empty() &&
- request.offset + request.size <= suballocations1st[m_1stNullItemsBeginCount].offset);
+ (VkDeviceSize)request.allocHandle + request.size <= suballocations1st[m_1stNullItemsBeginCount].offset);
SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();
switch (m_2ndVectorMode)
@@ -7697,10 +8232,11 @@ void VmaBlockMetadata_Linear::Alloc(
m_SumFreeSize -= newSuballoc.size;
}
-void VmaBlockMetadata_Linear::FreeAtOffset(VkDeviceSize offset)
+void VmaBlockMetadata_Linear::Free(VmaAllocHandle allocHandle)
{
SuballocationVectorType& suballocations1st = AccessSuballocations1st();
SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();
+ VkDeviceSize offset = (VkDeviceSize)allocHandle;
if (!suballocations1st.empty())
{
@@ -7785,9 +8321,10 @@ void VmaBlockMetadata_Linear::FreeAtOffset(VkDeviceSize offset)
VMA_ASSERT(0 && "Allocation to free not found in linear allocator!");
}
-void VmaBlockMetadata_Linear::GetAllocationInfo(VkDeviceSize offset, VmaVirtualAllocationInfo& outInfo)
+void VmaBlockMetadata_Linear::GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo)
{
- VmaSuballocation& suballoc = FindSuballocation(offset);
+ VmaSuballocation& suballoc = FindSuballocation((VkDeviceSize)allocHandle);
+ outInfo.offset = (VkDeviceSize)allocHandle;
outInfo.size = suballoc.size;
outInfo.pUserData = suballoc.userData;
}
@@ -7804,9 +8341,9 @@ void VmaBlockMetadata_Linear::Clear()
m_2ndNullItemsCount = 0;
}
-void VmaBlockMetadata_Linear::SetAllocationUserData(VkDeviceSize offset, void* userData)
+void VmaBlockMetadata_Linear::SetAllocationUserData(VmaAllocHandle allocHandle, void* userData)
{
- VmaSuballocation& suballoc = FindSuballocation(offset);
+ VmaSuballocation& suballoc = FindSuballocation((VkDeviceSize)allocHandle);
suballoc.userData = userData;
}
@@ -7961,7 +8498,6 @@ void VmaBlockMetadata_Linear::CleanupAfterFree()
}
bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress(
- VkDeviceSize bufferImageGranularity,
VkDeviceSize allocSize,
VkDeviceSize allocAlignment,
VmaSuballocationType allocType,
@@ -7970,6 +8506,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress(
{
const VkDeviceSize blockSize = GetSize();
const VkDeviceSize debugMargin = GetDebugMargin();
+ const VkDeviceSize bufferImageGranularity = GetBufferImageGranularity();
SuballocationVectorType& suballocations1st = AccessSuballocations1st();
SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();
@@ -8051,7 +8588,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress(
}
// All tests passed: Success.
- pAllocationRequest->offset = resultOffset;
+ pAllocationRequest->allocHandle = (VmaAllocHandle)resultOffset;
// pAllocationRequest->item, customData unused.
pAllocationRequest->type = VmaAllocationRequestType::EndOf1st;
return true;
@@ -8140,7 +8677,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress(
}
// All tests passed: Success.
- pAllocationRequest->offset = resultOffset;
+ pAllocationRequest->allocHandle = (VmaAllocHandle)resultOffset;
pAllocationRequest->type = VmaAllocationRequestType::EndOf2nd;
// pAllocationRequest->item, customData unused.
return true;
@@ -8151,7 +8688,6 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_LowerAddress(
}
bool VmaBlockMetadata_Linear::CreateAllocationRequest_UpperAddress(
- VkDeviceSize bufferImageGranularity,
VkDeviceSize allocSize,
VkDeviceSize allocAlignment,
VmaSuballocationType allocType,
@@ -8159,6 +8695,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_UpperAddress(
VmaAllocationRequest* pAllocationRequest)
{
const VkDeviceSize blockSize = GetSize();
+ const VkDeviceSize bufferImageGranularity = GetBufferImageGranularity();
SuballocationVectorType& suballocations1st = AccessSuballocations1st();
SuballocationVectorType& suballocations2nd = AccessSuballocations2nd();
@@ -8257,7 +8794,7 @@ bool VmaBlockMetadata_Linear::CreateAllocationRequest_UpperAddress(
}
// All tests passed: Success.
- pAllocationRequest->offset = resultOffset;
+ pAllocationRequest->allocHandle = (VmaAllocHandle)resultOffset;
// pAllocationRequest->item unused.
pAllocationRequest->type = VmaAllocationRequestType::UpperAddress;
return true;
@@ -8284,42 +8821,43 @@ class VmaBlockMetadata_Buddy : public VmaBlockMetadata
{
VMA_CLASS_NO_COPY(VmaBlockMetadata_Buddy)
public:
- VmaBlockMetadata_Buddy(const VkAllocationCallbacks* pAllocationCallbacks, bool isVirtual);
+ VmaBlockMetadata_Buddy(const VkAllocationCallbacks* pAllocationCallbacks,
+ VkDeviceSize bufferImageGranularity, bool isVirtual);
virtual ~VmaBlockMetadata_Buddy();
- virtual size_t GetAllocationCount() const { return m_AllocationCount; }
- virtual VkDeviceSize GetSumFreeSize() const { return m_SumFreeSize + GetUnusableSize(); }
- virtual bool IsEmpty() const { return m_Root->type == Node::TYPE_FREE; }
- virtual VkResult CheckCorruption(const void* pBlockData) { return VK_ERROR_FEATURE_NOT_PRESENT; }
+ size_t GetAllocationCount() const override { return m_AllocationCount; }
+ VkDeviceSize GetSumFreeSize() const override { return m_SumFreeSize + GetUnusableSize(); }
+ bool IsEmpty() const override { return m_Root->type == Node::TYPE_FREE; }
+ VkResult CheckCorruption(const void* pBlockData) override { return VK_ERROR_FEATURE_NOT_PRESENT; }
+ VkDeviceSize GetAllocationOffset(VmaAllocHandle allocHandle) const override { return (VkDeviceSize)allocHandle; };
- virtual void Init(VkDeviceSize size);
- virtual bool Validate() const;
+ void Init(VkDeviceSize size) override;
+ bool Validate() const override;
- virtual void CalcAllocationStatInfo(VmaStatInfo& outInfo) const;
- virtual void AddPoolStats(VmaPoolStats& inoutStats) const;
+ void CalcAllocationStatInfo(VmaStatInfo& outInfo) const override;
+ void AddPoolStats(VmaPoolStats& inoutStats) const override;
#if VMA_STATS_STRING_ENABLED
- virtual void PrintDetailedMap(class VmaJsonWriter& json) const;
+ void PrintDetailedMap(class VmaJsonWriter& json) const override;
#endif
- virtual bool CreateAllocationRequest(
- VkDeviceSize bufferImageGranularity,
+ bool CreateAllocationRequest(
VkDeviceSize allocSize,
VkDeviceSize allocAlignment,
bool upperAddress,
VmaSuballocationType allocType,
uint32_t strategy,
- VmaAllocationRequest* pAllocationRequest);
+ VmaAllocationRequest* pAllocationRequest) override;
- virtual void Alloc(
+ void Alloc(
const VmaAllocationRequest& request,
VmaSuballocationType type,
- void* userData);
+ void* userData) override;
- virtual void FreeAtOffset(VkDeviceSize offset);
- virtual void GetAllocationInfo(VkDeviceSize offset, VmaVirtualAllocationInfo& outInfo);
- virtual void Clear();
- virtual void SetAllocationUserData(VkDeviceSize offset, void* userData);
+ void Free(VmaAllocHandle allocHandle) override;
+ void GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) override;
+ void Clear() override;
+ void SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) override;
private:
static const size_t MAX_LEVELS = 48;
@@ -8411,8 +8949,9 @@ private:
};
#ifndef _VMA_BLOCK_METADATA_BUDDY_FUNCTIONS
-VmaBlockMetadata_Buddy::VmaBlockMetadata_Buddy(const VkAllocationCallbacks* pAllocationCallbacks, bool isVirtual)
- : VmaBlockMetadata(pAllocationCallbacks, isVirtual),
+VmaBlockMetadata_Buddy::VmaBlockMetadata_Buddy(const VkAllocationCallbacks* pAllocationCallbacks,
+ VkDeviceSize bufferImageGranularity, bool isVirtual)
+ : VmaBlockMetadata(pAllocationCallbacks, bufferImageGranularity, isVirtual),
m_NodeAllocator(pAllocationCallbacks, 32), // firstBlockCapacity
m_Root(VMA_NULL),
m_AllocationCount(0),
@@ -8527,7 +9066,6 @@ void VmaBlockMetadata_Buddy::AddPoolStats(VmaPoolStats& inoutStats) const
}
#if VMA_STATS_STRING_ENABLED
-
void VmaBlockMetadata_Buddy::PrintDetailedMap(class VmaJsonWriter& json) const
{
VmaStatInfo stat;
@@ -8554,7 +9092,6 @@ void VmaBlockMetadata_Buddy::PrintDetailedMap(class VmaJsonWriter& json) const
#endif // VMA_STATS_STRING_ENABLED
bool VmaBlockMetadata_Buddy::CreateAllocationRequest(
- VkDeviceSize bufferImageGranularity,
VkDeviceSize allocSize,
VkDeviceSize allocAlignment,
bool upperAddress,
@@ -8572,8 +9109,8 @@ bool VmaBlockMetadata_Buddy::CreateAllocationRequest(
allocType == VMA_SUBALLOCATION_TYPE_IMAGE_UNKNOWN ||
allocType == VMA_SUBALLOCATION_TYPE_IMAGE_OPTIMAL)
{
- allocAlignment = VMA_MAX(allocAlignment, bufferImageGranularity);
- allocSize = VMA_MAX(allocSize, bufferImageGranularity);
+ allocAlignment = VMA_MAX(allocAlignment, GetBufferImageGranularity());
+ allocSize = VmaAlignUp(allocSize, GetBufferImageGranularity());
}
if (allocSize > m_UsableSize)
@@ -8591,7 +9128,7 @@ bool VmaBlockMetadata_Buddy::CreateAllocationRequest(
if (freeNode->offset % allocAlignment == 0)
{
pAllocationRequest->type = VmaAllocationRequestType::Normal;
- pAllocationRequest->offset = freeNode->offset;
+ pAllocationRequest->allocHandle = (VmaAllocHandle)freeNode->offset;
pAllocationRequest->size = allocSize;
pAllocationRequest->customData = (void*)(uintptr_t)level;
return true;
@@ -8614,7 +9151,7 @@ void VmaBlockMetadata_Buddy::Alloc(
Node* currNode = m_FreeList[currLevel].front;
VMA_ASSERT(currNode != VMA_NULL && currNode->type == Node::TYPE_FREE);
- while (currNode->offset != request.offset)
+ while (currNode->offset != (VkDeviceSize)request.allocHandle)
{
currNode = currNode->free.next;
VMA_ASSERT(currNode != VMA_NULL && currNode->type == Node::TYPE_FREE);
@@ -8676,10 +9213,11 @@ void VmaBlockMetadata_Buddy::Alloc(
m_SumFreeSize -= request.size;
}
-void VmaBlockMetadata_Buddy::GetAllocationInfo(VkDeviceSize offset, VmaVirtualAllocationInfo& outInfo)
+void VmaBlockMetadata_Buddy::GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo)
{
uint32_t level = 0;
- const Node* const node = FindAllocationNode(offset, level);
+ const Node* const node = FindAllocationNode((VkDeviceSize)allocHandle, level);
+ outInfo.offset = (VkDeviceSize)allocHandle;
outInfo.size = LevelToNodeSize(level);
outInfo.pUserData = node->allocation.userData;
}
@@ -8705,10 +9243,10 @@ void VmaBlockMetadata_Buddy::Clear()
m_SumFreeSize = m_UsableSize;
}
-void VmaBlockMetadata_Buddy::SetAllocationUserData(VkDeviceSize offset, void* userData)
+void VmaBlockMetadata_Buddy::SetAllocationUserData(VmaAllocHandle allocHandle, void* userData)
{
uint32_t level = 0;
- Node* const node = FindAllocationNode(offset, level);
+ Node* const node = FindAllocationNode((VkDeviceSize)allocHandle, level);
node->allocation.userData = userData;
}
@@ -8799,10 +9337,10 @@ uint32_t VmaBlockMetadata_Buddy::AllocSizeToLevel(VkDeviceSize allocSize) const
return level;
}
-void VmaBlockMetadata_Buddy::FreeAtOffset(VkDeviceSize offset)
+void VmaBlockMetadata_Buddy::Free(VmaAllocHandle allocHandle)
{
uint32_t level = 0;
- Node* node = FindAllocationNode(offset, level);
+ Node* node = FindAllocationNode((VkDeviceSize)allocHandle, level);
++m_FreeCount;
--m_AllocationCount;
@@ -8933,6 +9471,764 @@ void VmaBlockMetadata_Buddy::PrintDetailedMapNode(class VmaJsonWriter& json, con
#endif // _VMA_BLOCK_METADATA_BUDDY_FUNCTIONS
#endif // _VMA_BLOCK_METADATA_BUDDY
+#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_BEST_FIT_BIT as strategy in CreateAllocationRequest()
+class VmaBlockMetadata_TLSF : public VmaBlockMetadata
+{
+ VMA_CLASS_NO_COPY(VmaBlockMetadata_TLSF)
+public:
+ VmaBlockMetadata_TLSF(const VkAllocationCallbacks* pAllocationCallbacks,
+ VkDeviceSize bufferImageGranularity, bool isVirtual);
+ virtual ~VmaBlockMetadata_TLSF();
+
+ size_t GetAllocationCount() const override { return m_AllocCount; }
+ VkDeviceSize GetSumFreeSize() const override { return m_BlocksFreeSize + m_NullBlock->size; }
+ bool IsEmpty() const override { return m_NullBlock->offset == 0; }
+ VkResult CheckCorruption(const void* pBlockData) override { return VK_ERROR_FEATURE_NOT_PRESENT; }
+ VkDeviceSize GetAllocationOffset(VmaAllocHandle allocHandle) const override { return ((Block*)allocHandle)->offset; };
+
+ void Init(VkDeviceSize size) override;
+ bool Validate() const override;
+
+ void CalcAllocationStatInfo(VmaStatInfo& outInfo) const override;
+ void AddPoolStats(VmaPoolStats& 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;
+
+ void Alloc(
+ const VmaAllocationRequest& request,
+ VmaSuballocationType type,
+ void* userData) override;
+
+ void Free(VmaAllocHandle allocHandle) override;
+ void GetAllocationInfo(VmaAllocHandle allocHandle, VmaVirtualAllocationInfo& outInfo) override;
+ void Clear() override;
+ void SetAllocationUserData(VmaAllocHandle allocHandle, void* userData) 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;
+
+ struct RegionInfo
+ {
+ uint8_t allocType;
+ uint16_t allocCount;
+ };
+ 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_IsFree;
+ uint8_t m_MemoryClasses;
+ uint16_t* m_InnerIsFree;
+ 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_IsFree(0),
+ m_MemoryClasses(0),
+ m_InnerIsFree(VMA_NULL),
+ m_ListsCount(0),
+ m_FreeList(VMA_NULL),
+ m_BlockAllocator(pAllocationCallbacks, INITIAL_BLOCK_ALLOC_COUNT * sizeof(Block)),
+ m_NullBlock(VMA_NULL),
+ m_GranularityHandler(bufferImageGranularity) {}
+
+VmaBlockMetadata_TLSF::~VmaBlockMetadata_TLSF()
+{
+ if (m_InnerIsFree)
+ vma_delete_array(GetAllocationCallbacks(), m_InnerIsFree, m_MemoryClasses);
+ 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) + 5;
+
+ m_MemoryClasses = memoryClass + 2;
+ m_InnerIsFree = vma_new_array(GetAllocationCallbacks(), uint16_t, m_MemoryClasses);
+ memset(m_InnerIsFree, 0, m_MemoryClasses * sizeof(uint16_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::CalcAllocationStatInfo(VmaStatInfo& outInfo) const
+{
+ VmaInitStatInfo(outInfo);
+ outInfo.blockCount = 1;
+ if (m_NullBlock->size > 0)
+ VmaAddStatInfoUnusedRange(outInfo, m_NullBlock->size);
+
+ for (Block* block = m_NullBlock->prevPhysical; block != VMA_NULL; block = block->prevPhysical)
+ {
+ if (block->IsFree())
+ VmaAddStatInfoUnusedRange(outInfo, block->size);
+ else
+ VmaAddStatInfoAllocation(outInfo, block->size);
+ }
+}
+
+void VmaBlockMetadata_TLSF::AddPoolStats(VmaPoolStats& inoutStats) const
+{
+ inoutStats.size += GetSize();
+ inoutStats.unusedSize += GetSumFreeSize();
+ inoutStats.allocationCount += m_AllocCount;
+ inoutStats.unusedRangeCount += m_BlocksFreeCount;
+ if(m_NullBlock->size > 0)
+ ++inoutStats.unusedRangeCount;
+}
+
+#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);
+
+ VmaStatInfo stat;
+ CalcAllocationStatInfo(stat);
+
+ PrintDetailedMap_Begin(json,
+ stat.unusedBytes,
+ stat.allocationCount,
+ stat.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->PrevFree());
+ }
+ 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);
+
+ // 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);
+
+ VkDeviceSize roundedSize = allocSize;
+ if (allocSize >= (1 << SECOND_LEVEL_INDEX))
+ {
+ // Round up to the next block
+ roundedSize += (1ULL << (VMA_BITSCAN_MSB(allocSize) - SECOND_LEVEL_INDEX)) - 1;
+ }
+
+ uint32_t listIndex = 0;
+ Block* block = FindFreeBlock(roundedSize, listIndex);
+ while (block)
+ {
+ if (CheckBlock(*block, listIndex, allocSize, allocAlignment, allocType, pAllocationRequest))
+ return true;
+
+ block = block->NextFree();
+ // Region does not meet requirements
+ if (strategy & VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT)
+ break;
+ }
+
+ if ((strategy & VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT) == 0)
+ {
+ // No region in previous bucket, check null block
+ if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, allocType, pAllocationRequest))
+ return true;
+ }
+
+ // No other region found, check previous bucket
+ uint32_t prevListIndex = 0;
+ Block* prevListBlock = FindFreeBlock(allocSize, prevListIndex);
+ while (prevListBlock)
+ {
+ if (CheckBlock(*prevListBlock, prevListIndex, allocSize, allocAlignment, allocType, pAllocationRequest))
+ return true;
+
+ prevListBlock = prevListBlock->NextFree();
+ }
+
+ if (strategy & VMA_ALLOCATION_CREATE_STRATEGY_BEST_FIT_BIT)
+ {
+ // No region in previous bucket, check null block
+ if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, allocType, pAllocationRequest))
+ return true;
+ }
+
+ // If all searches failed and first bucket still have some free regions then check it fully
+ while (block)
+ {
+ if (CheckBlock(*block, listIndex, allocSize, allocAlignment, allocType, pAllocationRequest))
+ return true;
+
+ block = block->NextFree();
+ }
+
+ // Worst case, if bufferImageGranularity is causing free blocks to become not suitable then full search has to be done
+ if (!IsVirtual() && m_GranularityHandler.IsEnabled())
+ {
+ while (++listIndex < m_ListsCount)
+ {
+ block = m_FreeList[listIndex];
+ while (block)
+ {
+ if (CheckBlock(*block, listIndex, allocSize, allocAlignment, allocType, pAllocationRequest))
+ return true;
+
+ block = block->NextFree();
+ }
+ }
+ }
+
+ // No more memory sadly
+ return false;
+}
+
+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;
+ VMA_ASSERT(currentBlock != VMA_NULL);
+
+ if (currentBlock != m_NullBlock)
+ RemoveFreeBlock(currentBlock);
+
+ // Append missing alignment to prev block or create new one
+ VMA_ASSERT(currentBlock->offset <= request.algorithmData);
+ VkDeviceSize misssingAlignment = request.algorithmData - currentBlock->offset;
+ if (misssingAlignment)
+ {
+ Block* prevBlock = currentBlock->prevPhysical;
+ VMA_ASSERT(prevBlock != VMA_NULL && "There should be no missing alignment at offset 0!");
+
+ if (prevBlock->IsFree())
+ {
+ 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;
+ }
+
+ if (currentBlock->size == request.size)
+ {
+ if (currentBlock == m_NullBlock)
+ {
+ // Setup new null block
+ m_NullBlock = m_BlockAllocator.Alloc();
+ m_NullBlock->size = 0;
+ m_NullBlock->offset = currentBlock->offset + request.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 > request.size && "Proper block already found, shouldn't find smaller one!");
+
+ // Create new free block
+ Block* newBlock = m_BlockAllocator.Alloc();
+ newBlock->size = currentBlock->size - request.size;
+ newBlock->offset = currentBlock->offset + request.size;
+ newBlock->prevPhysical = currentBlock;
+ newBlock->nextPhysical = currentBlock->nextPhysical;
+ currentBlock->nextPhysical = newBlock;
+ currentBlock->size = request.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 (!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;
+
+ // Try merging
+ Block* prev = block->prevPhysical;
+ if (prev != VMA_NULL && prev->IsFree())
+ {
+ 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::Clear()
+{
+ m_AllocCount = 0;
+ m_BlocksFreeCount = 0;
+ m_BlocksFreeSize = 0;
+ m_IsFree = 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_InnerIsFree, 0, m_MemoryClasses * sizeof(uint16_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;
+}
+
+uint8_t VmaBlockMetadata_TLSF::SizeToMemoryClass(VkDeviceSize size) const
+{
+ if (size > SMALL_BUFFER_SIZE)
+ return VMA_BITSCAN_MSB(size) - MEMORY_CLASS_SHIFT;
+ return 0;
+}
+
+uint16_t VmaBlockMetadata_TLSF::SizeToSecondIndex(VkDeviceSize size, uint8_t memoryClass) const
+{
+ if (memoryClass == 0)
+ 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;
+ return static_cast(memoryClass - 1) * (1 << SECOND_LEVEL_INDEX) + secondIndex + 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);
+ m_FreeList[index] = block->NextFree();
+ if (block->NextFree() == VMA_NULL)
+ {
+ m_InnerIsFree[memClass] &= ~(1U << secondIndex);
+ if (m_InnerIsFree[memClass] == 0)
+ m_IsFree &= ~(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);
+ block->PrevFree() = VMA_NULL;
+ block->NextFree() = m_FreeList[index];
+ m_FreeList[index] = block;
+ if (block->NextFree() != VMA_NULL)
+ block->NextFree()->PrevFree() = block;
+ else
+ {
+ m_InnerIsFree[memClass] |= 1U << secondIndex;
+ m_IsFree |= 1UL << memClass;
+ }
+ ++m_BlocksFreeCount;
+ m_BlocksFreeSize += block->size;
+}
+
+void VmaBlockMetadata_TLSF::MergeBlock(Block* block, Block* prev)
+{
+ VMA_ASSERT(block->prevPhysical == prev && "Cannot merge seperate 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);
+ uint16_t innerFreeMap = m_InnerIsFree[memoryClass] & (~0U << SizeToSecondIndex(size, memoryClass));
+ if (!innerFreeMap)
+ {
+ // Check higher levels for avaiable blocks
+ uint32_t freeMap = m_IsFree & (~0UL << (memoryClass + 1));
+ if (!freeMap)
+ return VMA_NULL; // No more memory avaible
+
+ // Find lowest free region
+ innerFreeMap = m_InnerIsFree[VMA_BITSCAN_LSB(freeMap)];
+ }
+ // Find lowest free subregion
+ listIndex = GetListIndex(memoryClass, VMA_BITSCAN_LSB(static_cast(innerFreeMap)));
+ 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.IsConflict(allocSize, alignedOffset, block.size, block.offset, allocType))
+ return false;
+
+ // Alloc successful
+ pAllocationRequest->type = VmaAllocationRequestType::TLSF;
+ pAllocationRequest->allocHandle = (VmaAllocHandle)█
+ pAllocationRequest->size = allocSize;
+ 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
@@ -9095,6 +10391,7 @@ struct VmaDefragmentationMove
size_t dstBlockIndex;
VkDeviceSize srcOffset;
VkDeviceSize dstOffset;
+ VmaAllocHandle dstHandle;
VkDeviceSize size;
VmaAllocation hAllocation;
VmaDeviceMemoryBlock* pSrcBlock;
@@ -9264,7 +10561,7 @@ private:
struct FreeSpace
{
- size_t blockInfoIndex; // SIZE_MAX means this structure is invalid.
+ size_t blockInfoIndex; // SIZE_MAX means this structure is invalid.
VkDeviceSize offset;
VkDeviceSize size;
} m_FreeSpaces[MAX_COUNT];
@@ -9538,13 +10835,14 @@ public:
VkResult Init() { return VK_SUCCESS; }
bool IsEmpty() const { return m_Metadata->IsEmpty(); }
- void Free(VkDeviceSize offset) { m_Metadata->FreeAtOffset(offset); }
- void SetAllocationUserData(VkDeviceSize offset, void* userData) { m_Metadata->SetAllocationUserData(offset, userData); }
+ 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(VkDeviceSize offset, VmaVirtualAllocationInfo& outInfo);
- VkResult Allocate(const VmaVirtualAllocationCreateInfo& createInfo, VkDeviceSize& outOffset);
+ void GetAllocationInfo(VmaVirtualAllocation allocation, VmaVirtualAllocationInfo& outInfo);
+ VkResult Allocate(const VmaVirtualAllocationCreateInfo& createInfo, VmaVirtualAllocation& outAllocation,
+ VkDeviceSize* outOffset);
void CalculateStats(VmaStatInfo& outStatInfo) const;
#if VMA_STATS_STRING_ENABLED
void BuildStatsString(bool detailedMap, VmaStringBuilder& sb) const;
@@ -9563,13 +10861,16 @@ VmaVirtualBlock_T::VmaVirtualBlock_T(const VmaVirtualBlockCreateInfo& createInfo
switch (algorithm)
{
case 0:
- m_Metadata = vma_new(GetAllocationCallbacks(), VmaBlockMetadata_Generic)(VK_NULL_HANDLE, true);
+ m_Metadata = vma_new(GetAllocationCallbacks(), VmaBlockMetadata_Generic)(VK_NULL_HANDLE, 1, true);
break;
case VMA_VIRTUAL_BLOCK_CREATE_BUDDY_ALGORITHM_BIT:
- m_Metadata = vma_new(GetAllocationCallbacks(), VmaBlockMetadata_Buddy)(VK_NULL_HANDLE, true);
+ m_Metadata = vma_new(GetAllocationCallbacks(), VmaBlockMetadata_Buddy)(VK_NULL_HANDLE, 1, true);
break;
case VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT:
- m_Metadata = vma_new(GetAllocationCallbacks(), VmaBlockMetadata_Linear)(VK_NULL_HANDLE, true);
+ m_Metadata = vma_new(GetAllocationCallbacks(), VmaBlockMetadata_Linear)(VK_NULL_HANDLE, 1, true);
+ break;
+ case VMA_VIRTUAL_BLOCK_CREATE_TLSF_ALGORITHM_BIT:
+ m_Metadata = vma_new(GetAllocationCallbacks(), VmaBlockMetadata_TLSF)(VK_NULL_HANDLE, 1, true);
break;
default:
VMA_ASSERT(0);
@@ -9592,17 +10893,16 @@ const VkAllocationCallbacks* VmaVirtualBlock_T::GetAllocationCallbacks() const
return m_AllocationCallbacksSpecified ? &m_AllocationCallbacks : VMA_NULL;
}
-void VmaVirtualBlock_T::GetAllocationInfo(VkDeviceSize offset, VmaVirtualAllocationInfo& outInfo)
+void VmaVirtualBlock_T::GetAllocationInfo(VmaVirtualAllocation allocation, VmaVirtualAllocationInfo& outInfo)
{
- m_Metadata->GetAllocationInfo(offset, outInfo);
+ m_Metadata->GetAllocationInfo((VmaAllocHandle)allocation, outInfo);
}
-VkResult VmaVirtualBlock_T::Allocate(const VmaVirtualAllocationCreateInfo& createInfo, VkDeviceSize& outOffset)
+VkResult VmaVirtualBlock_T::Allocate(const VmaVirtualAllocationCreateInfo& createInfo, VmaVirtualAllocation& outAllocation,
+ VkDeviceSize* outOffset)
{
- outOffset = VK_WHOLE_SIZE;
VmaAllocationRequest request = {};
if (m_Metadata->CreateAllocationRequest(
- 1, // bufferImageGranularity
createInfo.size, // allocSize
VMA_MAX(createInfo.alignment, (VkDeviceSize)1), // allocAlignment
(createInfo.flags & VMA_VIRTUAL_ALLOCATION_CREATE_UPPER_ADDRESS_BIT) != 0, // upperAddress
@@ -9613,9 +10913,12 @@ VkResult VmaVirtualBlock_T::Allocate(const VmaVirtualAllocationCreateInfo& creat
m_Metadata->Alloc(request,
VMA_SUBALLOCATION_TYPE_UNKNOWN, // type - unimportant
createInfo.pUserData);
- outOffset = request.offset;
+ outAllocation = (VmaVirtualAllocation)request.allocHandle;
+ if(outOffset)
+ *outOffset = m_Metadata->GetAllocationOffset(request.allocHandle);
return VK_SUCCESS;
}
+ outAllocation = (VmaVirtualAllocation)VK_WHOLE_SIZE;
return VK_ERROR_OUT_OF_DEVICE_MEMORY;
}
@@ -10033,7 +11336,8 @@ void VmaDeviceMemoryBlock::Init(
VkDeviceMemory newMemory,
VkDeviceSize newSize,
uint32_t id,
- uint32_t algorithm)
+ uint32_t algorithm,
+ VkDeviceSize bufferImageGranularity)
{
VMA_ASSERT(m_hMemory == VK_NULL_HANDLE);
@@ -10046,18 +11350,22 @@ void VmaDeviceMemoryBlock::Init(
{
case VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT:
m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_Linear)(hAllocator->GetAllocationCallbacks(),
- false); // isVirtual
+ bufferImageGranularity, false); // isVirtual
break;
case VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT:
m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_Buddy)(hAllocator->GetAllocationCallbacks(),
- false); // isVirtual
+ bufferImageGranularity, false); // isVirtual
+ break;
+ case VMA_POOL_CREATE_TLSF_ALGORITHM_BIT:
+ m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_TLSF)(hAllocator->GetAllocationCallbacks(),
+ bufferImageGranularity, false); // isVirtual
break;
default:
VMA_ASSERT(0);
// Fall-through.
case 0:
m_pMetadata = vma_new(hAllocator, VmaBlockMetadata_Generic)(hAllocator->GetAllocationCallbacks(),
- false); // isVirtual
+ bufferImageGranularity, false); // isVirtual
}
m_pMetadata->Init(newSize);
}
@@ -10267,7 +11575,7 @@ VmaAllocation_T::~VmaAllocation_T()
void VmaAllocation_T::InitBlockAllocation(
VmaDeviceMemoryBlock* block,
- VkDeviceSize offset,
+ VmaAllocHandle allocHandle,
VkDeviceSize alignment,
VkDeviceSize size,
uint32_t memoryTypeIndex,
@@ -10283,7 +11591,7 @@ void VmaAllocation_T::InitBlockAllocation(
m_MapCount = mapped ? MAP_COUNT_FLAG_PERSISTENT_MAP : 0;
m_SuballocationType = (uint8_t)suballocationType;
m_BlockAllocation.m_Block = block;
- m_BlockAllocation.m_Offset = offset;
+ m_BlockAllocation.m_AllocHandle = allocHandle;
}
void VmaAllocation_T::InitDedicatedAllocation(
@@ -10331,7 +11639,7 @@ void VmaAllocation_T::SetUserData(VmaAllocator hAllocator, void* pUserData)
void VmaAllocation_T::ChangeBlockAllocation(
VmaAllocator hAllocator,
VmaDeviceMemoryBlock* block,
- VkDeviceSize offset)
+ VmaAllocHandle allocHandle)
{
VMA_ASSERT(block != VMA_NULL);
VMA_ASSERT(m_Type == ALLOCATION_TYPE_BLOCK);
@@ -10347,13 +11655,27 @@ void VmaAllocation_T::ChangeBlockAllocation(
}
m_BlockAllocation.m_Block = block;
- m_BlockAllocation.m_Offset = offset;
+ m_BlockAllocation.m_AllocHandle = allocHandle;
}
-void VmaAllocation_T::ChangeOffset(VkDeviceSize newOffset)
+void VmaAllocation_T::ChangeAllocHandle(VmaAllocHandle newAllocHandle)
{
VMA_ASSERT(m_Type == ALLOCATION_TYPE_BLOCK);
- m_BlockAllocation.m_Offset = newOffset;
+ m_BlockAllocation.m_AllocHandle = newAllocHandle;
+}
+
+VmaAllocHandle VmaAllocation_T::GetAllocHandle() const
+{
+ switch (m_Type)
+ {
+ case ALLOCATION_TYPE_BLOCK:
+ return m_BlockAllocation.m_AllocHandle;
+ case ALLOCATION_TYPE_DEDICATED:
+ return VMA_NULL;
+ default:
+ VMA_ASSERT(0);
+ return VMA_NULL;
+ }
}
VkDeviceSize VmaAllocation_T::GetOffset() const
@@ -10361,7 +11683,7 @@ VkDeviceSize VmaAllocation_T::GetOffset() const
switch (m_Type)
{
case ALLOCATION_TYPE_BLOCK:
- return m_BlockAllocation.m_Offset;
+ return m_BlockAllocation.m_Block->m_pMetadata->GetAllocationOffset(m_BlockAllocation.m_AllocHandle);
case ALLOCATION_TYPE_DEDICATED:
return 0;
default:
@@ -10407,7 +11729,7 @@ void* VmaAllocation_T::GetMappedData() const
{
void* pBlockData = m_BlockAllocation.m_Block->GetMappedData();
VMA_ASSERT(pBlockData != VMA_NULL);
- return (char*)pBlockData + m_BlockAllocation.m_Offset;
+ return (char*)pBlockData + GetOffset();
}
else
{
@@ -10941,7 +12263,7 @@ void VmaBlockVector::Free(
pBlock->Unmap(m_hAllocator, 1);
}
- pBlock->m_pMetadata->FreeAtOffset(hAllocation->GetOffset());
+ pBlock->m_pMetadata->Free(hAllocation->GetAllocHandle());
VMA_HEAVY_ASSERT(pBlock->Validate());
VMA_DEBUG_LOG(" Freed from MemoryTypeIndex=%u", m_MemoryTypeIndex);
@@ -11043,7 +12365,6 @@ VkResult VmaBlockVector::AllocateFromBlock(
VmaAllocationRequest currRequest = {};
if (pBlock->m_pMetadata->CreateAllocationRequest(
- m_BufferImageGranularity,
size,
alignment,
isUpperAddress,
@@ -11066,7 +12387,7 @@ VkResult VmaBlockVector::AllocateFromBlock(
UpdateHasEmptyBlock();
(*pAllocation)->InitBlockAllocation(
pBlock,
- currRequest.offset,
+ currRequest.allocHandle,
alignment,
currRequest.size, // Not size, as actual allocation size may be larger than requested!
m_MemoryTypeIndex,
@@ -11081,7 +12402,7 @@ VkResult VmaBlockVector::AllocateFromBlock(
}
if (IsCorruptionDetectionEnabled())
{
- VkResult res = pBlock->WriteMagicValueAroundAllocation(m_hAllocator, currRequest.offset, currRequest.size);
+ VkResult res = pBlock->WriteMagicValueAroundAllocation(m_hAllocator, (*pAllocation)->GetOffset(), currRequest.size);
VMA_ASSERT(res == VK_SUCCESS && "Couldn't map block memory to write magic value.");
}
return VK_SUCCESS;
@@ -11143,7 +12464,8 @@ VkResult VmaBlockVector::CreateBlock(VkDeviceSize blockSize, size_t* pNewBlockIn
mem,
allocInfo.allocationSize,
m_NextBlockId++,
- m_Algorithm);
+ m_Algorithm,
+ m_BufferImageGranularity);
m_Blocks.push_back(pBlock);
if (pNewBlockIndex != VMA_NULL)
@@ -11638,8 +12960,8 @@ void VmaBlockVector::CommitDefragmentations(
{
const VmaDefragmentationMove& move = pCtx->defragmentationMoves[i];
- move.pSrcBlock->m_pMetadata->FreeAtOffset(move.srcOffset);
- move.hAllocation->ChangeBlockAllocation(m_hAllocator, move.pDstBlock, move.dstOffset);
+ move.pSrcBlock->m_pMetadata->Free(move.hAllocation->GetAllocHandle());
+ move.hAllocation->ChangeBlockAllocation(m_hAllocator, move.pDstBlock, move.dstHandle);
}
pCtx->defragmentationMovesCommitted = pCtx->defragmentationMovesProcessed;
@@ -11841,9 +13163,9 @@ VkResult VmaDefragmentationAlgorithm_Generic::DefragmentRound(
for (size_t dstBlockIndex = 0; dstBlockIndex <= srcBlockIndex; ++dstBlockIndex)
{
BlockInfo* pDstBlockInfo = m_Blocks[dstBlockIndex];
+ VmaBlockMetadata* pMetadata = pDstBlockInfo->m_pBlock->m_pMetadata;
VmaAllocationRequest dstAllocRequest;
- if (pDstBlockInfo->m_pBlock->m_pMetadata->CreateAllocationRequest(
- m_pBlockVector->GetBufferImageGranularity(),
+ if (pMetadata->CreateAllocationRequest(
size,
alignment,
false, // upperAddress
@@ -11851,7 +13173,7 @@ VkResult VmaDefragmentationAlgorithm_Generic::DefragmentRound(
strategy,
&dstAllocRequest) &&
MoveMakesSense(
- dstBlockIndex, dstAllocRequest.offset, srcBlockIndex, srcOffset))
+ dstBlockIndex, pMetadata->GetAllocationOffset(dstAllocRequest.allocHandle), srcBlockIndex, srcOffset))
{
// Reached limit on number of allocations or bytes to move.
if ((m_AllocationsMoved + 1 > maxAllocationsToMove) ||
@@ -11864,11 +13186,12 @@ VkResult VmaDefragmentationAlgorithm_Generic::DefragmentRound(
move.srcBlockIndex = pSrcBlockInfo->m_OriginalBlockIndex;
move.dstBlockIndex = pDstBlockInfo->m_OriginalBlockIndex;
move.srcOffset = srcOffset;
- move.dstOffset = dstAllocRequest.offset;
+ move.dstOffset = pMetadata->GetAllocationOffset(dstAllocRequest.allocHandle);
move.size = size;
move.hAllocation = allocInfo.m_hAllocation;
move.pSrcBlock = pSrcBlockInfo->m_pBlock;
move.pDstBlock = pDstBlockInfo->m_pBlock;
+ move.dstHandle = dstAllocRequest.allocHandle;
moves.push_back(move);
@@ -11876,8 +13199,8 @@ VkResult VmaDefragmentationAlgorithm_Generic::DefragmentRound(
if (freeOldAllocations)
{
- pSrcBlockInfo->m_pBlock->m_pMetadata->FreeAtOffset(srcOffset);
- allocInfo.m_hAllocation->ChangeBlockAllocation(m_hAllocator, pDstBlockInfo->m_pBlock, dstAllocRequest.offset);
+ pSrcBlockInfo->m_pBlock->m_pMetadata->Free(allocInfo.m_hAllocation->GetAllocHandle());
+ allocInfo.m_hAllocation->ChangeBlockAllocation(m_hAllocator, pDstBlockInfo->m_pBlock, dstAllocRequest.allocHandle);
}
if (allocInfo.m_pChanged != VMA_NULL)
@@ -12081,7 +13404,7 @@ VmaDefragmentationAlgorithm_Fast::VmaDefragmentationAlgorithm_Fast(
}
VkResult VmaDefragmentationAlgorithm_Fast::Defragment(
- VmaVector< VmaDefragmentationMove, VmaStlAllocator >& moves,
+ VmaVector>& moves,
VkDeviceSize maxBytesToMove,
uint32_t maxAllocationsToMove,
VmaDefragmentationFlags flags)
@@ -12160,7 +13483,7 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment(
VmaSuballocation suballoc = *srcSuballocIt;
suballoc.offset = dstAllocOffset;
- ((VmaAllocation)(suballoc.userData))->ChangeOffset(dstAllocOffset);
+ ((VmaAllocation)(suballoc.userData))->ChangeAllocHandle((VmaAllocHandle)dstAllocOffset);
m_BytesMoved += srcAllocSize;
++m_AllocationsMoved;
@@ -12175,6 +13498,7 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment(
move.dstBlockIndex = freeSpaceOrigBlockIndex;
move.srcOffset = srcAllocOffset;
move.dstOffset = dstAllocOffset;
+ move.dstHandle = (VmaAllocHandle)dstAllocOffset;
move.size = srcAllocSize;
moves.push_back(move);
@@ -12188,7 +13512,7 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment(
VmaSuballocation suballoc = *srcSuballocIt;
suballoc.offset = dstAllocOffset;
- ((VmaAllocation)(suballoc.userData))->ChangeBlockAllocation(m_hAllocator, pFreeSpaceBlock, dstAllocOffset);
+ ((VmaAllocation)(suballoc.userData))->ChangeBlockAllocation(m_hAllocator, pFreeSpaceBlock, (VmaAllocHandle)dstAllocOffset);
m_BytesMoved += srcAllocSize;
++m_AllocationsMoved;
@@ -12203,6 +13527,7 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment(
move.dstBlockIndex = freeSpaceOrigBlockIndex;
move.srcOffset = srcAllocOffset;
move.dstOffset = dstAllocOffset;
+ move.dstHandle = (VmaAllocHandle)dstAllocOffset;
move.size = srcAllocSize;
moves.push_back(move);
@@ -12254,7 +13579,7 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment(
else
{
srcSuballocIt->offset = dstAllocOffset;
- ((VmaAllocation)(srcSuballocIt->userData))->ChangeOffset(dstAllocOffset);
+ ((VmaAllocation)(srcSuballocIt->userData))->ChangeAllocHandle((VmaAllocHandle)dstAllocOffset);
dstOffset = dstAllocOffset + srcAllocSize;
m_BytesMoved += srcAllocSize;
++m_AllocationsMoved;
@@ -12264,6 +13589,7 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment(
move.dstBlockIndex = dstOrigBlockIndex;
move.srcOffset = srcAllocOffset;
move.dstOffset = dstAllocOffset;
+ move.dstHandle = (VmaAllocHandle)dstAllocOffset;
move.size = srcAllocSize;
moves.push_back(move);
@@ -12279,7 +13605,7 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment(
VmaSuballocation suballoc = *srcSuballocIt;
suballoc.offset = dstAllocOffset;
- ((VmaAllocation)(suballoc.userData))->ChangeBlockAllocation(m_hAllocator, pDstBlock, dstAllocOffset);
+ ((VmaAllocation)(suballoc.userData))->ChangeBlockAllocation(m_hAllocator, pDstBlock, (VmaAllocHandle)dstAllocOffset);
dstOffset = dstAllocOffset + srcAllocSize;
m_BytesMoved += srcAllocSize;
++m_AllocationsMoved;
@@ -12295,6 +13621,7 @@ VkResult VmaDefragmentationAlgorithm_Fast::Defragment(
move.dstBlockIndex = dstOrigBlockIndex;
move.srcOffset = srcAllocOffset;
move.dstOffset = dstAllocOffset;
+ move.dstHandle = (VmaAllocHandle)dstAllocOffset;
move.size = srcAllocSize;
moves.push_back(move);
@@ -15668,7 +16995,11 @@ VMA_CALL_PRE void VMA_CALL_POST vmaUnmapMemory(
allocator->Unmap(allocation);
}
-VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size)
+VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocation(
+ VmaAllocator allocator,
+ VmaAllocation allocation,
+ VkDeviceSize offset,
+ VkDeviceSize size)
{
VMA_ASSERT(allocator && allocation);
@@ -15681,7 +17012,11 @@ VMA_CALL_PRE VkResult VMA_CALL_POST vmaFlushAllocation(VmaAllocator allocator, V
return res;
}
-VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocation(VmaAllocator allocator, VmaAllocation allocation, VkDeviceSize offset, VkDeviceSize size)
+VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocation(
+ VmaAllocator allocator,
+ VmaAllocation allocation,
+ VkDeviceSize offset,
+ VkDeviceSize size)
{
VMA_ASSERT(allocator && allocation);
@@ -15744,7 +17079,9 @@ VMA_CALL_PRE VkResult VMA_CALL_POST vmaInvalidateAllocations(
return res;
}
-VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckCorruption(VmaAllocator allocator, uint32_t memoryTypeBits)
+VMA_CALL_PRE VkResult VMA_CALL_POST vmaCheckCorruption(
+ VmaAllocator allocator,
+ uint32_t memoryTypeBits)
{
VMA_ASSERT(allocator);
@@ -16306,29 +17643,33 @@ VMA_CALL_PRE VkBool32 VMA_CALL_POST vmaIsVirtualBlockEmpty(VmaVirtualBlock VMA_N
}
VMA_CALL_PRE void VMA_CALL_POST vmaGetVirtualAllocationInfo(VmaVirtualBlock VMA_NOT_NULL virtualBlock,
- VkDeviceSize offset, VmaVirtualAllocationInfo* VMA_NOT_NULL pVirtualAllocInfo)
+ VmaVirtualAllocation VMA_NOT_NULL 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(offset, *pVirtualAllocInfo);
+ virtualBlock->GetAllocationInfo(allocation, *pVirtualAllocInfo);
}
VMA_CALL_PRE VkResult VMA_CALL_POST vmaVirtualAllocate(VmaVirtualBlock VMA_NOT_NULL virtualBlock,
- const VmaVirtualAllocationCreateInfo* VMA_NOT_NULL pCreateInfo, VkDeviceSize* VMA_NOT_NULL pOffset)
+ const VmaVirtualAllocationCreateInfo* VMA_NOT_NULL pCreateInfo, VmaVirtualAllocation* VMA_NOT_NULL pAllocation,
+ VkDeviceSize* VMA_NULLABLE pOffset)
{
- VMA_ASSERT(virtualBlock != VK_NULL_HANDLE && pCreateInfo != VMA_NULL && pOffset != VMA_NULL);
+ 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, *pOffset);
+ return virtualBlock->Allocate(*pCreateInfo, *pAllocation, pOffset);
}
-VMA_CALL_PRE void VMA_CALL_POST vmaVirtualFree(VmaVirtualBlock VMA_NOT_NULL virtualBlock, VkDeviceSize offset)
+VMA_CALL_PRE void VMA_CALL_POST vmaVirtualFree(VmaVirtualBlock VMA_NOT_NULL virtualBlock, VmaVirtualAllocation VMA_NULLABLE allocation)
{
- VMA_ASSERT(virtualBlock != VK_NULL_HANDLE);
- VMA_DEBUG_LOG("vmaVirtualFree");
- VMA_DEBUG_GLOBAL_MUTEX_LOCK;
- virtualBlock->Free(offset);
+ if(virtualBlock != VMA_NULL)
+ {
+ 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)
@@ -16340,12 +17681,12 @@ VMA_CALL_PRE void VMA_CALL_POST vmaClearVirtualBlock(VmaVirtualBlock VMA_NOT_NUL
}
VMA_CALL_PRE void VMA_CALL_POST vmaSetVirtualAllocationUserData(VmaVirtualBlock VMA_NOT_NULL virtualBlock,
- VkDeviceSize offset, void* VMA_NULLABLE pUserData)
+ VmaVirtualAllocation VMA_NOT_NULL allocation, void* VMA_NULLABLE pUserData)
{
VMA_ASSERT(virtualBlock != VK_NULL_HANDLE);
VMA_DEBUG_LOG("vmaSetVirtualAllocationUserData");
VMA_DEBUG_GLOBAL_MUTEX_LOCK;
- virtualBlock->SetAllocationUserData(offset, pUserData);
+ virtualBlock->SetAllocationUserData(allocation, pUserData);
}
VMA_CALL_PRE void VMA_CALL_POST vmaCalculateVirtualBlockStats(VmaVirtualBlock VMA_NOT_NULL virtualBlock,
@@ -17586,14 +18927,14 @@ VkResult res = vmaCreateVirtualBlock(&blockCreateInfo, &block);
#VmaVirtualBlock object contains internal data structure that keeps track of free and occupied regions
using the same code as the main Vulkan memory allocator.
-However, there is no "virtual allocation" object.
-When you request a new allocation, a `VkDeviceSize` number is returned.
-It is an offset inside the block where the allocation has been placed, but it also uniquely identifies the allocation within this block.
+Similarly to #VmaAllocation for standard GPU allocations, there is #VmaVirtualAllocation type
+that represents an opaque handle to an allocation withing the virtual block.
-In order to make an allocation:
+In order to make such allocation:
-# Fill in #VmaVirtualAllocationCreateInfo structure.
--# Call vmaVirtualAllocate(). Get new `VkDeviceSize offset` that identifies the allocation.
+-# Call vmaVirtualAllocate(). Get new #VmaVirtualAllocation object that represents the allocation.
+ You can also receive `VkDeviceSize offset` that was assigned to the allocation.
Example:
@@ -17601,11 +18942,12 @@ Example:
VmaVirtualAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.size = 4096; // 4 KB
-VkDeviceSize allocOffset;
-res = vmaVirtualAllocate(block, &allocCreateInfo, &allocOffset);
+VmaVirtualAllocation alloc;
+VkDeviceSize offset;
+res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc, &offset);
if(res == VK_SUCCESS)
{
- // Use the 4 KB of your memory starting at allocOffset.
+ // Use the 4 KB of your memory starting at offset.
}
else
{
@@ -17616,8 +18958,8 @@ else
\section virtual_allocator_deallocation Deallocation
When no longer needed, an allocation can be freed by calling vmaVirtualFree().
-You can only pass to this function the exact offset that was previously returned by vmaVirtualAllocate()
-and not any other location within the memory.
+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.
@@ -17625,7 +18967,7 @@ However, if you don't want to call vmaVirtualFree() for each allocation, you can
a feature not available in normal Vulkan memory allocator. Example:
\code
-vmaVirtualFree(block, allocOffset);
+vmaVirtualFree(block, alloc);
vmaDestroyVirtualBlock(block);
\endcode
@@ -17643,20 +18985,20 @@ struct CustomAllocData
};
CustomAllocData* allocData = new CustomAllocData();
allocData->m_AllocName = "My allocation 1";
-vmaSetVirtualAllocationUserData(block, allocOffset, allocData);
+vmaSetVirtualAllocationUserData(block, alloc, allocData);
\endcode
-The pointer can later be fetched, along with allocation size, by passing the allocation offset to function
+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, allocOffset, &allocInfo);
+vmaGetVirtualAllocationInfo(block, alloc, &allocInfo);
delete (CustomAllocData*)allocInfo.pUserData;
-vmaVirtualFree(block, allocOffset);
+vmaVirtualFree(block, alloc);
\endcode
\section virtual_allocator_alignment_and_units Alignment and units
@@ -17670,8 +19012,8 @@ VmaVirtualAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.size = 4096; // 4 KB
allocCreateInfo.alignment = 4; // Returned offset must be a multiply of 4 B
-VkDeviceSize allocOffset;
-res = vmaVirtualAllocate(block, &allocCreateInfo, &allocOffset);
+VmaVirtualAllocation alloc;
+res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc, nullptr);
\endcode
Alignments of different allocations made from one block may vary.
@@ -17681,7 +19023,7 @@ It might be more convenient, but you need to make sure to use this new unit cons
- VmaVirtualBlockCreateInfo::size
- VmaVirtualAllocationCreateInfo::size and VmaVirtualAllocationCreateInfo::alignment
-- Using offset returned by vmaVirtualAllocate()
+- Using offset returned by vmaVirtualAllocate() or in VmaVirtualAllocationInfo::offset
\section virtual_allocator_statistics Statistics
diff --git a/src/Tests.cpp b/src/Tests.cpp
index 1ce4ad4..c937c71 100644
--- a/src/Tests.cpp
+++ b/src/Tests.cpp
@@ -75,6 +75,8 @@ static const char* AlgorithmToStr(uint32_t algorithm)
return "Linear";
case VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT:
return "Buddy";
+ case VMA_POOL_CREATE_TLSF_ALGORITHM_BIT:
+ return "TLSF";
case 0:
return "Default";
default:
@@ -2677,6 +2679,7 @@ static void TestVirtualBlocks()
const VkDeviceSize blockSize = 16 * MEGABYTE;
const VkDeviceSize alignment = 256;
+ VkDeviceSize offset;
// # Create block 16 MB
@@ -2686,50 +2689,55 @@ static void TestVirtualBlocks()
VmaVirtualBlock block;
TEST(vmaCreateVirtualBlock(&blockCreateInfo, &block) == VK_SUCCESS && block);
- // # Allocate 8 MB
+ // # Allocate 8 MB (also fetch offset from the allocation)
VmaVirtualAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.alignment = alignment;
allocCreateInfo.pUserData = (void*)(uintptr_t)1;
allocCreateInfo.size = 8 * MEGABYTE;
- VkDeviceSize alloc0Offset;
- TEST(vmaVirtualAllocate(block, &allocCreateInfo, &alloc0Offset) == VK_SUCCESS);
- TEST(alloc0Offset < blockSize);
+ VmaVirtualAllocation allocation0;
+ TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation0, &offset) == VK_SUCCESS);
// # Validate the allocation
- VmaVirtualAllocationInfo allocInfo = {};
- vmaGetVirtualAllocationInfo(block, alloc0Offset, &allocInfo);
- TEST(allocInfo.size == allocCreateInfo.size);
- TEST(allocInfo.pUserData = allocCreateInfo.pUserData);
+ VmaVirtualAllocationInfo allocInfo0 = {};
+ vmaGetVirtualAllocationInfo(block, allocation0, &allocInfo0);
+ TEST(allocInfo0.offset < blockSize);
+ TEST(allocInfo0.offset == offset);
+ TEST(allocInfo0.size == allocCreateInfo.size);
+ TEST(allocInfo0.pUserData = allocCreateInfo.pUserData);
// # Check SetUserData
- vmaSetVirtualAllocationUserData(block, alloc0Offset, (void*)(uintptr_t)2);
- vmaGetVirtualAllocationInfo(block, alloc0Offset, &allocInfo);
- TEST(allocInfo.pUserData = (void*)(uintptr_t)2);
+ vmaSetVirtualAllocationUserData(block, allocation0, (void*)(uintptr_t)2);
+ vmaGetVirtualAllocationInfo(block, allocation0, &allocInfo0);
+ TEST(allocInfo0.pUserData = (void*)(uintptr_t)2);
- // # Allocate 4 MB
+ // # Allocate 4 MB (also test passing null as pOffset during allocation)
allocCreateInfo.size = 4 * MEGABYTE;
- UINT64 alloc1Offset;
- TEST(vmaVirtualAllocate(block, &allocCreateInfo, &alloc1Offset) == VK_SUCCESS);
- TEST(alloc1Offset < blockSize);
- TEST(alloc1Offset + 4 * MEGABYTE <= alloc0Offset || alloc0Offset + 8 * MEGABYTE <= alloc1Offset); // Check if they don't overlap.
+ VmaVirtualAllocation allocation1;
+ TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation1, nullptr) == VK_SUCCESS);
+ VmaVirtualAllocationInfo allocInfo1 = {};
+ vmaGetVirtualAllocationInfo(block, allocation1, &allocInfo1);
+ TEST(allocInfo1.offset < blockSize);
+ TEST(allocInfo1.offset + 4 * MEGABYTE <= allocInfo0.offset || allocInfo0.offset + 8 * MEGABYTE <= allocInfo1.offset); // Check if they don't overlap.
// # Allocate another 8 MB - it should fail
allocCreateInfo.size = 8 * MEGABYTE;
- UINT64 alloc2Offset;
- TEST(vmaVirtualAllocate(block, &allocCreateInfo, &alloc2Offset) < 0);
- TEST(alloc2Offset == VK_WHOLE_SIZE);
+ VmaVirtualAllocation allocation2;
+ TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation2, nullptr) < 0);
+ TEST(allocation2 == (VmaVirtualAllocation)VK_WHOLE_SIZE);
// # Free the 4 MB block. Now allocation of 8 MB should succeed.
- vmaVirtualFree(block, alloc1Offset);
- TEST(vmaVirtualAllocate(block, &allocCreateInfo, &alloc2Offset) == VK_SUCCESS);
- TEST(alloc2Offset < blockSize);
- TEST(alloc2Offset + 4 * MEGABYTE <= alloc0Offset || alloc0Offset + 8 * MEGABYTE <= alloc2Offset); // Check if they don't overlap.
+ vmaVirtualFree(block, allocation1);
+ TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation2, nullptr) == VK_SUCCESS);
+ VmaVirtualAllocationInfo allocInfo2 = {};
+ vmaGetVirtualAllocationInfo(block, allocation2, &allocInfo2);
+ TEST(allocInfo2.offset < blockSize);
+ TEST(allocInfo2.offset + 4 * MEGABYTE <= allocInfo0.offset || allocInfo0.offset + 8 * MEGABYTE <= allocInfo2.offset); // Check if they don't overlap.
// # Calculate statistics
@@ -2753,34 +2761,36 @@ static void TestVirtualBlocks()
// # Free alloc0, leave alloc2 unfreed.
- vmaVirtualFree(block, alloc0Offset);
+ vmaVirtualFree(block, allocation0);
// # Test alignment
{
constexpr size_t allocCount = 10;
- VkDeviceSize allocOffset[allocCount] = {};
+ VmaVirtualAllocation allocations[allocCount] = {};
for(size_t i = 0; i < allocCount; ++i)
{
const bool alignment0 = i == allocCount - 1;
allocCreateInfo.size = i * 3 + 15;
allocCreateInfo.alignment = alignment0 ? 0 : 8;
- TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocOffset[i]) == VK_SUCCESS);
+ TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocations[i], nullptr) == VK_SUCCESS);
if(!alignment0)
{
- TEST(allocOffset[i] % allocCreateInfo.alignment == 0);
+ VmaVirtualAllocationInfo info;
+ vmaGetVirtualAllocationInfo(block, allocations[i], &info);
+ TEST(info.offset % allocCreateInfo.alignment == 0);
}
}
for(size_t i = allocCount; i--; )
{
- vmaVirtualFree(block, allocOffset[i]);
+ vmaVirtualFree(block, allocations[i]);
}
}
// # Final cleanup
- vmaVirtualFree(block, alloc2Offset);
+ vmaVirtualFree(block, allocation2);
vmaDestroyVirtualBlock(block);
{
@@ -2792,8 +2802,8 @@ static void TestVirtualBlocks()
for(size_t i = 0; i < 8; ++i)
{
- VkDeviceSize offset = 0;
- TEST(vmaVirtualAllocate(block, &allocCreateInfo, &offset) == VK_SUCCESS);
+ VmaVirtualAllocation allocation;
+ TEST(vmaVirtualAllocate(block, &allocCreateInfo, &allocation, nullptr) == VK_SUCCESS);
}
vmaClearVirtualBlock(block);
@@ -2808,7 +2818,7 @@ static void TestVirtualBlocksAlgorithms()
RandomNumberGenerator rand{3454335};
auto calcRandomAllocSize = [&rand]() -> VkDeviceSize { return rand.Generate() % 20 + 5; };
- for(size_t algorithmIndex = 0; algorithmIndex < 3; ++algorithmIndex)
+ for(size_t algorithmIndex = 0; algorithmIndex < 4; ++algorithmIndex)
{
// Create the block
VmaVirtualBlockCreateInfo blockCreateInfo = {};
@@ -2818,6 +2828,7 @@ static void TestVirtualBlocksAlgorithms()
{
case 1: blockCreateInfo.flags = VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT; break;
case 2: blockCreateInfo.flags = VMA_VIRTUAL_BLOCK_CREATE_BUDDY_ALGORITHM_BIT; break;
+ case 3: blockCreateInfo.flags = VMA_VIRTUAL_BLOCK_CREATE_TLSF_ALGORITHM_BIT; break;
}
VmaVirtualBlock block = nullptr;
VkResult res = vmaCreateVirtualBlock(&blockCreateInfo, &block);
@@ -2825,7 +2836,8 @@ static void TestVirtualBlocksAlgorithms()
struct AllocData
{
- VkDeviceSize offset, requestedSize, allocationSize;
+ VmaVirtualAllocation allocation;
+ VkDeviceSize allocOffset, requestedSize, allocationSize;
};
std::vector allocations;
@@ -2843,12 +2855,13 @@ static void TestVirtualBlocksAlgorithms()
AllocData alloc = {};
alloc.requestedSize = allocCreateInfo.size;
- res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.offset);
+ res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.allocation, nullptr);
TEST(res == VK_SUCCESS);
VmaVirtualAllocationInfo allocInfo;
- vmaGetVirtualAllocationInfo(block, alloc.offset, &allocInfo);
+ vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
TEST(allocInfo.size >= allocCreateInfo.size);
+ alloc.allocOffset = allocInfo.offset;
alloc.allocationSize = allocInfo.size;
allocations.push_back(alloc);
@@ -2858,7 +2871,7 @@ static void TestVirtualBlocksAlgorithms()
for(size_t i = 0; i < 5; ++i)
{
const size_t index = rand.Generate() % allocations.size();
- vmaVirtualFree(block, allocations[index].offset);
+ vmaVirtualFree(block, allocations[index].allocation);
allocations.erase(allocations.begin() + index);
}
@@ -2871,12 +2884,13 @@ static void TestVirtualBlocksAlgorithms()
AllocData alloc = {};
alloc.requestedSize = allocCreateInfo.size;
- res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.offset);
+ res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.allocation, nullptr);
TEST(res == VK_SUCCESS);
VmaVirtualAllocationInfo allocInfo;
- vmaGetVirtualAllocationInfo(block, alloc.offset, &allocInfo);
+ vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
TEST(allocInfo.size >= allocCreateInfo.size);
+ alloc.allocOffset = allocInfo.offset;
alloc.allocationSize = allocInfo.size;
allocations.push_back(alloc);
@@ -2892,13 +2906,14 @@ static void TestVirtualBlocksAlgorithms()
AllocData alloc = {};
alloc.requestedSize = allocCreateInfo.size;
- res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.offset);
+ res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc.allocation, nullptr);
TEST(res == VK_SUCCESS);
- TEST(alloc.offset % 16 == 0);
VmaVirtualAllocationInfo allocInfo;
- vmaGetVirtualAllocationInfo(block, alloc.offset, &allocInfo);
+ vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
+ TEST(allocInfo.offset % 16 == 0);
TEST(allocInfo.size >= allocCreateInfo.size);
+ alloc.allocOffset = allocInfo.offset;
alloc.allocationSize = allocInfo.size;
allocations.push_back(alloc);
@@ -2906,21 +2921,21 @@ static void TestVirtualBlocksAlgorithms()
// Check if the allocations don't overlap
std::sort(allocations.begin(), allocations.end(), [](const AllocData& lhs, const AllocData& rhs) {
- return lhs.offset < rhs.offset; });
+ return lhs.allocOffset < rhs.allocOffset; });
for(size_t i = 0; i < allocations.size() - 1; ++i)
{
- TEST(allocations[i+1].offset >= allocations[i].offset + allocations[i].allocationSize);
+ TEST(allocations[i+1].allocOffset >= allocations[i].allocOffset + allocations[i].allocationSize);
}
// Check pUserData
{
const AllocData& alloc = allocations.back();
VmaVirtualAllocationInfo allocInfo = {};
- vmaGetVirtualAllocationInfo(block, alloc.offset, &allocInfo);
+ vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
TEST((uintptr_t)allocInfo.pUserData == alloc.requestedSize * 10);
- vmaSetVirtualAllocationUserData(block, alloc.offset, (void*)(uintptr_t)666);
- vmaGetVirtualAllocationInfo(block, alloc.offset, &allocInfo);
+ vmaSetVirtualAllocationUserData(block, alloc.allocation, (void*)(uintptr_t)666);
+ vmaGetVirtualAllocationInfo(block, alloc.allocation, &allocInfo);
TEST((uintptr_t)allocInfo.pUserData == 666);
}
@@ -4229,7 +4244,7 @@ static void BenchmarkAlgorithms(FILE* file)
for(uint32_t emptyIndex = 0; emptyIndex < emptyCount; ++emptyIndex)
{
- for(uint32_t algorithmIndex = 0; algorithmIndex < 3; ++algorithmIndex)
+ for(uint32_t algorithmIndex = 0; algorithmIndex < 4; ++algorithmIndex)
{
uint32_t algorithm = 0;
switch(algorithmIndex)
@@ -4242,6 +4257,9 @@ static void BenchmarkAlgorithms(FILE* file)
case 2:
algorithm = VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT;
break;
+ case 3:
+ algorithm = VMA_POOL_CREATE_TLSF_ALGORITHM_BIT;
+ break;
default:
assert(0);
}