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:407
VmaAllocatorCreateInfo
Description of a Allocator to be created.
Definition vk_mem_alloc.h:1068
VmaAllocatorCreateInfo::physicalDevice
VkPhysicalDevice physicalDevice
Vulkan physical device.
Definition vk_mem_alloc.h:1073
VmaAllocatorCreateInfo::flags
VmaAllocatorCreateFlags flags
Flags for created allocator. Use VmaAllocatorCreateFlagBits enum.
Definition vk_mem_alloc.h:1070
VmaAllocatorCreateInfo::pVulkanFunctions
const VmaVulkanFunctions * pVulkanFunctions
Pointers to Vulkan functions. Can be null.
Definition vk_mem_alloc.h:1116
VmaAllocatorCreateInfo::instance
VkInstance instance
Handle to Vulkan instance object.
Definition vk_mem_alloc.h:1121
VmaAllocatorCreateInfo::device
VkDevice device
Vulkan device.
Definition vk_mem_alloc.h:1076
VmaAllocatorCreateInfo::vulkanApiVersion
uint32_t vulkanApiVersion
Optional. Vulkan version that the application uses.
Definition vk_mem_alloc.h:1132
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:550
VmaAllocationCreateInfo
Parameters of new VmaAllocation.
Definition vk_mem_alloc.h:1291
VmaAllocationCreateInfo::usage
VmaMemoryUsage usage
Intended usage of memory.
Definition vk_mem_alloc.h:1299
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.