Book Image

Vulkan Cookbook

By : Pawel Lapinski
Book Image

Vulkan Cookbook

By: Pawel Lapinski

Overview of this book

Vulkan is the next generation graphics API released by the Khronos group. It is expected to be the successor to OpenGL and OpenGL ES, which it shares some similarities with such as its cross-platform capabilities, programmed pipeline stages, or nomenclature. Vulkan is a low-level API that gives developers much more control over the hardware, but also adds new responsibilities such as explicit memory and resources management. With it, though, Vulkan is expected to be much faster. This book is your guide to understanding Vulkan through a series of recipes. We start off by teaching you how to create instances in Vulkan and choose the device on which operations will be performed. You will then explore more complex topics such as command buffers, resources and memory management, pipelines, GLSL shaders, render passes, and more. Gradually, the book moves on to teach you advanced rendering techniques, how to draw 3D scenes, and how to improve the performance of your applications. By the end of the book, you will be familiar with the latest advanced techniques implemented with the Vulkan API, which can be used on a wide range of platforms.
Table of Contents (13 chapters)

Checking available device extensions

Some Vulkan features we would like to use, require us to explicitly enable certain extensions (contrary to OpenGL, in which extensions were automatically/implicitly enabled). There are two kinds, or two levels, of extensions: Instance-level and device-level. Like Instance extensions, device extensions are enabled during logical device creation. We can't ask for a device extension if it is not supported by a given physical device or we won't be able to create a logical device for it. So, before we start creating a logical device, we need to make sure that all requested extensions are supported by a given physical device, or we need to search for another device that supports them all.

How to do it...

  1. Take one of the physical device handles returned by the vkEnumeratePhysicalDevices() function and store it in a variable of type VkPhysicalDevice called physical_device.
  2. Prepare a variable of type uint32_t named extensions_count.
  3. Call vkEnumerateDeviceExtensionProperties( physical_device, nullptr, &extensions_count, nullptr ). In the first parameter, provide the handle of a physical device available on a given hardware platform: the physical_device variable; the second and last parameters should be set to nullptr, and the third parameter should point to the extensions_count variable.
  4. If a function call is successful, the extensions_count variable will contain the total number of available device-level extensions.
  5. Prepare the storage for the list of extension properties. The best solution is to use a variable of type std::vector with elements of type VkExtensionProperties. Call it available_extensions.
  6. Resize the vector to be able to hold at least the extensions_count elements.
  7. Call vkEnumerateDeviceExtensionProperties( physical_device, nullptr, &extensions_count, &available_extensions[0] ). However, this time, replace the last parameter with a pointer to the first element of an array with elements of type VkExtensionProperties. This array must have enough space to contain at least extensions_count elements. Here, provide a pointer to the first element of the available_extensions variable.
  8. If the function returns successfully, the available_extensions vector will contain a list of all extensions supported by a given physical device.

How it works...

The process of acquiring the list of supported device-level extensions can be divided into two stages: Firstly, we check how many extensions are supported by a given physical device. This is done by calling a function named vkEnumerateDeviceExtensionProperties() and setting its last parameter to nullptr as follows:

uint32_t extensions_count = 0; 
VkResult result = VK_SUCCESS; 

result = vkEnumerateDeviceExtensionProperties( physical_device, nullptr, &extensions_count, nullptr ); 
if( (result != VK_SUCCESS) || 
    (extensions_count == 0) ) { 
  std::cout << "Could not get the number of device extensions." << std::endl; 
  return false; 
}

Secondly, we need to prepare an array that will be able to store enough elements of type VkExtensionProperties. In the example, we create a vector variable and resize it so it has the extensions_count number of elements. In the second vkEnumerateDeviceExtensionProperties() function call, we provide an address of the first element of the available_extensions variable. When the call is successful, the variable will be filled with properties (names and versions) of all extensions supported by a given physical device.

available_extensions.resize( extensions_count ); 
result = vkEnumerateDeviceExtensionProperties( physical_device, nullptr, &extensions_count, &available_extensions[0] ); 
if( (result != VK_SUCCESS) || 
    (extensions_count == 0) ) { 
  std::cout << "Could not enumerate device extensions." << std::endl; 
  return false; 
} 

return true;

Once again, we can see the pattern of calling the same function twice: The first call (with the last parameter set to nullptr) informs us of the number of elements returned by the second call. The second call (with the last parameter pointing to an array of VkExtensionProperties elements) returns the requested data, in this case device extensions, which we can iterate over and check whether the extensions we are interested in are available on a given physical device.

See also

  • The following recipes in this chapter:
    • Checking available Instance extensions
    • Enumerating available physical devices
  • The following recipe in Chapter 2, Image Presentation:
    • Creating a logical device with WSI extensions enabled