diff --git a/README.md b/README.md index b1e2dba..e89e5b5 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Additional features: - Convert this JSON dump into a picture to visualize your memory. See [tools/VmaDumpVis](tools/VmaDumpVis/README.md). - Debugging incorrect memory usage: Enable initialization of all allocated memory with a bit pattern to detect usage of uninitialized or freed memory. Enable validation of a magic number before and after every allocation to detect out-of-bounds memory corruption. - Record and replay sequence of calls to library functions to a file to check correctness, measure performance, and gather statistics. +- Support for interoperability with OpenGL. # Prequisites diff --git a/docs/html/allocation_annotation.html b/docs/html/allocation_annotation.html index 0c20818..9f39d7c 100644 --- a/docs/html/allocation_annotation.html +++ b/docs/html/allocation_annotation.html @@ -71,7 +71,7 @@ $(function() {
You can annotate allocations with your own information, e.g. for debugging purposes. To do that, fill VmaAllocationCreateInfo::pUserData field when creating an allocation. It's an opaque void*
pointer. You can use it e.g. as a pointer, some handle, index, key, ordinal number or any other value that would associate the allocation with your custom metadata.
You can annotate allocations with your own information, e.g. for debugging purposes. To do that, fill VmaAllocationCreateInfo::pUserData field when creating an allocation. It is an opaque void*
pointer. You can use it e.g. as a pointer, some handle, index, key, ordinal number or any other value that would associate the allocation with your custom metadata.
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.
Memory for allocations is reserved out of larger block of VkDeviceMemory
allocated from Vulkan internally. That's 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.:
pNext
chain.To use custom memory pools:
Ring buffer is available only in pools with one memory block - VmaPoolCreateInfo::maxBlockCount must be 1. Otherwise behavior is undefined.
There is another allocation algorithm that can be used with custom pools, called "buddy". Its internal data structure is based on a 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's 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 a tree node 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.
+There is another allocation algorithm that can be used with custom pools, called "buddy". Its internal data structure is based on a 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 a tree node 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 read "Buddy memory allocation" on Wikipedia or other sources that describe this concept in general.
diff --git a/docs/html/debugging_memory_usage.html b/docs/html/debugging_memory_usage.html index ce21667..b66e56f 100644 --- a/docs/html/debugging_memory_usage.html +++ b/docs/html/debugging_memory_usage.html @@ -99,7 +99,7 @@ Corruption detectionWhen 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's 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.
This number is validated automatically when the allocation is destroyed. If it is not equal to the expected value, VMA_ASSERT()
is executed. It clearly means that either CPU or GPU overwritten the memory outside of boundaries of the allocation, which indicates a serious bug.
You can also explicitly request checking margins of all allocations in all memory blocks that belong to specified memory types by using function vmaCheckCorruption(), or in memory blocks that belong to specified custom pool, by using function vmaCheckPoolCorruption().
Margin validation (corruption detection) works only for memory types that are HOST_VISIBLE
and HOST_COHERENT
.
If you want to implement your own, custom defragmentation algorithm, there is infrastructure prepared for that, but it is not exposed through the library API - you need to hack its source code. Here are steps needed to do this:
VmaDefragmentationAlgorithm
and implement your version of its pure virtual methods. See definition and comments of this class for details.VmaBlockMetadata_Generic
.VmaBlockMetadata_Generic
.VmaDefragmentationFlagBits
and use them in VmaDefragmentationInfo2::flags.VmaBlockVectorDefragmentationContext::Begin
to create object of your new class whenever needed. Features deliberately excluded from the scope of this library:
VkBufferCreateInfo
, VkImageCreateInfo
are not stored in VmaAllocation object.VkBufferCreateInfo
, VkImageCreateInfo
are not stored in VmaAllocation object.If your game oversubscribes video memory, if may work OK in previous-generation graphics APIs (DirectX 9, 10, 11, OpenGL) because resources are automatically paged to system RAM. In Vulkan you can't do it because when you run out of memory, an allocation just fails. If you have more data (e.g. textures) that can fit into VRAM and you don't need it all at once, you may want to upload them to GPU on demand and "push out" ones that are not used for a long time to make room for the new ones, effectively using VRAM (or a cartain memory pool) as a form of cache. Vulkan Memory Allocator can help you with that by supporting a concept of "lost allocations".
-To create an allocation that can become lost, include VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag in VmaAllocationCreateInfo::flags. Before using a buffer or image bound to such allocation in every new frame, you need to query it if it's not lost. To check it, call vmaTouchAllocation(). If the allocation is lost, you should not use it or buffer/image bound to it. You mustn't forget to destroy this allocation and this buffer/image. vmaGetAllocationInfo() can also be used for checking status of the allocation. Allocation is lost when returned VmaAllocationInfo::deviceMemory == VK_NULL_HANDLE
.
To create an allocation that can become lost, include VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag in VmaAllocationCreateInfo::flags. Before using a buffer or image bound to such allocation in every new frame, you need to query it if it is not lost. To check it, call vmaTouchAllocation(). If the allocation is lost, you should not use it or buffer/image bound to it. You mustn't forget to destroy this allocation and this buffer/image. vmaGetAllocationInfo() can also be used for checking status of the allocation. Allocation is lost when returned VmaAllocationInfo::deviceMemory == VK_NULL_HANDLE
.
To create an allocation that can make some other allocations lost to make room for it, use VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT flag. You will usually use both flags VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT and VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT at the same time.
Warning! Current implementation uses quite naive, brute force algorithm, which can make allocation calls that use VMA_ALLOCATION_CREATE_CAN_MAKE_OTHER_LOST_BIT flag quite slow. A new, more optimal algorithm and data structure to speed this up is planned for the future.
-Q: When interleaving creation of new allocations with usage of existing ones, how do you make sure that an allocation won't become lost while it's used in the current frame?
-It is ensured because vmaTouchAllocation() / vmaGetAllocationInfo() not only returns allocation status/parameters and checks whether it's not lost, but when it's not, it also atomically marks it as used in the current frame, which makes it impossible to become lost in that frame. It uses lockless algorithm, so it works fast and doesn't involve locking any internal mutex.
-Q: What if my allocation may still be in use by the GPU when it's rendering a previous frame while I already submit new frame on the CPU?
+Q: When interleaving creation of new allocations with usage of existing ones, how do you make sure that an allocation won't become lost while it is used in the current frame?
+It is ensured because vmaTouchAllocation() / vmaGetAllocationInfo() not only returns allocation status/parameters and checks whether it is not lost, but when it is not, it also atomically marks it as used in the current frame, which makes it impossible to become lost in that frame. It uses lockless algorithm, so it works fast and doesn't involve locking any internal mutex.
+Q: What if my allocation may still be in use by the GPU when it is rendering a previous frame while I already submit new frame on the CPU?
You can make sure that allocations "touched" by vmaTouchAllocation() / vmaGetAllocationInfo() will not become lost for a number of additional frames back from the current one by specifying this number as VmaAllocatorCreateInfo::frameInUseCount (for default memory pool) and VmaPoolCreateInfo::frameInUseCount (for custom pool).
Q: How do you inform the library when new frame starts?
You need to call function vmaSetCurrentFrameIndex().
@@ -97,7 +97,7 @@ $(function() {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:
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's 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:
If you want to attach VkExportMemoryAllocateInfoKHR
structure to pNext
chain of memory allocations made by the library:
It is recommended to create a Custom memory pools for such allocations. Define and fill in your VkExportMemoryAllocateInfoKHR
structure and attach it to VmaPoolCreateInfo::pMemoryAllocateNext while creating the custom pool. Please note that the structure must remain alive and unchanged for the whole lifetime of the VmaPool, not only while creating it, as no copy of the structure is made, but its original pointer is used for each allocation instead.
It is recommended to create Custom memory pools for such allocations. Define and fill in your VkExportMemoryAllocateInfoKHR
structure and attach it to VmaPoolCreateInfo::pMemoryAllocateNext while creating the custom pool. Please note that the structure must remain alive and unchanged for the whole lifetime of the VmaPool, not only while creating it, as no copy of the structure is made, but its original pointer is used for each allocation instead.
If you want to export all memory allocated by the library from certain memory types, also dedicated allocations or other allocations made from default pools, an alternative solution is to fill in VmaAllocatorCreateInfo::pTypeExternalMemoryHandleTypes. It should point to an array with VkExternalMemoryHandleTypeFlagsKHR
to be automatically passed by the library through VkExportMemoryAllocateInfoKHR
on each allocation made from a specific memory type. This is currently the only method to use if you need exported dedicated allocations, as they cannot be created out of custom pools. This will change in future versions of the library though.
You should not mix these two methods in a way that allows to apply both to the same memory type. Otherwise, VkExportMemoryAllocateInfoKHR
structure would be attached twice to the pNext
chain of VkMemoryAllocateInfo
.
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 a 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.
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
. This is currently the only method to use if you need exported dedicated allocations, as they cannot be created out of custom pools. This will change in future versions of the library though.
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.
To record sequence of calls to a file: Fill in VmaAllocatorCreateInfo::pRecordSettings member while creating VmaAllocator object. File is opened and written during whole lifetime of the allocator.
To replay file: Use VmaReplay - standalone command-line program. Precompiled binary can be found in "bin" directory. Its source can be found in "src/VmaReplay" directory. Its project is generated by Premake. Command line syntax is printed when the program is launched without parameters. Basic usage:
VmaReplay.exe MyRecording.csv -
Documentation of file format can be found in file: "docs/Recording file format.md". It's a human-readable, text file in CSV format (Comma Separated Values).
+Documentation of file format can be found in file: "docs/Recording file format.md". It is a human-readable, text file in CSV format (Comma Separated Values).
When developing a graphics-intensive game or program, it is important to avoid allocating more GPU memory than it's physically available. When the memory is over-committed, various bad things can happen, depending on the specific GPU, graphics driver, and operating system:
+When developing a graphics-intensive game or program, it is important to avoid allocating more GPU memory than it is physically available. When the memory is over-committed, various bad things can happen, depending on the specific GPU, graphics driver, and operating system:
Using this flag may increase performance because internal mutexes are not used.
Enables usage of VK_KHR_dedicated_allocation extension.
-The flag works only if VmaAllocatorCreateInfo::vulkanApiVersion == VK_API_VERSION_1_0
. When it's VK_API_VERSION_1_1
, the flag is ignored because the extension has been promoted to Vulkan 1.1.
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:
Enables usage of VK_KHR_bind_memory2 extension.
-The flag works only if VmaAllocatorCreateInfo::vulkanApiVersion == VK_API_VERSION_1_0
. When it's VK_API_VERSION_1_1
, the flag is ignored because the extension has been promoted to Vulkan 1.1.
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().
Returns VK_TRUE
if allocation is not lost and atomically marks it as used in current frame.
If the allocation has been created with VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag, this function returns VK_TRUE
if it's not in lost state, so it can still be used. It then also atomically "touches" the allocation - marks it as used in current frame, so that you can be sure it won't become lost in current frame or next frameInUseCount
frames.
If the allocation has been created with VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag, this function returns VK_TRUE
if it is not in lost state, so it can still be used. It then also atomically "touches" the allocation - marks it as used in current frame, so that you can be sure it won't become lost in current frame or next frameInUseCount
frames.
If the allocation is in lost state, the function returns VK_FALSE
. Memory of such allocation, as well as buffer or image bound to it, should not be used. Lost allocation and the buffer/image still need to be destroyed.
If the allocation has been created without VMA_ALLOCATION_CREATE_CAN_BECOME_LOST_BIT flag, this function always returns VK_TRUE
.
That's all. The extension will be automatically used whenever you create a buffer using vmaCreateBuffer() or image using vmaCreateImage().
+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 27564fc..acfb702 100644 --- a/include/vk_mem_alloc.h +++ b/include/vk_mem_alloc.h @@ -18535,6 +18535,8 @@ It can be useful if you want to: - Enforce particular, fixed size of Vulkan memory blocks. - Limit maximum amount of Vulkan memory allocated for that pool. - Reserve minimum or fixed amount of Vulkan memory always preallocated for that pool. +- Use extra parameters for a set of your allocations that are available in #VmaPoolCreateInfo but not in + #VmaAllocationCreateInfo - e.g., custom minimum alignment, custom `pNext` chain. To use custom memory pools: @@ -19351,7 +19353,7 @@ VMA provides some features that help with interoperability with OpenGL. If you want to attach `VkExportMemoryAllocateInfoKHR` structure to `pNext` chain of memory allocations made by the library: -It is recommended to create a \ref custom_memory_pools for such allocations. +It is recommended to create \ref custom_memory_pools for such allocations. Define and fill in your `VkExportMemoryAllocateInfoKHR` structure and attach it to VmaPoolCreateInfo::pMemoryAllocateNext while creating the custom pool. Please note that the structure must remain alive and unchanged for the whole lifetime of the #VmaPool, @@ -19376,7 +19378,7 @@ Buffers or images exported to a different API like OpenGL may require a differen higher than the one used by the library automatically, queried from functions like `vkGetBufferMemoryRequirements`. To impose such alignment: -It is recommended to create a \ref custom_memory_pools for such allocations. +It is recommended to create \ref custom_memory_pools for such allocations. Set VmaPoolCreateInfo::minAllocationAlignment member to the minimum alignment required for each allocation to be made out of this pool. The alignment actually used will be the maximum of this member and the alignment returned for the specific buffer or image