Open 3D Engine Atom Gem API Reference  24.09
O3DE is an open-source, fully-featured, high-fidelity, modular 3D engine for building games and simulations, available to every industry.
Resource aliasing (overlap)

New explicit graphics APIs (Vulkan and Direct3D 12), thanks to manual memory management, give an opportunity to alias (overlap) multiple resources in the same region of memory - a feature not available in the old APIs (Direct3D 11, OpenGL). It can be useful to save video memory, but it must be used with caution.

For example, if you know the flow of your whole render frame in advance, you are going to use some intermediate textures or buffers only during a small range of render passes, and you know these ranges don't overlap in time, you can create these resources in the same place in memory, even if they have completely different parameters (width, height, format etc.).

Resource aliasing (overlap)

Such scenario is possible using D3D12MA, but you need to create your resources using special function D3D12MA::Allocator::CreateAliasingResource. Before that, you need to allocate memory with parameters calculated using formula:

  • allocation size = max(size of each resource)
  • allocation alignment = max(alignment of each resource)

Following example shows two different textures created in the same place in memory, allocated to fit largest of them.

D3D12_RESOURCE_DESC resDesc1 = {};
resDesc1.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
resDesc1.Alignment = 0;
resDesc1.Width = 1920;
resDesc1.Height = 1080;
resDesc1.DepthOrArraySize = 1;
resDesc1.MipLevels = 1;
resDesc1.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
resDesc1.SampleDesc.Count = 1;
resDesc1.SampleDesc.Quality = 0;
resDesc1.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
resDesc1.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
D3D12_RESOURCE_DESC resDesc2 = {};
resDesc2.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
resDesc2.Alignment = 0;
resDesc2.Width = 1024;
resDesc2.Height = 1024;
resDesc2.DepthOrArraySize = 1;
resDesc2.MipLevels = 0;
resDesc2.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
resDesc2.SampleDesc.Count = 1;
resDesc2.SampleDesc.Quality = 0;
resDesc2.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
resDesc2.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
const D3D12_RESOURCE_ALLOCATION_INFO allocInfo1 =
device->GetResourceAllocationInfo(0, 1, &resDesc1);
const D3D12_RESOURCE_ALLOCATION_INFO allocInfo2 =
device->GetResourceAllocationInfo(0, 1, &resDesc2);
D3D12_RESOURCE_ALLOCATION_INFO finalAllocInfo = {};
finalAllocInfo.Alignment = std::max(allocInfo1.Alignment, allocInfo2.Alignment);
finalAllocInfo.SizeInBytes = std::max(allocInfo1.SizeInBytes, allocInfo2.SizeInBytes);
D3D12MA::ALLOCATION_DESC allocDesc = {};
allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT;
allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES;
hr = allocator->AllocateMemory(&allocDesc, &finalAllocInfo, &alloc);
assert(alloc != NULL && alloc->GetHeap() != NULL);
ID3D12Resource* res1;
hr = allocator->CreateAliasingResource(
alloc,
0, // AllocationLocalOffset
&resDesc1,
D3D12_RESOURCE_STATE_COMMON,
NULL, // pOptimizedClearValue
IID_PPV_ARGS(&res1));
ID3D12Resource* res2;
hr = allocator->CreateAliasingResource(
alloc,
0, // AllocationLocalOffset
&resDesc2,
D3D12_RESOURCE_STATE_COMMON,
NULL, // pOptimizedClearValue
IID_PPV_ARGS(&res2));
// You can use res1 and res2, but not at the same time!
res2->Release();
res1->Release();
alloc->Release();
Represents single memory allocation.
Definition: D3D12MemAlloc.h:462
ID3D12Heap * GetHeap() const
Returns memory heap that the resource is created in.
Parameters of created D3D12MA::Allocation object. To be used with Allocator::CreateResource.
Definition: D3D12MemAlloc.h:278
D3D12_HEAP_FLAGS ExtraHeapFlags
Additional heap flags to be used when allocating memory.
Definition: D3D12MemAlloc.h:303
D3D12_HEAP_TYPE HeapType
The type of memory heap where the new allocation should be placed.
Definition: D3D12MemAlloc.h:287

Remember that using resouces that alias in memory requires proper synchronization. You need to issue a special barrier of type D3D12_RESOURCE_BARRIER_TYPE_ALIASING. You also need to treat a resource after aliasing as uninitialized - containing garbage data. For example, if you use res1 and then want to use res2, you need to first initialize res2 using either Clear, Discard, or Copy to the entire resource.

Additional considerations:

  • D3D12 also allows to interpret contents of memory between aliasing resources consistently in some cases, which is called "data inheritance". For details, see Microsoft documentation chapter "Memory Aliasing and Data Inheritance".
  • You can create more complex layout where different textures 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 in some further passes. To bind a resource at non-zero offset of an allocation, call D3D12MA::Allocator::CreateAliasingResource with appropriate value of AllocationLocalOffset parameter.
  • Resources of the three categories: buffers, textures with RENDER_TARGET or DEPTH_STENCIL flags, and all other textures, can be placed in the same memory only when allocator->GetD3D12Options().ResourceHeapTier >= D3D12_RESOURCE_HEAP_TIER_2. Otherwise they must be placed in different memory heap types, and thus aliasing them is not possible.

New explicit graphics APIs (Vulkan and Direct3D 12), thanks to manual memory management, give an opportunity to alias (overlap) multiple resources in the same region of memory - a feature not available in the old APIs (Direct3D 11, OpenGL). It can be useful to save video memory, but it must be used with caution.

For example, if you know the flow of your whole render frame in advance, you are going to use some intermediate textures or buffers only during a small range of render passes, and you know these ranges don't overlap in time, you can bind these resources to the same place in memory, even if they have completely different parameters (width, height, format etc.).

Resource aliasing (overlap)

Such scenario is possible using VMA, but you need to create your images manually. Then you need to calculate parameters of an allocation to be made using formula:

  • allocation size = max(size of each image)
  • allocation alignment = max(alignment of each image)
  • allocation memoryTypeBits = bitwise AND(memoryTypeBits of each image)

Following example shows two different images bound to the same place in memory, allocated to fit largest of them.

// A 512x512 texture to be sampled.
VkImageCreateInfo img1CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
img1CreateInfo.imageType = VK_IMAGE_TYPE_2D;
img1CreateInfo.extent.width = 512;
img1CreateInfo.extent.height = 512;
img1CreateInfo.extent.depth = 1;
img1CreateInfo.mipLevels = 10;
img1CreateInfo.arrayLayers = 1;
img1CreateInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
img1CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
img1CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
img1CreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
img1CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
// A full screen texture to be used as color attachment.
VkImageCreateInfo img2CreateInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
img2CreateInfo.imageType = VK_IMAGE_TYPE_2D;
img2CreateInfo.extent.width = 1920;
img2CreateInfo.extent.height = 1080;
img2CreateInfo.extent.depth = 1;
img2CreateInfo.mipLevels = 1;
img2CreateInfo.arrayLayers = 1;
img2CreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
img2CreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
img2CreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
img2CreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
img2CreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
VkImage img1;
res = vkCreateImage(device, &img1CreateInfo, nullptr, &img1);
VkImage img2;
res = vkCreateImage(device, &img2CreateInfo, nullptr, &img2);
vkGetImageMemoryRequirements(device, img1, &img1MemReq);
vkGetImageMemoryRequirements(device, img2, &img2MemReq);
VkMemoryRequirements finalMemReq = {};
finalMemReq.size = std::max(img1MemReq.size, img2MemReq.size);
finalMemReq.alignment = std::max(img1MemReq.alignment, img2MemReq.alignment);
finalMemReq.memoryTypeBits = img1MemReq.memoryTypeBits & img2MemReq.memoryTypeBits;
// Validate if(finalMemReq.memoryTypeBits != 0)
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
res = vmaAllocateMemory(allocator, &finalMemReq, &allocCreateInfo, &alloc, nullptr);
res = vmaBindImageMemory(allocator, alloc, img1);
res = vmaBindImageMemory(allocator, alloc, img2);
// You can use img1, img2 here, but not at the same time!
vmaFreeMemory(allocator, alloc);
vkDestroyImage(allocator, img2, nullptr);
vkDestroyImage(allocator, img1, nullptr);
VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory(VmaAllocator VMA_NOT_NULL allocator, VmaAllocation VMA_NOT_NULL allocation, VkImage VMA_NOT_NULL_NON_DISPATCHABLE image)
Binds image to allocation.
VMA_CALL_PRE void VMA_CALL_POST vmaFreeMemory(VmaAllocator VMA_NOT_NULL allocator, const VmaAllocation VMA_NULLABLE allocation)
Frees memory previously allocated using vmaAllocateMemory(), vmaAllocateMemoryForBuffer(),...
VMA_CALL_PRE VkResult VMA_CALL_POST vmaAllocateMemory(VmaAllocator VMA_NOT_NULL allocator, const VkMemoryRequirements *VMA_NOT_NULL pVkMemoryRequirements, const VmaAllocationCreateInfo *VMA_NOT_NULL pCreateInfo, VmaAllocation VMA_NULLABLE *VMA_NOT_NULL pAllocation, VmaAllocationInfo *VMA_NULLABLE pAllocationInfo)
General purpose memory allocation.
Definition: vulkan.h:10069
Definition: vulkan.h:9924
Parameters of new VmaAllocation.
Definition: vk_mem_alloc.h:1219
VkMemoryPropertyFlags preferredFlags
Flags that preferably should be set in a memory type chosen for an allocation.
Definition: vk_mem_alloc.h:1237
Represents single memory allocation.

VMA also provides convenience functions that create a buffer or image and bind it to memory represented by an existing VmaAllocation: vmaCreateAliasingBuffer(), vmaCreateAliasingBuffer2(), vmaCreateAliasingImage(), vmaCreateAliasingImage2(). Versions with "2" offer additional parameter allocationLocalOffset.

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 in 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.