Vulkan Memory Allocator: Quick start

Vulkan Memory Allocator

Loading...
Searching...
No Matches
Quick start

# Project setup

Vulkan Memory Allocator comes in form of a "stb-style" single header file. While you can pull the entire repository e.g. as Git module, there is also Cmake script provided, you don't need to build it as a separate library project. You can add file "vk_mem_alloc.h" directly to your project and submit it to code repository next to your other source files.

"Single header" doesn't mean that everything is contained in C/C++ declarations, like it tends to be in case of inline functions or C++ templates. It means that implementation is bundled with interface in a single file and needs to be extracted using preprocessor macro. If you don't do it properly, it will result in linker errors.

To do it properly:

1. Include "vk_mem_alloc.h" file in each CPP file where you want to use the library. This includes declarations of all members of the library.
2. In exactly one CPP file define following macro before this include. It enables also internal definitions.

#define VMA_IMPLEMENTATION
#include "vk_mem_alloc.h"
vk_mem_alloc.h

It may be a good idea to create dedicated CPP file just for this purpose, e.g. "VmaUsage.cpp".

This library includes header <vulkan/vulkan.h>, which in turn includes <windows.h> on Windows. If you need some specific macros defined before including these headers (like WIN32_LEAN_AND_MEAN or WINVER for Windows, VK_USE_PLATFORM_WIN32_KHR for Vulkan), you must define them before every #include of this library. It may be a good idea to create a dedicate header file for this purpose, e.g. "VmaUsage.h", that will be included in other source files instead of VMA header directly.

This library is written in C++, but has C-compatible interface. Thus, you can include and use "vk_mem_alloc.h" in C or C++ code, but full implementation with VMA_IMPLEMENTATION macro must be compiled as C++, NOT as C. Some features of C++14 are used and required. Features of C++20 are used optionally when available. Some headers of standard C and C++ library are used, but STL containers, RTTI, or C++ exceptions are not used.

# Initialization

VMA offers library interface in a style similar to Vulkan, with object handles like VmaAllocation, structures describing parameters of objects to be created like VmaAllocationCreateInfo, and errors codes returned from functions using VkResult type.

The first and the main object that needs to be created is VmaAllocator. It represents the initialization of the entire library. Only one such object should be created per VkDevice. You should create it at program startup, after VkDevice was created, and before any device memory allocator needs to be made. It must be destroyed before VkDevice is destroyed.

At program startup:

1. Initialize Vulkan to have VkInstance, VkPhysicalDevice, VkDevice object.
2. Fill VmaAllocatorCreateInfo structure and call vmaCreateAllocator() to create VmaAllocator object.

Only members physicalDevice, device, instance are required. However, you should inform the library which Vulkan version do you use by setting VmaAllocatorCreateInfo::vulkanApiVersion and which extensions did you enable by setting VmaAllocatorCreateInfo::flags. Otherwise, VMA would use only features of Vulkan 1.0 core with no extensions. See below for details.

## Selecting Vulkan version

VMA supports Vulkan version down to 1.0, for backward compatibility. If you want to use higher version, you need to inform the library about it. This is a two-step process.

Step 1: Compile time. By default, VMA compiles with code supporting the highest Vulkan version found in the included <vulkan/vulkan.h> that is also supported by the library. If this is OK, you don't need to do anything. However, if you want to compile VMA as if only some lower Vulkan version was available, define macro VMA_VULKAN_VERSION before every #include "vk_mem_alloc.h". It should have decimal numeric value in form of ABBBCCC, where A = major, BBB = minor, CCC = patch Vulkan version. For example, to compile against Vulkan 1.2:

#define VMA_VULKAN_VERSION 1002000 // Vulkan 1.2
#include "vk_mem_alloc.h"

Step 2: Runtime. Even when compiled with higher Vulkan version available, VMA can use only features of a lower version, which is configurable during creation of the VmaAllocator object. By default, only Vulkan 1.0 is used. To initialize the allocator with support for higher Vulkan version, you need to set member VmaAllocatorCreateInfo::vulkanApiVersion to an appropriate value, e.g. using constants like VK_API_VERSION_1_2. See code sample below.

## Importing Vulkan functions

You may need to configure importing Vulkan functions. There are 4 ways to do this:

1. If you link with Vulkan static library (e.g. "vulkan-1.lib" on Windows):
   - You don't need to do anything.
   - VMA will use these, as macro VMA_STATIC_VULKAN_FUNCTIONS is defined to 1 by default.
2. If you want VMA to fetch pointers to Vulkan functions dynamically using vkGetInstanceProcAddr, vkGetDeviceProcAddr (this is the option presented in the example below):
   - Define VMA_STATIC_VULKAN_FUNCTIONS to 0, VMA_DYNAMIC_VULKAN_FUNCTIONS to 1.
   - Provide pointers to these two functions via VmaVulkanFunctions::vkGetInstanceProcAddr, VmaVulkanFunctions::vkGetDeviceProcAddr.
   - The library will fetch pointers to all other functions it needs internally.
3. If you fetch pointers to all Vulkan functions in a custom way:
   - Define VMA_STATIC_VULKAN_FUNCTIONS and VMA_DYNAMIC_VULKAN_FUNCTIONS to 0.
   - Pass these pointers via structure VmaVulkanFunctions.
4. If you use volk library:
   - Define VMA_STATIC_VULKAN_FUNCTIONS and VMA_DYNAMIC_VULKAN_FUNCTIONS to 0.
   - Use function vmaImportVulkanFunctionsFromVolk() to fill in the structure VmaVulkanFunctions. For more information, see the description of this function.

## Enabling extensions

VMA can automatically use following Vulkan extensions. If you found them available on the selected physical device and you enabled them while creating VkInstance / VkDevice object, inform VMA about their availability by setting appropriate flags in VmaAllocatorCreateInfo::flags.

Vulkan extension | VMA flag
--- | ---
VK_KHR_dedicated_allocation | VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT
VK_KHR_bind_memory2 | VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT
VK_KHR_maintenance4 | VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE4_BIT
VK_KHR_maintenance5 | VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT
VK_EXT_memory_budget | VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT
VK_KHR_buffer_device_address | VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT
VK_EXT_memory_priority | VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT
VK_AMD_device_coherent_memory | VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT
VK_KHR_external_memory_win32 | VMA_ALLOCATOR_CREATE_KHR_EXTERNAL_MEMORY_WIN32_BIT

Example with fetching pointers to Vulkan functions dynamically:

#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
#include "vk_mem_alloc.h"
...
VmaVulkanFunctions vulkanFunctions = {};
vulkanFunctions.vkGetInstanceProcAddr = &vkGetInstanceProcAddr;
vulkanFunctions.vkGetDeviceProcAddr = &vkGetDeviceProcAddr;
VmaAllocatorCreateInfo allocatorCreateInfo = {};
allocatorCreateInfo.flags = VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
allocatorCreateInfo.vulkanApiVersion = VK_API_VERSION_1_2;
allocatorCreateInfo.physicalDevice = physicalDevice;
allocatorCreateInfo.device = device;
allocatorCreateInfo.instance = instance;
allocatorCreateInfo.pVulkanFunctions = &vulkanFunctions;
VmaAllocator allocator;
vmaCreateAllocator(&allocatorCreateInfo, &allocator);
// Entire program...
// At the end, don't forget to:
vmaDestroyAllocator(allocator);
vmaCreateAllocator
VkResult vmaCreateAllocator(const VmaAllocatorCreateInfo *pCreateInfo, VmaAllocator *pAllocator)
Creates VmaAllocator object.
vmaDestroyAllocator
void vmaDestroyAllocator(VmaAllocator allocator)
Destroys allocator object.
VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT
@ VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT
Definition vk_mem_alloc.h:420
VmaAllocatorCreateInfo
Description of a Allocator to be created.
Definition vk_mem_alloc.h:1085
VmaAllocatorCreateInfo::physicalDevice
VkPhysicalDevice physicalDevice
Vulkan physical device.
Definition vk_mem_alloc.h:1090
VmaAllocatorCreateInfo::flags
VmaAllocatorCreateFlags flags
Flags for created allocator. Use VmaAllocatorCreateFlagBits enum.
Definition vk_mem_alloc.h:1087
VmaAllocatorCreateInfo::pVulkanFunctions
const VmaVulkanFunctions * pVulkanFunctions
Pointers to Vulkan functions. Can be null.
Definition vk_mem_alloc.h:1133
VmaAllocatorCreateInfo::instance
VkInstance instance
Handle to Vulkan instance object.
Definition vk_mem_alloc.h:1138
VmaAllocatorCreateInfo::device
VkDevice device
Vulkan device.
Definition vk_mem_alloc.h:1093
VmaAllocatorCreateInfo::vulkanApiVersion
uint32_t vulkanApiVersion
Optional. Vulkan version that the application uses.
Definition vk_mem_alloc.h:1149
VmaAllocator
Represents main object of this library initialized.

## Other configuration options

There are additional configuration options available through preprocessor macros that you can define before including VMA header and through parameters passed in VmaAllocatorCreateInfo. They include a possibility to use your own callbacks for host memory allocations (VkAllocationCallbacks), callbacks for device memory allocations (instead of vkAllocateMemory, vkFreeMemory), or your custom VMA_ASSERT macro, among others. For more information, see: Configuration.

# Resource allocation

When you want to create a buffer or image:

1. Fill VkBufferCreateInfo / VkImageCreateInfo structure.
2. Fill VmaAllocationCreateInfo structure.
3. Call vmaCreateBuffer() / vmaCreateImage() to get VkBuffer/VkImage with memory already allocated and bound to it, plus VmaAllocation objects that represents its underlying memory.

VkBufferCreateInfo bufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufferInfo.size = 65536;
bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VmaAllocationCreateInfo allocInfo = {};
allocInfo.usage = VMA_MEMORY_USAGE_AUTO;
VkBuffer buffer;
VmaAllocation allocation;
vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);
vmaCreateBuffer
VkResult vmaCreateBuffer(VmaAllocator allocator, const VkBufferCreateInfo *pBufferCreateInfo, const VmaAllocationCreateInfo *pAllocationCreateInfo, VkBuffer *pBuffer, VmaAllocation *pAllocation, VmaAllocationInfo *pAllocationInfo)
Creates a new VkBuffer, allocates and binds memory for it.
VMA_MEMORY_USAGE_AUTO
@ VMA_MEMORY_USAGE_AUTO
Definition vk_mem_alloc.h:563
VmaAllocationCreateInfo
Parameters of new VmaAllocation.
Definition vk_mem_alloc.h:1308
VmaAllocationCreateInfo::usage
VmaMemoryUsage usage
Intended usage of memory.
Definition vk_mem_alloc.h:1316
VmaAllocation
Represents single memory allocation.

Don't forget to destroy your buffer and allocation objects when no longer needed:

vmaDestroyBuffer(allocator, buffer, allocation);
vmaDestroyBuffer
void vmaDestroyBuffer(VmaAllocator allocator, VkBuffer buffer, VmaAllocation allocation)
Destroys Vulkan buffer and frees allocated memory.

If you need to map the buffer, you must set flag VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT or VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT in VmaAllocationCreateInfo::flags. There are many additional parameters that can control the choice of memory type to be used for the allocation and other features. For more information, see documentation chapters: Choosing memory type, Memory mapping.