Now that Vulkan 1.1 has been released, you may desire to take advantage of the new functionality. However, there are some changes to the VkInstance creation process that you must perform in order to properly access all of the new functionality.
Primary code file for this section is 16-vulkan_1_1.cpp
The main change to the VkInstance creation step, starting
with Vulkan 1.1, is the new vkEnumerateInstanceVersion
function call, which has the following prototype:
VkResult vkEnumerateInstanceVersion(
uint32_t* pApiVersion);
All Vulkan 1.1 loaders will natively export this command. However, if you write a Vulkan 1.1 application and someone attempts to use that application with a Vulkan 1.0 loader, then the application will fail to run. For more information on handling this in a more versatile fashion, see the section on Making Your Application More Versatile.
The pApiVersion
returned by the new vkEnumerateInstanceVersion
command indicates the most recent API version of Vulkan that
the Vulkan runtime/loader on your system supports. It is
formatted in a similar way to wall the other standard Vulkan
API versions defined using the VK_MAKE_VERSION macro and
resulting in a bit-shifted unsigned integeger containing the
major, minor, and patch version of the API. For those
unfamiliar with the VK_MAKE_VERSION macro, it does the
following:
#define VK_MAKE_VERSION(major, minor, patch) \
(((major) << 22) | ((minor) << 12) | (patch))
You could easily write your own macros/functions to extract this same information from a returned value, but Khronos provides the following additional macros for your use:
#define VK_VERSION_MAJOR(version) ((uint32_t)(version) >> 22)
#define VK_VERSION_MINOR(version) (((uint32_t)(version) >> 12) & 0x3ff)
#define VK_VERSION_PATCH(version) ((uint32_t)(version) & 0xfff)
Using these provided macros, you can easily extract the Vulkan major and minor API versions out of the returned uint32_t as the following code snippet shows:
uint32_t api_version= 0;
vkEnumerateInstanceVersion(&api_version);
uint16_t api_major_version = VK_VERSION_MAJOR(api_version);
uint16_t api_minor_version = VK_VERSION_MINOR(api_version);
Then, to determine if you can execute a particular version, say, Vulkan 1.1, you perform a simple check:
if (api_major_version > 1 || api_minor_version >= 1) {
// 1.1 is available
}
If the check passes, you can then continue from here and create a Vulkan 1.1 Instance. If this fails, your only option is creating a Vulkan 1.0 Instance.
When you've determine that you can create a Vulkan Instance for a particular version of the API, you should what that desired API version is to the Vulkan components. This is not required, but certain behaviors may change from one minor version of Vulkan to another. By providing clear information about the API version you intend to use, your application will get the behavior it expects. Also, validation layers will use this information to inform you of cases where your application may be using something incorrectly based on the API version you requested.
To inform Vulkan of what version of the API you intend
to use, simply provide your desired API version in the
VkApplicationInfo
structure during
Instance Creation.
As a brief recap, the VkApplicationInfo
structure
contains the following information:
struct VkApplicationInfo {
VkStructureType sType;
const void* pNext;
const char* pApplicationName;
uint32_t applicationVersion;
const char* pEngineName;
uint32_t engineVersion;
uint32_t apiVersion;
};
To fill in the API version your application intends
to use, simply update apiVersion
using the
VK_MAKE_VERSION macro to indicate the correct
version. For example, to indicate you're using
Vulkan 1.1, you would do the following:
VkApplicationInfo application_info = {};
application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
application_info.pNext = NULL;
application_info.apiVersion = VK_MAKE_VERSION(1, 1, 0);
...
Make sure that you pass the pointer to your VkApplicationinfo
structure to the pApplicationInfo
member of your
VkInstanceCreateInfo
structure that gets pass into
vkCreateInstance
, and you're set.
One other change that may affect you is that vkCreateInstance
no longer returns the VK_ERROR_INCOMPATIBLE_DRIVER
error if
you pass in an incorrect version. This is because the
apiVersion
field of the VkApplicationinfo
structure is now
considered informational only. This means that the following
code from the first sample in this Tutorial is no longer
valid if you create an instance in Vulkan 1.1:
res = vkCreateInstance(&inst_info, NULL, &inst);
if (res == VK_ERROR_INCOMPATIBLE_DRIVER) {
std::cout << "cannot find a compatible Vulkan ICD\n";
exit(-1);
} else if (res) {
std::cout << "unknown error\n";
exit(-1);
}
Now perform only the "else if" portion of the check:
res = vkCreateInstance(&inst_info, NULL, &inst);
if (res) {
std::cout << "unknown error\n";
exit(-1);
}
Once you've created a Vulkan 1.1 Instance, you need to determine if any of the physical devices support 1.1 as well. The first step is to gather a list of Vulkan physical devices available on your system, just like you did for Step 2 Enumerate Device.
When you have your list of available physical devices,
you need to determine what version of the Vulkan API
each device supports using vkGetPhysicalDeviceProperties
:
void vkGetPhysicalDeviceProperties(
VkPhysicalDevice physicalDevice,
VkPhysicalDeviceProperties* pProperties);
This command returns physical device information in the
VkPhysicalDeviceProperties
structure. Which includes
the following information:
struct VkPhysicalDeviceProperties {
uint32_t apiVersion;
uint32_t driverVersion;
uint32_t vendorID;
uint32_t deviceID;
VkPhysicalDeviceType deviceType;
char deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE];
uint8_t pipelineCacheUUID[VK_UUID_SIZE];
VkPhysicalDeviceLimits limits;
VkPhysicalDeviceSparseProperties sparseProperties;
};
For each physical device returned by
vkEnumeratePhysicalDevices
you should make the corresponding
call to vkGetPhysicalDeviceProperties
to get the properties.
Then, check the apiVersion
member to determine the API
version that the driver associated with that physical device
supports.
Once you've looped through all your devices, use the physical device with the API closest to the one you desire. If none supports the desired version, you can only use functionality included in the version of the physical device that you do end up selecting.
As mentioned in Determining Vulkan Loader Support, if you use the new API commands that are directly exported by the Vulkan loader/runtime, you run into the possibility of a user encountering issues when they try to use your application. The main issue that typically arises is if a user has an older loader which doesn't export the new Vulkan 1.1 commands. This scenario would result in your application failing to start on their system due to missing symbols. This may not be a bad thing because it can be used to force your users to upgrade their runtime and/or graphics drivers.
However, if you want your application to gracefully handle
the lack of a Vulkan 1.1-capable loader, you should query
if the command exists and then use function pointers for
all the new functionality. This is what the
vkGetInstanceProcAddr
and vkGetDeviceProcAddr
commands
are provided for.
The first command you want to query existence of is the
new vkEnumerateInstanceVersion
command. Since this command
has to be called prior to the creation of an Instance, you can
pass in VK_NULL_HANDLE for the vkGetInstanceProcAddr
call:
PFN_vkEnumerateInstanceVersion my_EnumerateInstanceVersion =
(PFN_vkEnumerateInstanceVersion)vkGetInstanceProcAddr(
VK_NULL_HANDLE, "vkEnumerateInstanceVersion");
if (nullptr != my_EnumerateInstanceVersion) {
// Command is present and defined.
} else {
// Command is not present, treat as Vulkan 1.0 only
}
For each new instance command (commands that take either a VkInstance
or a VkPhysicalDevice) you should query a similar function pointer
using vkGetInstanceProcAddr
. For all other commands, you should
query a pointer using the created logical device and vkGetDeviceProcAddr
.
The "cube" demo provided with the Vulkan SDK provides several
examples of using vkGetInstanceProcAddr
and vkGetDeviceProcAddr
should you need an example.
Using the returned function pointers will allow you to check at runtime for the presence of a Vulkan 1.1-capable system, and the fall back gracefully if functionality isn't present. This does mean that your application would have to support at least two different versions of the API (1.0 and whatever version you would prefer).
The code for this modified sample showing the more flexible way
to query the Vulkan API version can be found in vulkan_1_1_flexible.cpp
That's all, we hope you enjoy working on Vulkan!
Previous: Draw Cube | Index |