Each Vulkan device has its own properties, feature set, limits, set of supported extensions and so on. We can query maximum texture resolution, find out if geometry shader is supported, if we can use double or half precision computations, or whether particular image format is supported on the given physical device. We can also get supported Vulkan version and list of extensions.
If you run 1-3-DeviceInfo example, you can see the output similar to the following one:
Vulkan instance: Version: 1.3.275 Vulkan devices: Quadro RTX 3000 Vulkan version: 1.3.277 Device type: DiscreteGpu VendorID: 0x10de DeviceID: 0x1f36 MaxTextureSize: 32768 Geometry shader: supported Double precision: supported Memory heaps: 0: 5955MiB (device local) 1: 16200MiB 2: 214MiB (device local) Queue families: 0: gct (count: 16) 1: t (count: 2) 2: ct (count: 8) 3: t (count: 3) 4: t (count: 1) Format support for images with optimal tiling: R8G8B8A8Srgb: yes R8G8B8Srgb: no R16G16B16A16Sfloat: yes R16G16B16Sfloat: no
We can see various information about the Vulkan instance and device. How to get all this information?
Vulkan provides number of query functions that return much of information in number of structures. Quick overview can be found in the following text:
vk::enumerateInstanceVersion() returns instance version as uint32_t | |
Vulkan instance: |
|
vk::getPhysicalDeviceProperties() returns vk::PhysicalDeviceProperties structure | |
Vulkan devices: |
VkPhysicalDeviceProperties::deviceName |
vk::getPhysicalDeviceFeatures() returns vk::PhysicalDeviceFeatures structure | |
Geometry shader: supported |
VkPhysicalDeviceFeatures::geometryShader |
vk::getPhysicalDeviceMemoryProperties() returns vk::PhysicalDeviceMemoryProperties structure | |
Memory heaps: |
vk::PhysicalDeviceMemoryProperties::memoryHeapCount |
vk::getPhysicalDeviceQueueFamilyProperties() returns vk::QueueFamilyProperties structure | |
Queue families: |
vk::QueueFamilyProperties::queueFlags |
vk::getPhysicalDeviceFormatProperties() returns vk::FormatProperties structure | |
Format support for [...]: |
vk::FormatProperties::optimalTilingFeatures
|
To get Vulkan instance version, vk::enumerateInstanceVersion() function is used:
// instance version uint32_t instanceVersion = vk::enumerateInstanceVersion(); cout << "Vulkan instance:\n" << " Version: " << vk::apiVersionMajor(instanceVersion) << "." << vk::apiVersionMinor(instanceVersion) << "." << vk::apiVersionPatch(instanceVersion) << endl;
As we can see, the version is returned as uint32_t. Then, major, minor and patch versions are extracted from particular bits of uint32_t value by vk::apiVersion[Major|Minor|Patch]() functions.
Instance version tells us about the functionality that is provided by Vulkan instance. For example, version 1.0 might be too low for many applications, while 1.1 is quite ok in many cases.
One note: vkEnumerateInstanceVersion() was introduced by Vulkan 1.1. So, if the function is absent, the instance version is 1.0. For the sake of convenience, vkg hides this technical detail and vk::enumerateInstanceVersion() always returns valid instance version, even in the case of version 1.0.
Vulkan 1.0 stores device properties in vk::PhysicalDeviceProperties structure. We can find device name there, device type (integrated/discrete/...), Vulkan version of the device, various device limits in nested structure vk::PhysicalDeviceLimits, and so on.
To get the properties, vk::getPhysicalDeviceProperties() function can be used:
// device properties vk::PhysicalDeviceProperties properties = vk::getPhysicalDeviceProperties(pd); cout << " " << properties.deviceName << endl; // device Vulkan version cout << " Vulkan version: " << vk::apiVersionMajor(properties.apiVersion) << "." << vk::apiVersionMinor(properties.apiVersion) << "." << vk::apiVersionPatch(properties.apiVersion) << endl; // device type const char* s; switch(properties.deviceType) { case vk::PhysicalDeviceType::eIntegratedGpu: s = "IntegratedGpu"; break; case vk::PhysicalDeviceType::eDiscreteGpu: s = "DiscreteGpu"; break; case vk::PhysicalDeviceType::eVirtualGpu: s = "VirtualGpu"; break; case vk::PhysicalDeviceType::eCpu: s = "Cpu"; break; default: s = "Other"; } cout << " Device type: " << s << endl; // VendorID and DeviceID cout << " VendorID: 0x" << hex << properties.vendorID << endl; cout << " DeviceID: 0x" << properties.deviceID << dec << endl; // device limits cout << " MaxTextureSize: " << properties.limits.maxImageDimension2D << endl;
More info can be found in the Vulkan specification.
Device features are functionalities not necessarily supported on all devices. Some functionalities might be supported but others do not. Moreover, we must explicitly enable each feature before use.
// device features vk::PhysicalDeviceFeatures features = vk::getPhysicalDeviceFeatures(pd); cout << " Geometry shader: "; if(features.geometryShader) cout << "supported" << endl; else cout << "not supported" << endl; cout << " Double precision: "; if(features.shaderFloat64) cout << "supported" << endl; else cout << "not supported" << endl;
More queryable features of Vulkan 1.0 are described in vk::PhysicalDeviceFeatures structure documentation.
Vulkan distinguishes between device memory and host memory. The device memory is usually the one on graphics card and host memory is usually plugged into the motherboard close to the processor. No wonder that device can usually access its graphic memory very efficiently while access to the host memory on the motherboard might be more complicated and slower. The same for the processor - its access to graphics card memory might be more complicated and slower than to the memory on the motherboard. Interesting situation appears on integrated graphics solutions where device memory and host memory might be the same physical memory.
To get information about the memory configuration on the given system, vk::getPhysicalDeviceMemoryProperties() can be used:
// memory properties cout << " Memory heaps:" << endl; vk::PhysicalDeviceMemoryProperties memoryProperties = vk::getPhysicalDeviceMemoryProperties(pd); for(uint32_t i=0, c=memoryProperties.memoryHeapCount; i<c; i++) { vk::MemoryHeap& h = memoryProperties.memoryHeaps[i]; cout << " " << i << ": " << h.size/1024/1024 << "MiB"; if(h.flags & vk::MemoryHeapFlagBits::eDeviceLocal) cout << " (device local)"; cout << endl; }
Device queues execute commands submitted for execution on particular device and by particular queue. They are organized into families. Each family has the same properties and the same functionality. We can list queue families as follows:
// queue family properties cout << " Queue families:" << endl; vk::vector<vk::QueueFamilyProperties> queueFamilyList = vk::getPhysicalDeviceQueueFamilyProperties(pd); for(uint32_t i=0, c=uint32_t(queueFamilyList.size()); i<c; i++) { cout << " " << i << ": "; vk::QueueFamilyProperties& queueFamilyProperties = queueFamilyList[i]; if(queueFamilyProperties.queueFlags & vk::QueueFlagBits::eGraphics) cout << "g"; if(queueFamilyProperties.queueFlags & vk::QueueFlagBits::eCompute) cout << "c"; if(queueFamilyProperties.queueFlags & vk::QueueFlagBits::eTransfer) cout << "t"; cout << " (count: " << queueFamilyProperties.queueCount << ")" << endl; }
We output family index followed by letters g, c and t. These letters indicate graphics, compute and transfer capability. So, we might have queues just for graphics, just for compute or for both. Some of the families might support transfer capability as well. Finally, we output number of the queues in the given family.
Buffers and images use various formats for their data. To find our if particular format is supported on the device, we can use vk::getPhysicalDeviceFormatProperties().
For example, to find out if we can render into R8G8B8A8Srgb format, we can use following code:
// format support for images with optimal tiling cout << " Format support for rendering into images with optimal tiling:" << endl; vk::FormatProperties formatProperties = vk::getPhysicalDeviceFormatProperties(pd, vk::Format::eR8G8B8A8Srgb); if(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eColorAttachment) cout << " R8G8B8A8Srgb: yes" << endl; else cout << " R8G8B8A8Srgb: no" << endl;
As the result, we get vk::FormatProperties structure that contains few variables full of bit flags that indicate support for various use cases. In our case, we are interested in eColorAttachment flag inside optimal tiling member. If the flag is set, we can render into image with R8G8B8A8Srgb format and optimal tiling.
For other formats, we use the same code:
formatProperties = vk::getPhysicalDeviceFormatProperties(pd, vk::Format::eR8G8B8Srgb); if(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eColorAttachment) cout << " R8G8B8Srgb: yes" << endl; else cout << " R8G8B8Srgb: no" << endl; formatProperties = vk::getPhysicalDeviceFormatProperties(pd, vk::Format::eR16G16B16A16Sfloat); if(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eColorAttachment) cout << " R16G16B16A16Sfloat: yes" << endl; else cout << " R16G16B16A16Sfloat: no" << endl; formatProperties = vk::getPhysicalDeviceFormatProperties(pd, vk::Format::eR16G16B16Sfloat); if(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eColorAttachment) cout << " R16G16B16Sfloat: yes" << endl; else cout << " R16G16B16Sfloat: no" << endl;
R8G8B8A8Srgb is a kind of standard rendering output for many applications. Its support is mandatory by Vulkan specification in Formats chapter. So, our test is actually superfluous. R8G8B8Srgb would be more memory efficient alternative while less memory bandwidth might sometimes improve the performance despite that fact that it is not 4-byte aligned. R8G8B8Srgb format support is optional and we have to test for it.
R16G16B16A16Sfloat is half float based format. It can be used to do HDR rendering. Its support is mandatory, at least for color attachment (e.g. for rendering output), so our test is again superfluous. R16G16B16Srgb is optional. So, we would need to test for its support before use.
Vulkan 1.0 supports few more query functions:
In the case of interest, they are described in Vulkan specification. Concerning vkEnumerateDeviceExtensionProperties(), we will deal with the topic of device extensions in the next article.