Allocation module

The allocator module encompasses everything required for the operation/use of this project's rather complex GPU memory management subsystem.

Contents

In Vulkan, allocating VkDeviceMemory objects for every single individual resource requiring GPU memory is wasteful, and not practical: there is a limit in each Vulkan implementation to just how many allocations can exist at one time (sometimes as low as 1024). Thus, we will instead allocate large chunks of memory when a previously unallocated memory type is requested: from there, resources (like buffers, images, etc) will bind to subregions of this larger memory object. Binding is much faster than allocation, and it is also much quicker to de-allocate resources since this only involves registering a newly freed location in this subsystem.

The allocator object is spawned as a member of the LogicalDevice class, but it can be accessed by anyone that can access a logical device. Its relatively safe to access, and should be thread-safe. Utility methods exist to simplify the creation and allocation of VkImage and VkBuffer objects as much as possible, as well.

This module also is minimally complete, at best. It is robust enough to not fail in common usage, and currently has functioned wonderfully in most tasks thrown at it. However, the todo list details stuff that still needs to be done (e.g, further splitting pools and creating some kind of defragmentation system).

Lastly, huge credit to GPU-Open as this is primarily just a slightly more object-oriented/"Modern C++" styled implementation of their excellent memory allocator. This would not have been possible without their work: https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator

Classes

class vpr::Allocation

Allocation class represents a singular allocation: can be a private allocation (i.e, only user of attached DeviceMemory) or a block allocation (bound to sub-region of device memory)
struct vpr::AllocationRequirements
This struct is the primary item submitted to allocator methods for resource creation.
class vpr::Allocator
The primary interface and class of this subsystem.
class vpr::AllocationCollection
An allocation collection is just a vector of MemoryBlocks of the same type.
class vpr::MemoryBlock
A MemoryBlock is a large contiguous region of Vulkan memory of a uniform type (device local, host coherent, host visible, etc) that other objects bind to subregions of.
struct vpr::Suballocation
During the process of finding a suitable region to bind to, we need to store things like "SuballocationType", which helps keep track of the precise kind of memory we are looking for and lets us know if its optimally/linearly tiled.
struct vpr::SuballocationRequest
Represents an in-progress allocation that we will shortly attempt to assign a slot to.

Enums

enum class ValidationCode: uint8_t { VALIDATION_PASSED = 0, NULL_MEMORY_HANDLE, ZERO_MEMORY_SIZE, INCORRECT_SUBALLOC_OFFSET, NEED_MERGE_SUBALLOCS, FREE_SUBALLOC_COUNT_MISMATCH, USED_SUBALLOC_IN_FREE_LIST, FREE_SUBALLOC_SORT_INCORRECT, FINAL_SIZE_MISMATCH, FINAL_FREE_SIZE_MISMATCH }
These validation codes are returned by the memory validation routine, giving information on the error encountered.

Functions

static auto CheckBlocksOnSamePage(const VkDeviceSize& item_a_offset, const VkDeviceSize& item_a_size, const VkDeviceSize& item_b_offset, const VkDeviceSize& page_size) -> bool constexpr
Taken from the Vulkan specification, section 11.6. Essentially, we need to ensure that linear and non-linear resources are properly placed on separate memory pages so that they avoid any accidental aliasing.
static auto CheckBufferImageGranularityConflict(SuballocationType type_a, SuballocationType type_b) -> bool constexpr
Checks to make sure the two objects of type "type_a" and "type_b" wouldn't cause a conflict with the buffer-image granularity values.
auto operator<<(std::ostream& os, const ValidationCode& code) -> std::ostream&
This is a simple and common overload to print enum info to any stream (this also works, FYI, with easylogging++).

Enum documentation

enum class ValidationCode: uint8_t

These validation codes are returned by the memory validation routine, giving information on the error encountered.

They will also be printed to the console, if it is enabled, and logged to the log file as well.

Enumerators
VALIDATION_PASSED
NULL_MEMORY_HANDLE

Suballocation's memory handle is invalid.

ZERO_MEMORY_SIZE

Suballocation's memory size is zero.

INCORRECT_SUBALLOC_OFFSET

Offset of suballocation is incorrect: it may overlap with another, or it may be placed beyond the range of the allocation.

NEED_MERGE_SUBALLOCS

Two adjacent free suballoctions: merge them into one bigger suballocation.

FREE_SUBALLOC_COUNT_MISMATCH

We found more free suballocations while validating than there are in the free suballocation list.

USED_SUBALLOC_IN_FREE_LIST

Non-free suballocation in free suballocation list.

FREE_SUBALLOC_SORT_INCORRECT

Free suballocation list is sorted by available space descending: sorting is incorrect and smaller free region is before larger free region.

FINAL_SIZE_MISMATCH

Calculated offset as sum of all suballoc sizes is not equal to allocations total size.

FINAL_FREE_SIZE_MISMATCH

Calculated total available size doesn't match stored available size.

Function documentation

static bool CheckBlocksOnSamePage(const VkDeviceSize& item_a_offset, const VkDeviceSize& item_a_size, const VkDeviceSize& item_b_offset, const VkDeviceSize& page_size) constexpr

Taken from the Vulkan specification, section 11.6 Essentially, we need to ensure that linear and non-linear resources are properly placed on separate memory pages so that they avoid any accidental aliasing.

Parameters
item_a_offset non-linear object's offset
item_a_size non-linear object's size
item_b_offset linear object's offset
page_size almost universally tends to be the bufferImageGranularity value retrieved by the parent Allocator class.

Linear resources are just those that could be read like any other memory region, without any particular optimization for size or access speed. Optimally tiled resources are those that are tiled either by the hardware drivers, or the Vulkan implementation. Think of things like Z-Order curve encoding for texture data, or block-based compression for DDS/KTX texture formats.

static bool CheckBufferImageGranularityConflict(SuballocationType type_a, SuballocationType type_b) constexpr

Checks to make sure the two objects of type "type_a" and "type_b" wouldn't cause a conflict with the buffer-image granularity values.

Returns true if conflict, false if no conflict. This is unlike the CheckBlocksOnSamePage method, in that it doesn't check memory location and alignment values, merely comparing the resource types for incompatabilities. This is used to avoid the more detailed checks like CheckBlocksOnSamePage (and the corrections required if this also fails)

BufferImageGranularity specifies interactions between linear and non-linear resources, so we check based on those.

std::ostream& operator<<(std::ostream& os, const ValidationCode& code)

This is a simple and common overload to print enum info to any stream (this also works, FYI, with easylogging++).

A note to make, however, is that compilers running at W4/Wall warning levels will warn that this method is unreferenced in release mode, if validation is not forced. As the memory verification routine is not used in this case, much of that code will fold away but should still be left in for debug builds.