The implementation of the preceding recipe, similarly to other queries, can be divided into two stages: Firstly, we acquire information about the total number of queue families available on a given physical device. This is done by calling a vkGetPhysicalDeviceQueueFamilyProperties() function, with the last argument set to nullptr:
uint32_t queue_families_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties( physical_device, &queue_families_count, nullptr );
if( queue_families_count == 0 ) {
std::cout << "Could not get the number of queue families." << std::endl;
return false;
}
Secondly, when we know how many queue families there are, we can prepare sufficient memory to be able to store the properties of all of them. In the presented example, we create a variable of type std::vector with VkQueueFamilyProperties elements and resize it to the value returned by the first query. After that, we perform a second vkGetPhysicalDeviceQueueFamilyProperties() function call, with the last parameter pointing to the first element of the created vector. In this vector, the parameters of all available queue families will be stored.
queue_families.resize( queue_families_count );
vkGetPhysicalDeviceQueueFamilyProperties( physical_device, &queue_families_count, &queue_families[0] );
if( queue_families_count == 0 ) {
std::cout << "Could not acquire properties of queue families." << std::endl;
return false;
}
return true;
The most important information we can get from properties is the types of operations that can be performed by the queues in a given family. Types of operations supported by queues are divided into:
- Graphics: For creating graphics pipelines and drawing
- Compute: For creating compute pipelines and dispatching compute shaders
- Transfer: Used for very fast memory-copying operations
- Sparse: Allows for additional memory management features
Queues from the given family may support more than one type of operation. There may also be a situation where different queue families support exactly the same types of operation.
Family properties also inform us about the number of queues that are available in the given family, about the timestamp support (for time measurements), and the granularity of image transfer operations (how small parts of image can be specified during copy/blit operations).
With the knowledge of the number of queue families, their properties, and the available number of queues in each family, we can prepare for logical device creation. All this information is needed, because we don't create queues by ourselves. We just request them during logical device creation, for which we must specify how many queues are needed and from which families. When a device is created, queues are created automatically along with it. We just need to acquire the handles of all requested queues.