Open 3D Engine Atom Gem API Reference 25.05.0
O3DE is an open-source, fully-featured, high-fidelity, modular 3D engine for building games and simulations, available to every industry.
Quick start

Project setup and initialization

This is a small, standalone C++ library. It consists of a pair of 2 files: "D3D12MemAlloc.h" header file with public interface and "D3D12MemAlloc.cpp" with internal implementation. The only external dependencies are WinAPI, Direct3D 12, and parts of C/C++ standard library (but STL containers, exceptions, or RTTI are not used).

The library is developed and tested using Microsoft Visual Studio 2019, but it should work with other compilers as well. It is designed for 64-bit code.

To use the library in your project:

(1.) Copy files D3D12MemAlloc.cpp, D3D12MemAlloc.h to your project.

(2.) Make D3D12MemAlloc.cpp compiling as part of the project, as C++ code.

(3.) Include library header in each CPP file that needs to use the library.

#include "D3D12MemAlloc.h"

(4.) Right after you created ID3D12Device, fill D3D12MA::ALLOCATOR_DESC structure and call function D3D12MA::CreateAllocator to create the main D3D12MA::Allocator object.

Please note that all symbols of the library are declared inside #D3D12MA namespace.

IDXGIAdapter* adapter = (...)
ID3D12Device* device = (...)
D3D12MA::ALLOCATOR_DESC allocatorDesc = {};
allocatorDesc.pDevice = device;
allocatorDesc.pAdapter = adapter;
D3D12MA::Allocator* allocator;
HRESULT hr = D3D12MA::CreateAllocator(&allocatorDesc, &allocator);
Represents main object of this library initialized for particular ID3D12Device.
Definition D3D12MemAlloc.h:1068
Parameters of created Allocator object. To be used with CreateAllocator().
Definition D3D12MemAlloc.h:1029
IDXGIAdapter * pAdapter
Definition D3D12MemAlloc.h:1055
ID3D12Device * pDevice
Definition D3D12MemAlloc.h:1037

(5.) Right before destroying the D3D12 device, destroy the allocator object.

Objects of this library must be destroyed by calling Release method. They are somewhat compatible with COM: they implement IUnknown interface with its virtual methods: AddRef, Release, QueryInterface, and they are reference-counted internally. You can use smart pointers designed for COM with objects of this library - e.g. CComPtr or Microsoft::WRL::ComPtr. The reference counter is thread-safe. QueryInterface method supports only IUnknown, as classes of this library don't define their own GUIDs.

allocator->Release();

Creating resources

To use the library for creating resources (textures and buffers), call method D3D12MA::Allocator::CreateResource in the place where you would previously call ID3D12Device::CreateCommittedResource.

The function has similar syntax, but it expects structure D3D12MA::ALLOCATION_DESC to be passed along with D3D12_RESOURCE_DESC and other parameters for created resource. This structure describes parameters of the desired memory allocation, including choice of D3D12_HEAP_TYPE.

The function returns a new object of type D3D12MA::Allocation. It represents allocated memory and can be queried for size, offset, ID3D12Heap. It also holds a reference to the ID3D12Resource, which can be accessed by calling D3D12MA::Allocation::GetResource().

D3D12_RESOURCE_DESC resourceDesc = {};
resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
resourceDesc.Alignment = 0;
resourceDesc.Width = 1024;
resourceDesc.Height = 1024;
resourceDesc.DepthOrArraySize = 1;
resourceDesc.MipLevels = 1;
resourceDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
resourceDesc.SampleDesc.Count = 1;
resourceDesc.SampleDesc.Quality = 0;
resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
D3D12MA::ALLOCATION_DESC allocationDesc = {};
allocationDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT;
D3D12MA::Allocation* allocation;
HRESULT hr = allocator->CreateResource(
&allocationDesc,
&resourceDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
NULL,
&allocation,
IID_NULL, NULL);
// Use allocation->GetResource()...
Represents single memory allocation.
Definition D3D12MemAlloc.h:462
HRESULT CreateResource(const ALLOCATION_DESC *pAllocDesc, const D3D12_RESOURCE_DESC *pResourceDesc, D3D12_RESOURCE_STATES InitialResourceState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, Allocation **ppAllocation, REFIID riidResource, void **ppvResource)
Allocates memory and creates a D3D12 resource (buffer or texture). This is the main allocation functi...
Parameters of created D3D12MA::Allocation object. To be used with Allocator::CreateResource.
Definition D3D12MemAlloc.h:278
D3D12_HEAP_TYPE HeapType
The type of memory heap where the new allocation should be placed.
Definition D3D12MemAlloc.h:287

You need to release the allocation object when no longer needed. This will also release the D3D12 resource.

allocation->Release();

The advantage of using the allocator instead of creating committed resource, and the main purpose of this library, is that it can decide to allocate bigger memory heap internally using ID3D12Device::CreateHeap and place multiple resources in it, at different offsets, using ID3D12Device::CreatePlacedResource. The library manages its own collection of allocated memory blocks (heaps) and remembers which parts of them are occupied and which parts are free to be used for new resources.

It is important to remember that resources created as placed don't have their memory initialized to zeros, but may contain garbage data, so they need to be fully initialized before usage, e.g. using Clear (ClearRenderTargetView), Discard (DiscardResource), or copy (CopyResource).

The library also automatically handles resource heap tier. When D3D12_FEATURE_DATA_D3D12_OPTIONS::ResourceHeapTier equals D3D12_RESOURCE_HEAP_TIER_1, resources of 3 types: buffers, textures that are render targets or depth-stencil, and other textures must be kept in separate heaps. When D3D12_RESOURCE_HEAP_TIER_2, they can be kept together. By using this library, you don't need to handle this manually.

Resource reference counting

ID3D12Resource and other interfaces of Direct3D 12 use COM, so they are reference-counted. Objects of this library are reference-counted as well. An object of type D3D12MA::Allocation remembers the resource (buffer or texture) that was created together with this memory allocation and holds a reference to the ID3D12Resource object. (Note this is a difference to Vulkan Memory Allocator, where a VmaAllocation object has no connection with the buffer or image that was created with it.) Thus, it is important to manage the resource reference counter properly.

The simplest use case is shown in the code snippet above. When only D3D12MA::Allocation object is obtained from a function call like D3D12MA::Allocator::CreateResource, it remembers the ID3D12Resource that was created with it and holds a reference to it. The resource can be obtained by calling allocation->GetResource(), which doesn't increment the resource reference counter. Calling allocation->Release() will decrease the resource reference counter, which is = 1 in this case, so the resource will be released.

Second option is to retrieve a pointer to the resource along with D3D12MA::Allocation. Last parameters of the resource creation function can be used for this purpose.

D3D12MA::Allocation* allocation;
ID3D12Resource* resource;
HRESULT hr = allocator->CreateResource(
&allocationDesc,
&resourceDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
NULL,
&allocation,
IID_PPV_ARGS(&resource));
// Use resource...

In this case, returned pointer resource is equal to allocation->GetResource(), but the creation function additionally increases resource reference counter for the purpose of returning it from this call (it actually calls QueryInterface internally), so the resource will have the counter = 2. The resource then need to be released along with the allocation, in this particular order, to make sure the resource is destroyed before its memory heap can potentially be freed.

resource->Release();
allocation->Release();

More advanced use cases are possible when we consider that an D3D12MA::Allocation object can just hold a reference to any resource. It can be changed by calling D3D12MA::Allocation::SetResource. This function releases the old resource and calls AddRef on the new one.

Special care must be taken when performing defragmentation. The new resource created at the destination place should be set as pass.pMoves[i].pDstTmpAllocation->SetResource(newRes), but it is moved to the source allocation at end of the defragmentation pass, while the old resource accessible through pass.pMoves[i].pSrcAllocation->GetResource() is then released. For more information, see documentation chapter Defragmentation.

Mapping memory

The process of getting regular CPU-side pointer to the memory of a resource in Direct3D is called "mapping". There are rules and restrictions to this process, as described in D3D12 documentation of ID3D12Resource::Map method.

Mapping happens on the level of particular resources, not entire memory heaps, and so it is out of scope of this library. Just as the documentation of the Map function says:

  • Returned pointer refers to data of particular subresource, not entire memory heap.
  • You can map same resource multiple times. It is ref-counted internally.
  • Mapping is thread-safe.
  • Unmapping is not required before resource destruction.
  • Unmapping may not be required before using written data - some heap types on some platforms support resources persistently mapped.

When using this library, you can map and use your resources normally without considering whether they are created as committed resources or placed resources in one large heap.

Example for buffer created and filled in UPLOAD heap type:

const UINT64 bufSize = 65536;
const float* bufData = (...);
D3D12_RESOURCE_DESC resourceDesc = {};
resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
resourceDesc.Alignment = 0;
resourceDesc.Width = bufSize;
resourceDesc.Height = 1;
resourceDesc.DepthOrArraySize = 1;
resourceDesc.MipLevels = 1;
resourceDesc.Format = DXGI_FORMAT_UNKNOWN;
resourceDesc.SampleDesc.Count = 1;
resourceDesc.SampleDesc.Quality = 0;
resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
D3D12MA::ALLOCATION_DESC allocationDesc = {};
allocationDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD;
D3D12Resource* resource;
D3D12MA::Allocation* allocation;
HRESULT hr = allocator->CreateResource(
&allocationDesc,
&resourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
NULL,
&allocation,
IID_PPV_ARGS(&resource));
void* mappedPtr;
hr = resource->Map(0, NULL, &mappedPtr);
memcpy(mappedPtr, bufData, bufSize);
resource->Unmap(0, NULL);

Project setup and initialization

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"

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 3 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):
  3. If you fetch pointers to all Vulkan functions in a custom way, e.g. using some loader like Volk:
    • Define VMA_STATIC_VULKAN_FUNCTIONS and VMA_DYNAMIC_VULKAN_FUNCTIONS to 0.
    • Pass these pointers via structure VmaVulkanFunctions.

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

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.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:
VMA_CALL_PRE void VMA_CALL_POST vmaDestroyAllocator(VmaAllocator VMA_NULLABLE allocator)
Destroys allocator object.
VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateAllocator(const VmaAllocatorCreateInfo *VMA_NOT_NULL pCreateInfo, VmaAllocator VMA_NULLABLE *VMA_NOT_NULL pAllocator)
Creates VmaAllocator object.
@ VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT
Definition vk_mem_alloc.h:393
Description of a Allocator to be created.
Definition vk_mem_alloc.h:1040
VmaAllocatorCreateFlags flags
Flags for created allocator. Use VmaAllocatorCreateFlagBits enum.
Definition vk_mem_alloc.h:1042
const VmaVulkanFunctions *VMA_NULLABLE pVulkanFunctions
Pointers to Vulkan functions. Can be null.
Definition vk_mem_alloc.h:1088
VkInstance VMA_NOT_NULL instance
Handle to Vulkan instance object.
Definition vk_mem_alloc.h:1093
VkDevice VMA_NOT_NULL device
Vulkan device.
Definition vk_mem_alloc.h:1048
VkPhysicalDevice VMA_NOT_NULL physicalDevice
Vulkan physical device.
Definition vk_mem_alloc.h:1045
uint32_t vulkanApiVersion
Optional. Vulkan version that the application uses.
Definition vk_mem_alloc.h:1104
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 = {};
VkBuffer buffer;
VmaAllocation allocation;
vmaCreateBuffer(allocator, &bufferInfo, &allocInfo, &buffer, &allocation, nullptr);
VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer(VmaAllocator VMA_NOT_NULL allocator, const VkBufferCreateInfo *VMA_NOT_NULL pBufferCreateInfo, const VmaAllocationCreateInfo *VMA_NOT_NULL pAllocationCreateInfo, VkBuffer VMA_NULLABLE_NON_DISPATCHABLE *VMA_NOT_NULL pBuffer, VmaAllocation VMA_NULLABLE *VMA_NOT_NULL pAllocation, VmaAllocationInfo *VMA_NULLABLE pAllocationInfo)
Creates a new VkBuffer, allocates and binds memory for it.
@ VMA_MEMORY_USAGE_AUTO
Definition vk_mem_alloc.h:527
Definition vulkan.h:9995
Parameters of new VmaAllocation.
Definition vk_mem_alloc.h:1263
VmaMemoryUsage usage
Intended usage of memory.
Definition vk_mem_alloc.h:1271
Represents single memory allocation.

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

vmaDestroyBuffer(allocator, buffer, allocation);
VMA_CALL_PRE void VMA_CALL_POST vmaDestroyBuffer(VmaAllocator VMA_NOT_NULL allocator, VkBuffer VMA_NULLABLE_NON_DISPATCHABLE buffer, VmaAllocation VMA_NULLABLE 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.