Creating a logical device with geometry shaders, graphics, and compute queues
In Vulkan, when we create various objects, we need to prepare many different structures that describe the creation process itself, but they may also require other objects to be created.
A logical device is no different: We need to enumerate physical devices, check their properties and supported queue families, and prepare a VkDeviceCreateInfo
structure that requires much more information.
To organize these operations, we will present a sample recipe that creates a logical device from one of the available physical devices that support geometry shaders, and both graphics and compute queues.
How to do it...
- Prepare a variable of type
VkDevice
namedlogical_device
. - Create two variables of type
VkQueue
, one namedgraphics_queue
and one namedcompute_queue
.
- Create a variable of type
std::vector<VkPhysicalDevice>
namedphysical_devices
. - Get the list of all physical devices available on a given platform and store it in the
physical_devices
vector (refer to the Enumerating available physical devices recipe). - For each physical device from the
physical_devices
vector:- Create a variable of type
VkPhysicalDeviceFeatures
nameddevice_features
. - Acquire the list of features supported by a given physical device and store it in the
device_features
variable. - Check whether the
geometryShader
member of thedevice_features
variable is equal toVK_TRUE
(is not0
). If it is, reset all the other members of thedevice_features
variable (set their values to zero); if it is not, start again with another physical device. - Create two variables of type
uint32_t
namedgraphics_queue_family_index
andcompute_queue_family_index
. - Acquire indices of queue families that support graphics and compute operations, and store them in the
graphics_queue_family_index
andcompute_queue_family_index
variables, respectively (refer to the Selecting index of a queue family with desired capabilities recipe). If any of these operations is not supported, search for another physical device. - Create a variable of type
std::vector
with elements of typeQueueInfo
(refer to Creating a logical device recipe). Name this variablerequested_queues
. - Store the
graphics_queue_family_index
variable and one-element vector offloats
with a1.0f
value in therequested_queues
variable. If a value of thecompute_queue_family_index
variable is different than the value of thegraphics_queue_family_index
variable, add another element to therequested_queues
vector, with thecompute_queue_family_index
variable and a one-element vector offloats
with1.0f
value. - Create a logical device using the
physical_device
,requested_queues
,device_features
andlogical_device
variables (refer to the Creating a logical device recipe). If this operation failed, repeat the preceding operations with another physical device.
- If the logical device was successfully created, load the device-level functions (refer to the Loading device-level functions recipe). Get the handle of the queue from the
graphics_queue_family_index
family and store it in thegraphics_queue
variable. Get the queue from thecompute_queue_family_index
family and store it in thecompute_queue
variable.
- Create a variable of type
How it works...
To start the process of creating a logical device, we need to acquire the handles of all physical devices available on a given computer:
std::vector<VkPhysicalDevice> physical_devices; EnumerateAvailablePhysicalDevices( instance, physical_devices );
Next we need to loop through all available physical devices. For each such device, we need to acquire its features. This will give us the information about whether a given physical device supports geometry shaders:
for( auto & physical_device : physical_devices ) { VkPhysicalDeviceFeatures device_features; VkPhysicalDeviceProperties device_properties; GetTheFeaturesAndPropertiesOfAPhysicalDevice( physical_device, device_features, device_properties ); if( !device_features.geometryShader ) { continue; } else { device_features = {}; device_features.geometryShader = VK_TRUE; }
If geometry shaders are supported, we can reset all the other members of a returned list of features. We will provide this list during the logical device creation, but we don't want to enable any other feature. In this example, geometry shaders are the only additional feature we want to use.
Next we need to check if a given physical device exposes queue families that support graphics and compute operations. This may be just one single family or two separate families. We acquire the indices of such queue families:
uint32_t graphics_queue_family_index; if( !SelectIndexOfQueueFamilyWithDesiredCapabilities( physical_device, VK_QUEUE_GRAPHICS_BIT, graphics_queue_family_index ) ) { continue; } uint32_t compute_queue_family_index; if( !SelectIndexOfQueueFamilyWithDesiredCapabilities( physical_device, VK_QUEUE_COMPUTE_BIT, compute_queue_family_index ) ) { continue; }
Next, we need to prepare a list of queue families, from which we want to request queues. We also need to assign priorities to each queue from each family:
std::vector<QueueInfo> requested_queues = { { graphics_queue_family_index, { 1.0f } } }; if( graphics_queue_family_index != compute_queue_family_index ) { requested_queues.push_back( { compute_queue_family_index, { 1.0f } } ); }
If graphics and compute queue families have the same index, we request only one queue from one queue family. If they are different, we need to request two queues: One from the graphics family and one from the compute family.
We are ready to create a logical device for which we provide the prepared data. Upon success, we can the load device-level functions and acquire the handles of the requested queues:
if( !CreateLogicalDevice( physical_device, requested_queues, {}, &device_features, logical_device ) ) { continue; } else { if( !LoadDeviceLevelFunctions( logical_device, {} ) ) { return false; } GetDeviceQueue( logical_device, graphics_queue_family_index, 0, graphics_queue ); GetDeviceQueue( logical_device, compute_queue_family_index, 0, compute_queue ); return true; } } return false;
See also
The following recipes in this chapter:
- Enumerating available physical devices
- Getting features and properties of a physical device
- Selecting the index of a queue family with the desired capabilities
- Creating a logical device
- Loading device-level functions
- Getting a device queue
- Destroying a logical device