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: ts**v**  (count: 3, **decode H264, decode H265**)
         4: ts**v**  (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:

  • Vulkan core version - e.g. Vulkan 1.0, Vulkan 1.1, …
  • Extension - such as VKKHRvideoqueue or VKKHRrayquery
  • Feature - such as double precision support - various features might or might not be supported by a particular device

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 1.0
      Vulkan version:  1.3.277             // Vulkan 1.0
      Device type:     DiscreteGpu         // Vulkan 1.0
      VendorID:        0x10de              // Vulkan 1.0
      DeviceID:        0x1f36              // Vulkan 1.0
      Device UUID:     e0e7b72b-a391-...   // device version 1.2, instance version 1.1
      Driver name:     NVIDIA              // device version 1.2, instance version 1.1
      Driver info:     552.74              // device version 1.2, instance version 1.1
      MaxTextureSize:  32768               // Vulkan 1.0
      Geometry shader:     supported       // optional, Vulkan 1.0
      Double precision:    supported       // optional, Vulkan 1.0
      Half precision:      supported       // optional, device version 1.2, instance version 1.1
      Vulkan Video:        supported       // VK_KHR_video_queue extension, instance version 1.1
      Vulkan Ray Tracing:  supported       // 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                       // Vulkan 1.0
         2: 214MiB  (device local)
      Queue families:
         0: gcts  (count: 16)
         1: ts  (count: 2)
         2: cts  (count: 8)                // queue info: Vulkan 1.0
         3: tsv  (count: 3, decode         //  video info: device version 1.1, instance version 1.1
                 H264, decode H265)
         4: tsv  (count: 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:

  • Block-Compressed image formats (BC1 to BC7)
  • ETC compressed formats (ETC2 and EAC)
  • ASTC LDR formats (since Vulkan 1.0)
  • ASTC HDR formats (since Vulkan 1.3)

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;