While implementing a Vulkan application, the very first thing that a developer may need to do, and be interested in, is seeing the extended features, functionalities, and capabilities offered by the API. These allow them to gather vital information that can be used to report errors, debug, and trace commands; they can also be used for validation purposes. Vulkan makes use of layers and extensions to expose these additional functionalities:
vulkan.h
for the registered extension along with the necessary data structures and enumerations.An extension can be categorized into two types:
VkDevice
It is suggested that layers and extensions should be enabled during the development phase of an application and turned off at the production stage when the product is expected for release. Turning off the extensions and layers at the production stage allows the application to save unnecessary validation overheads, thus offering higher performance.
Before we get started with application programming, let's take a look at which user-defined classes are used by the sample and what their responsibilities are:
main()
function. The program control logic is built within this file (main.cpp
).VulkanLayerAndExtension.h/.cpp
and provides layer- and extension-based functionalities for the instance and device. It also offers debugging capabilities.VulkanInstance.h/.cpp
.VulkanDevice.h/.cpp
is responsible for creating the logical and physical devices. Each physical device is capable of exposing one or more queues. This class also manages a device's queue and its respective properties.In this section, we will implement the main
, VulkanApplication
, and VulkanLayerAndExtension
classes. Now we will begin our Vulkan programming. We'll start by querying the advertised Vulkan layers. Refer to the following instructions to implement this:
<vulkan/vulkan.h>
to the header file Header.h
. It contains the most commonly used Vulkan APIs and structure declarations.VulkanLayerAndExtension
class and declare the function and variables, as specified in the highlighted part of the following code. Please refer to the inline comments for more details:struct LayerProperties { VkLayerProperties properties; vector<VkExtensionProperties> extensions; }; class VulkanLayerAndExtension{ // Layers and corresponding extension list std::vector<LayerProperties> // Instance/global layergetInstanceLayerProperties(); // Global extensions VkResult getExtensionProperties(LayerProperties &layerProps, VkPhysicalDevice* gpu = NULL); // Device based extensions VkResult getDeviceExtensionProperties(VkPhysicalDevice*gpu); };
getInstanceLayerProperties()
helper function queries either instance or global layers. It gets the total count of the layers and stores all of the layer information in a VkLayerProperties
vector called layerProperties
. Both the operations (count and store) are done by calling vkEnumerateInstanceLayerProperties()
twice. For the first time, calling the API with the second argument as NULL
returns the layer count in the first argument, instanceLayerCount
. In the second instance, instead of providing the second argument as NULL
, pass it as an array/vector of VkLayerProperties
and fetch detailed property information into it.Most enumerated APIs under Vulkan are used to perform more than one functionality, based on the arguments supplied. Just now, we have seen that the vkEnumerateInstanceLayerProperties
API is not only used to retrieve the layer count (by supplying a NULL
argument), but also to get the array of layers (by supplying an array of data structures) that contains information.
Here's the syntax of the preceding code:
VkResult VulkanLayerAndExtension::getInstanceLayerProperties() { // Stores number of instance layers uint32_t instanceLayerCount; // Vector to store layer properties std::vector<VkLayerProperties> layerProperties; // Check Vulkan API result status VkResult result; // Query all the layers do { result = vkEnumerateInstanceLayerProperties (&instanceLayerCount, NULL); if (result) return result; if (instanceLayerCount == 0) return VK_INCOMPLETE; // return fail layerProperties.resize(instanceLayerCount); result = vkEnumerateInstanceLayerProperties (&instanceLayerCount, layerProperties.data()); } while (result == VK_INCOMPLETE); // Query all the extensions for each layer and store it. std::cout << " Instanced Layers" << std::endl; std::cout << "===================" << std::endl; for (auto globalLayerProp: layerProperties) { // Print layer name and its description std::cout <<" "<< globalLayerProp.description << " | |---[Layer Name]--> " << globalLayerProp.layerName <<" "; LayerProperties layerProps; layerProps.properties = globalLayerProp; // Get Instance level extensions for // corresponding layer properties result = getExtensionProperties(layerProps); if (result){ continue; } layerPropertyList.push_back(layerProps); // Print extension name for each instance layer for (auto j : layerProps.extensions){ std::cout << " | |--- [Layer Extension]--> " << j.extensionName << " "; } } return result; }
The following is the syntax of vkEnumerateInstanceLayerProperties()
:
VkResult vkEnumerateInstanceLayerProperties ( uint32_t* pPropertyCount, VkLayerProperties* pProperties);
The following table describes the vkEnumerateInstanceLayerProperties()
API fields:
Parameters |
Description |
|
This variable represents the number of layers at the instance level. This variable works as an input or output variable, depending upon the value passed to |
|
This variable can take two values. When specified as |
Once we retrieve the layer property information for each layer, we'll use it to iterate through all the layers in order to query the extensions exposed by each layer. We'll do this by calling our user-defined helper function getExtensionProperties()
.
Upon the successful execution of the layer and their extensions, you'll see the following output on the console:
LunarG debug layer |---[Layer Name]--> VK_LAYER_LUNARG_api_dump LunarG Validation Layer |---[Layer Name]--> VK_LAYER_LUNARG_core_validation |---[Layer Extesion]--> VK_EXT_debug_report LunarG Standard Validation Layer |---[Layer Name]--> VK_LAYER_LUNARG_standard_validation LunarG Validation Layer |---[Layer Name]--> VK_LAYER_LUNARG_device_limits |---[Layer Extesion]--> VK_EXT_debug_report
Each layer may be capable of supporting one or more extensions. The getExtensionProperties()
function first enumerates the layers to get the number of extensions exposed. Then, it stores the extension properties information in the LayerProperties
data structures using the vkEnumerate-InstanceExtensionProperties()
API. This whole process is very similar to the layer enumeration; refer to getInstanceLayerProperties()
for more information on the last step. The getExtensionProperties()
function queries the extensions for both the instance and device:
// This function retrieves extension and its // properties at instance and device level. // Pass a valid physical device pointer (gpu) to retrieve // device level extensions, otherwise use NULL to // retrieve extension specific to instance level. VkResult VulkanLayerAndExtension::getExtensionProperties (LayerProperties &layerProps, VkPhysicalDevice* gpu) { // Stores number of extension per layer uint32_t extensionCount; VkResult result; // Name of the layer char* layerName = layerProps.properties.layerName; do { // Get the total number of extension in this layer if(gpu){ result = vkEnumerateDeviceExtensionProperties (*gpu, layerName, &extensionCount, NULL); } else{ result = vkEnumerateInstanceExtensionProperties (layerName, &extensionCount, NULL); } if (result || extensionCount == 0) continue; layerProps.extensions.resize(extensionCount); // Gather all extension properties if (gpu){ result = vkEnumerateDeviceExtensionProperties (*gpu, layerName, &extensionCount, layerProps.extensions.data()); } else{ result = vkEnumerateInstanceExtensionProperties (layerName, &extensionCount, layerProps.extensions.data()); } } while (result == VK_INCOMPLETE);
3.129.42.243