Using helper libraries for Vulkan
Having full control of your graphics hardware sounds cool, but the extensive amount of code for the basic initialization might scare people who are new to Vulkan. Writing about 1,000 lines of code just to get a colored triangle onto the screen may sound frightening.
To reduce the code a bit, two helper libraries are integrated:
- vk-bootstrap, the Vulkan Bootstrap, which is for the first steps of creating the instance, device, and swapchain
- The Vulkan Memory Allocator (VMA), taking some of the complexity out of the memory management out of the code
We start with the simplification of the creation of the most important objects.
Initializing Vulkan via vk-bootstrap
If you visit the GitHub page for vk-bootstrap at https://github.com/charles-lunarg/vk-bootstrap, the benefits are listed right at the top of the README
file. It will help you with all the steps needed for the following:
- Instance creation, enabling the validation layers, if desired
- Selection of the physical device (also with additional criteria)
- Device and swapchain creation, plus queue retrieving
Next, we will see how to use vk-bootstrap.
You need to download and include three files in your project:
VkBootstrap.h
VkBootstrapDispatch.h
VkBootstrap.cpp
Only the first header file has to be in the files with functions and objects of vk-bootstrap:
#include <VkBootstrap.h>
After this line, you are ready to go. The example code in the Basic Usage section of the vk-bootstrap GitHub page shows the steps to create a Vulkan instance:
vkb::InstanceBuilder builder;
auto inst_ret =
builder.set_app_name ("Example Vulkan Application")
.request_validation_layers ()
.use_default_debug_messenger ()
.build ();
Here vkb::InstanceBuilder
simplifies the creation of the Vulkan instance object. The application name is set first, here just to an example string. The graphics driver could use the name to apply optimizations or bug fixes. The instance will have the validation layers enabled, helping to find incorrect resource usage. The default debug messenger is used by the validation layers, printing out any errors to the command window or the console of the program. With the build()
call in the last step, the instance is finally created.
If the Vulkan instance creation fails, we signal this failure to the calling function. And if the creation succeeds, we read the instance value from the builder:
if (!inst_ret) {
std::cerr << "Failed to create Vulkan instance" << "\n";
std::cerr << "Error: " << inst_ret.error().message() <<
"\n";
return false;
}
vkb::Instance vkb_inst = inst_ret.value();
The vk-bootstrap objects reside in the C++ namespace called vkb
. Also, a lot of functions are attached to these objects; this makes the function calls for all important initialization steps available directly on the vk-bootstrap objects.
You may check the code in the Vulkan tutorial (see the Additional resources section) and compare the amount of code required to create a Vulkan instance with the validation layers enabled at https://github.com/Overv/VulkanTutorial/blob/main/code/02_validation_layers.cpp.
The preceding vk-bootstrap code takes less than 15 lines of code; the full-featured Vulkan code of the tutorial is about 200 lines long. And that’s just the instance creation; you also need to select the physical device, the logical device, and the queues.
As we need to acquire and free the memory for every Vulkan image and buffer, we use the Vulkan Memory Allocator (VMA). VMA was created by Advanced Micro Devices (AMD), and the source code is freely available as Open Source library. Working with VMA makes our lives a bit easier compared to using only the built-in Vulkan functions.
Memory management with VMA
The memory management of Vulkan images and buffers lies entirely in the hands of the programmers.
OpenGL hides the process of the creation and deletion of textures behind a single line. You just have to point it to the source image loaded into the memory of your computer. But Vulkan forces you to allocate and free the memory, in addition to the definition of all resources for the image. This includes finding the right memory type for the image you want to create, calculating the correct size, and binding the acquired memory to the buffer. Even with some helper code to detect the memory types, you will be far away from the simplicity of VMA.
To use VMA, download the vk_mem_alloc.h
file from the GitHub project page (see the Additional resources section), put it into the include
path of your project, and include it:
#include <vk_mem_alloc.h>
After initializing the allocator, you only need a structure and a single call:
VmaAllocationCreateInfo vmaAllocInfo{};
vmaAllocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
vmaCreateBuffer(allocator, &bufferInfoStruct,
&vmaAllocInfo, &mVertexBuffer, &mVertexBufferAlloc,
nullptr);
All remaining calculations of the size, memory type, and binding are hidden behind this one call.
After all the theoretical parts of the Vulkan API, let’s take a closer look at some implementation details, including more about vk-bootstrap and VMA.