Vulkan Tutorial

1-4 - Advanced Info

Vulkan is now much more capable 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
      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)
      R8G8B8A8Srgb format support for color attachment:
         Images with linear tiling: no
         Images with optimal tiling: yes
         Buffers: no
      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. Out of the supported extension list, we can find out Vulkan video and Vulkan raytracing support:

// supported extensions
vk::Vector 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;

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. For example, pNext may point to vk::PhysicalDeviceVulkan12Properties or vk::PhysicalDeviceVulkan11Properties structure. Both of them were introduced by Vulkan 1.2, so we should include them in pNext chain only if the device version is at least 1.2. We start with getting device version:

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

Now, we will do a little trick. If Vulkan instance is of version 1.0 only, we cannot use any device core functionality of Vulkan 1.1+. It is a historical limitation when Vulkan was very new and some functionality was not yet developed for instance. Such condition is indicated by vulkan10enforced boolean. When this boolean is set, we just reduce device version to 1.0. Using this trick we can skip all the further tests whether instance is at least 1.1 when dealing with device 1.1+ functionality:

// limit device Vulkan version on Vulkan 1.0 instances
if(vulkan10enforced)
	properties.apiVersion = vk::ApiVersion10 | vk::apiVersionPatch(properties.apiVersion);

Then, we create properties12 and properties11 and chain it with pNext pointers. We just omit structures that are not supported by the device:

// extended device properties
vk::PhysicalDeviceVulkan12Properties properties12{  // requires Vulkan 1.2
	.pNext = nullptr,
};
vk::PhysicalDeviceVulkan11Properties properties11{  // requires Vulkan 1.2 (really 1.2, not 1.1)
	.pNext = &properties12,
};
if(properties.apiVersion >= vk::ApiVersion12)
	properties2.pNext = &properties11;

Finally, if we are on Vulkan 1.1+, we call vk::getPhysicalDeviceProperties2(). It makes all the structures to be filled with the data:

if(properties.apiVersion >= vk::ApiVersion11)
	vk::getPhysicalDeviceProperties2(pd, properties2);

// device name
cout << "   " << properties.deviceName << endl;

If we are on Vulkan 1.0 only, data pointed by properties variable is all the valid data we have. On Vulkan 1.2+, we can print additional info from vk::PhysicalDeviceVulkan11Properties and vk::PhysicalDeviceVulkan12Properties structures. For example, we can print device UUID to distinguish the same graphics cards in multi-gpu systems. We can print driver name and its info string, and so on:

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;
}

Device features

Vulkan 1.1+ features are retrieved in the similar way as properties:

// device features
vk::PhysicalDeviceVulkan12Features features12{
	.pNext = nullptr,
};
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;

Device queue family properties

To get extended info for queue families, we use vector of vk::QueueFamilyProperties2. Any additional info structures, such as vk::QueueFamilyVideoPropertiesKHR, use extra vector:

// 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 useful.

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;