Vulkan Tutorial

1-4 - Advanced Info

Vulkan is much more capable now than it was in the time of Vulkan 1.0 release in 2016. Some examples of new functionalities can be seen in the following output produced by the example code of this article. The new functionalities are marked by the red tint:

Vulkan instance:
   Version: 1.3.275
   Extensions (18 in total):
      VK_KHR_device_group_creation
      VK_KHR_external_fence_capabilities
      VK_KHR_external_memory_capabilities
      VK_KHR_external_semaphore_capabilities
      VK_KHR_get_physical_device_properties2
      VK_KHR_get_surface_capabilities2
      VK_KHR_surface
      VK_KHR_surface_protected_capabilities
      VK_KHR_win32_surface
      VK_EXT_debug_report
      VK_EXT_debug_utils
      VK_EXT_surface_maintenance1
      VK_EXT_swapchain_colorspace
      VK_NV_external_memory_capabilities
      VK_KHR_portability_enumeration
      VK_LUNARG_direct_driver_loading
      VK_EXT_layer_settings
      VK_EXT_validation_features
Vulkan devices:
   Quadro RTX 3000
      Vulkan version:  1.3.277
      Device type:     DiscreteGpu
      VendorID:        0x10de
      DeviceID:        0x1f36
      Device UUID:     e0e7b72b-a391-a141-9bbf-2059ba86ca79
      Driver name:     NVIDIA
      Driver info:     552.74
      PCI bus info:
         domain: 0, bus: 1, device: 0, function: 0
      MaxTextureSize:  32768
      Geometry shader:     supported
      Double precision:    supported
      Half precision:      supported
      Vulkan Video:        supported
      Vulkan Ray Tracing:  supported
      Memory heaps:
         0: 5955MiB  (device local)
         1: 16200MiB
         2: 214MiB  (device local)
      Queue families:
         0: gcts  (count: 16)
         1: ts  (count: 2)
         2: cts  (count: 8)
         3: tsv  (count: 3, decode H264, decode H265)
         4: tsv  (count: 1)
      Format support for compressed textures:
         BC7  (BC7_SRGB):            yes
         ETC2 (ETC2_R8G8B8A8_SRGB):  no
         ASTC (ASTC_4x4_SRGB):       no
         ASTC (ASTC_4x4_SFLOAT):     yes
      Extensions (215 in total):  VK_KHR_16bit_storage, [...]

We can see new extensions for both - instance and devices. If list of extensions floods your screen too much, you might disable it by --no-extension-list. As we can see, the instance supports 18 extensions and the device 215. Using the device extension list, we can find out Vulkan video, Vulkan raytracing and PCI bus info support:

// supported extensions
vk::vector<vk::ExtensionProperties> extensionList =
	vk::enumerateDeviceExtensionProperties(pd, nullptr);
bool videoQueueSupported = vk::isExtensionSupported(extensionList, "VK_KHR_video_queue") &&
                           instanceVersion >= vk::ApiVersion11;
bool raytracingSupported = vk::isExtensionSupported(extensionList, "VK_KHR_acceleration_structure") &&
                           vk::isExtensionSupported(extensionList, "VK_KHR_ray_tracing_pipeline") &&
                           vk::isExtensionSupported(extensionList, "VK_KHR_ray_query") &&
                           instanceVersion >= vk::ApiVersion11;
bool pciBusInfoSupported = vk::isExtensionSupported(extensionList, "VK_EXT_pci_bus_info") &&
                           instanceVersion >= vk::ApiVersion11;

The important rule is: Before we use some Vulkan functionality, we should make sure it is supported. The support can be provided by:

Let's analyze the output of this article example:

Vulkan instance:
   Version:  1.3.275

query for instance version since instance version 1.1,
but emulated by vkg on Vulkan 1.0
   Extensions (18 in total):
      [...]
extension enumeration since Vulkan 1.0
Vulkan devices:
   Quadro RTX 3000
      Vulkan version:  1.3.277
      Device type:     DiscreteGpu
      VendorID:        0x10de
      DeviceID:        0x1f36
      Device UUID:     e0e7b72b-a391-...
      Driver name:     NVIDIA
      Driver info:     552.74
      MaxTextureSize:  32768

Vulkan 1.0
Vulkan 1.0
Vulkan 1.0
Vulkan 1.0
Vulkan 1.0
device version 1.2, instance version 1.1
device version 1.2, instance version 1.1
device version 1.2, instance version 1.1
Vulkan 1.0
      Geometry shader:     supported
      Double precision:    supported
      Half precision:      supported
      Vulkan Video:        supported
      Vulkan Ray Tracing:  supported
optional, Vulkan 1.0
optional, Vulkan 1.0
optional, device version 1.2, instance version 1.1
VK_KHR_video_queue extension, instance version 1.1
VK_KHR_acceleration_structure + VK_KHR_ray_tracing_pipeline +
VK_KHR_ray_query device extensions, instance version 1.1
      Memory heaps:
         0: 5955MiB  (device local)
         1: 16200MiB
         2: 214MiB  (device local)
Vulkan 1.0
      Queue families:
         0: gcts  (count: 16)
         1: ts  (count: 2)
         2: cts  (count: 8)
         3: tsv  (count: 3, decode
                 H264, decode H265)
         4: tsv  (count: 1)
queue info: Vulkan 1.0
video info: device version 1.1, instance version 1.1
      Extensions (215 in total):  [...] Vulkan 1.0

Device properties

Vulkan 1.1 introduces vk::PhysicalDeviceProperties2 structure that allows for extending vk::PhysicalDeviceProperties by pNext pointer:

struct PhysicalDeviceProperties2 {
	StructureType               sType = StructureType::ePhysicalDeviceProperties2;
	void*                       pNext = nullptr;
	PhysicalDeviceProperties    properties;
};

Now, we can chain structures using pNext pointer. Before we do that, let's start with declaring the structures and getting device version:

// get device properties
vk::PhysicalDeviceProperties2 properties2;
vk::PhysicalDeviceProperties& properties = properties2.properties;
properties = vk::getPhysicalDeviceProperties(pd);

We follow with chaining of the structures using pNext pointer:

// extended device properties
vk::PhysicalDevicePCIBusInfoPropertiesEXT pciBusInfo;  // requires VK_EXT_pci_bus_info
vk::PhysicalDeviceVulkan12Properties properties12;  // requires Vulkan 1.2
vk::PhysicalDeviceVulkan11Properties properties11;  // requires Vulkan 1.2 (really 1.2, not 1.1)
if(properties.apiVersion >= vk::ApiVersion11) {
	void** lastPNext = &properties2.pNext;
	if(properties.apiVersion >= vk::ApiVersion12) {
		properties2.pNext = &properties11;
		properties11.pNext = &properties12;
		lastPNext = &properties12.pNext;
	}
	if(pciBusInfoSupported)
		*lastPNext = &pciBusInfo;
	vk::getPhysicalDeviceProperties2(pd, properties2);
}

During vk::getPhysicalDeviceProperties2() call, all the structures in the structure chain are filled with the data. Now, we can print additional info from the structures:

if(properties.apiVersion >= vk::ApiVersion12) {
	cout << "      Driver name:     " << properties12.driverName << endl;
	cout << "      Driver info:     " << properties12.driverInfo << endl;
}
else {
	cout << "      Driver name:     < unknown >" << endl;
	cout << "      Driver info:     < unknown >" << endl;
}

If VK_EXT_pci_bus_info extension is supported, we can also print pci bus information:

// PCI bus info
cout << "      PCI bus info:" << endl;
if(pciBusInfoSupported)
	cout << "         domain: " << pciBusInfo.pciDomain << ", bus: " << pciBusInfo.pciBus
	     << ", device: " << pciBusInfo.pciDevice << ", function: " << pciBusInfo.pciFunction << endl;
else
	cout << "         not available" << endl;

Device features

Vulkan features from newer Vulkan versions are retrieved in the similar way as properties:

// device features
vk::PhysicalDeviceVulkan12Features features12;
vk::PhysicalDeviceFeatures2 features2{
	.pNext = (properties.apiVersion>=vk::ApiVersion12) ? &features12 : nullptr,
};
vk::PhysicalDeviceFeatures& features = features2.features;
if(properties.apiVersion >= vk::ApiVersion11)
	vk::getPhysicalDeviceFeatures2(pd, features2);
else
	features = vk::getPhysicalDeviceFeatures(pd);

Then, we can, for example, find out if half precision floats are supported:

cout << "      Half precision:      ";
if(properties.apiVersion >= vk::ApiVersion12 && features12.shaderFloat16)
	cout << "supported" << endl;
else
	cout << "not supported" << endl;
cout << "      Half precision:      ";

Device queue family properties

To get extended info for queue families, we use vector of vk::QueueFamilyProperties2 and vector of vk::QueueFamilyVideoPropertiesKHR:

// queue family properties
cout << "      Queue families:" << endl;
vk::vector<vk::QueueFamilyProperties2> queueFamilyList;
vk::vector<vk::QueueFamilyVideoPropertiesKHR> queueVideoPropertiesList;
if(properties.apiVersion >= vk::ApiVersion11)
	queueFamilyList = vk::getPhysicalDeviceQueueFamilyProperties2(
		pd, queueVideoPropertiesList, videoQueueSupported);
else {
	vk::vector<vk::QueueFamilyProperties> v = vk::getPhysicalDeviceQueueFamilyProperties(pd);
	queueFamilyList.resize(v.size());
	for(size_t i=0; i<v.size(); i++)
		queueFamilyList[i].queueFamilyProperties = v[i];
}

If we are on Vulkan 1.1+, we call vk::getPhysicalDeviceQueueFamilyProperties2. Otherwise, we use old Vulkan 1.0 function and copy the results from the old structures into the new ones.

Then, we can process the information about the queue families. The extension provided functionality is marked by bold text:

for(uint32_t i=0, c=uint32_t(queueFamilyList.size()); i<c; i++) {
	cout << "         " << i << ": ";
	vk::QueueFamilyProperties& queueFamilyProperties = queueFamilyList[i].queueFamilyProperties;
	if(queueFamilyProperties.queueFlags & vk::QueueFlagBits::eGraphics)
		cout << "g";
	if(queueFamilyProperties.queueFlags & vk::QueueFlagBits::eCompute)
		cout << "c";
	if(queueFamilyProperties.queueFlags & vk::QueueFlagBits::eTransfer)
		cout << "t";
	if(queueFamilyProperties.queueFlags & vk::QueueFlagBits::eSparseBinding)
		cout << "s";
	if(queueFamilyProperties.queueFlags & (vk::QueueFlagBits::eVideoDecodeKHR | vk::QueueFlagBits::eVideoEncodeKHR))
		cout << "v";
	cout << "  (count: " << queueFamilyProperties.queueCount;
	if(videoQueueSupported) {
		if(queueVideoPropertiesList[i].videoCodecOperations & vk::VideoCodecOperationFlagBitsKHR::eDecodeH264)
			cout << ", decode H264";
		if(queueVideoPropertiesList[i].videoCodecOperations & vk::VideoCodecOperationFlagBitsKHR::eDecodeH265)
			cout << ", decode H265";
		if(queueVideoPropertiesList[i].videoCodecOperations & vk::VideoCodecOperationFlagBitsKHR::eDecodeAV1)
			cout << ", decode AV1";
	}
	cout << ")" << endl;
}

Device format properties

We can test, for example, for compressed texture support. The compressed textures reduce memory consumption, lower bandwidth, but might reduce visual quality. Memory consumption and bandwidth requirements might be of issue for example, on mobile devices. But checking of the visual quality of compressed textures might be sometimes important.

As of 2025, we can query for these Vulkan core formats:

So, we query for Bc7Srgb, Etc2R8G8B8A8Srgb, Astc4x4Srgb formats. If Vulkan 1.3 is supported, we query for Astc4x4Sfloat as well. In all the cases, we test for SampledImageFilterLinear bit:

cout << "      Format support for compressed textures:" << endl;
vk::FormatProperties formatProperties = vk::getPhysicalDeviceFormatProperties(pd, vk::Format::eBc7SrgbBlock);
if(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)
	cout << "         BC7  (BC7_SRGB):            yes" << endl;
else
	cout << "         BC7  (BC7_SRGB):            no" << endl;
formatProperties = vk::getPhysicalDeviceFormatProperties(pd, vk::Format::eEtc2R8G8B8A8SrgbBlock);
if(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)
	cout << "         ETC2 (ETC2_R8G8B8A8_SRGB):  yes" << endl;
else
	cout << "         ETC2 (ETC2_R8G8B8A8_SRGB):  no" << endl;
formatProperties = vk::getPhysicalDeviceFormatProperties(pd, vk::Format::eAstc4x4SrgbBlock);
if(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)
	cout << "         ASTC (ASTC_4x4_SRGB):       yes" << endl;
else
	cout << "         ASTC (ASTC_4x4_SRGB):       no" << endl;
if(properties.apiVersion >= vk::ApiVersion13) {
	formatProperties = vk::getPhysicalDeviceFormatProperties(pd, vk::Format::eAstc4x4SfloatBlock);
	if(formatProperties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)
		cout << "         ASTC (ASTC_4x4_SFLOAT):     yes" << endl;
	else
		cout << "         ASTC (ASTC_4x4_SFLOAT):     no" << endl;
} else
	cout << "         ASTC (ASTC_4x4_SFLOAT):     no" << endl;