Vulkan queue families
The concept
The GPU is an asynchronous machine. We do not give it direct commands (as we would when calling a C++ function). Instead, we stack lists of commands (command buffers) into "mailboxes" called queues.
These queues are grouped into queue families based on what they can do. Why families? Because the GPU's physical architecture is divided: some chips are only capable of performing mathematical operations (compute queue), others are only capable of copying memory (transfer queue), and others can do everything, including rendering graphics (graphics queue).
For our engine, we look for one family capable of doing two things at once:
- Graphics: execute 3D rendering commands.
- Presentation: send the rendered images to the Vulkan surface (the screen).
This is one of the criteria used to select the Vulkan physical device, and the family index found here is what the Vulkan logical device uses to create its queue.
In game engine
bool VulkanContext::findGraphicsQueueFamily()
{
// Enumerate all the properties of the queue family for the selected GPU
const auto queueFamilies = d_physicalDevice.getQueueFamilyProperties();
for (uint32_t i = 0; i < queueFamilies.size(); i++) {
// Check whether the family supports graphics operations
const vk::Flags<vk::QueueFlagBits> supportsGraphics =
queueFamilies[i].queueFlags & vk::QueueFlagBits::eGraphics;
// Check whether the family supports the display on our surface
const bool supportsPresent =
d_physicalDevice.getSurfaceSupportKHR(i, *d_surface);
if (supportsGraphics && supportsPresent) {
d_graphicsQueueFamilyIndex = i;
return true;
}
}
std::cerr << "No queue family found that supports both Graphics and "
"Presentation!" << std::endl;
return false;
}