Vulkan Memory Allocator

VMA has full CMake support, but it is also a single-header library that requires users to "instantiate" it in a single translation unit. Isolating that into a wrapper library to minimize warning pollution etc, we create our own vma::vma target that compiles this source file:

// vk_mem_alloc.cpp
#define VMA_IMPLEMENTATION

#include <vk_mem_alloc.h>

Unlike VulkanHPP, VMA's interface is C only, thus we shall use our Scoped class template to wrap objects in RAII types. The first thing we need is a VmaAllocator, which is similar to a vk::Device or GLFWwindow*:

// vma.hpp
namespace lvk::vma {
struct Deleter {
  void operator()(VmaAllocator allocator) const noexcept;
};

using Allocator = Scoped<VmaAllocator, Deleter>;

[[nodiscard]] auto create_allocator(vk::Instance instance,
                                    vk::PhysicalDevice physical_device,
                                    vk::Device device) -> Allocator;
} // namespace lvk::vma

// vma.cpp
void Deleter::operator()(VmaAllocator allocator) const noexcept {
  vmaDestroyAllocator(allocator);
}

// ...
auto vma::create_allocator(vk::Instance const instance,
                           vk::PhysicalDevice const physical_device,
                           vk::Device const device) -> Allocator {
  auto const& dispatcher = VULKAN_HPP_DEFAULT_DISPATCHER;
  // need to zero initialize C structs, unlike VulkanHPP.
  auto vma_vk_funcs = VmaVulkanFunctions{};
  vma_vk_funcs.vkGetInstanceProcAddr = dispatcher.vkGetInstanceProcAddr;
  vma_vk_funcs.vkGetDeviceProcAddr = dispatcher.vkGetDeviceProcAddr;

  auto allocator_ci = VmaAllocatorCreateInfo{};
  allocator_ci.physicalDevice = physical_device;
  allocator_ci.device = device;
  allocator_ci.pVulkanFunctions = &vma_vk_funcs;
  allocator_ci.instance = instance;
  VmaAllocator ret{};
  auto const result = vmaCreateAllocator(&allocator_ci, &ret);
  if (result == VK_SUCCESS) { return ret; }

  throw std::runtime_error{"Failed to create Vulkan Memory Allocator"};
}

App stores and creates a vma::Allocator object:

// ...
vma::Allocator m_allocator{}; // anywhere between m_device and m_shader.

// ...
void App::create_allocator() {
  m_allocator = vma::create_allocator(*m_instance, m_gpu.device, *m_device);
}