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;