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)

Preparing for loading Vulkan API functions

When we want to use Vulkan API in our application, we need to acquire procedures specified in the Vulkan documentation. In order to do that, we can add a dependency to the Vulkan Loader library, statically link with it in our project, and use function prototypes defined in the vulkan.h header file. The second approach is to disable the function prototypes defined in the vulkan.h header file and load function pointers dynamically in our application.

The first approach is little bit easier, but it uses functions defined directly in the Vulkan Loader library. When we perform operations on a given device, Vulkan Loader needs to redirect function calls to the proper implementation based on the handle of the device we provide as an argument. This redirection takes some time, and thus impacts performance.

The second option requires more work on the application side, but allows us to skip the preceding redirection (jump) and save some performance. It is performed by loading functions directly from the device we want to use. This way, we can also choose only the subset of Vulkan functions if we don't need them all.

In this book, the second approach is presented, as this gives developers more control over the things that are going in their applications. To dynamically load functions from a Vulkan Loader library, it is convenient to wrap the names of all Vulkan API functions into a set of simple macros and divide declarations, definitions and function loading into multiple files.

How to do it...

  1. Define the VK_NO_PROTOTYPES preprocessor definition in the project: do this in the project properties (when using development environments such as Microsoft Visual Studio or Qt Creator), or by using the #define VK_NO_PROTOTYPES preprocessor directive just before the vulkan.h file is included in the source code of our application.
  2. Create a new file, named ListOfVulkanFunctions.inl.
  3. Type the following contents into the file:
      #ifndef EXPORTED_VULKAN_FUNCTION 
      #define EXPORTED_VULKAN_FUNCTION( function ) 
      #endif 

      #undef EXPORTED_VULKAN_FUNCTION 
      // 
      #ifndef GLOBAL_LEVEL_VULKAN_FUNCTION 
      #define GLOBAL_LEVEL_VULKAN_FUNCTION( function ) 
      #endif 

      #undef GLOBAL_LEVEL_VULKAN_FUNCTION 
      // 
      #ifndef INSTANCE_LEVEL_VULKAN_FUNCTION 
      #define INSTANCE_LEVEL_VULKAN_FUNCTION( function ) 
      #endif 

      #undef INSTANCE_LEVEL_VULKAN_FUNCTION 
      // 
      #ifndef INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION 
      #define INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( function,       extension ) 
      #endif 

      #undef INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION 
      // 
      #ifndef DEVICE_LEVEL_VULKAN_FUNCTION 
      #define DEVICE_LEVEL_VULKAN_FUNCTION( function ) 
      #endif 

      #undef DEVICE_LEVEL_VULKAN_FUNCTION 
      // 
      #ifndef DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION 
      #define DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( function,
      extension ) 
      #endif 
       
      #undef DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION
  1. Create a new header file, named VulkanFunctions.h.
  2. Insert the following contents into the file:
      #include "vulkan.h" 

      namespace VulkanCookbook { 

      #define EXPORTED_VULKAN_FUNCTION( name ) extern PFN_##name name; 
      #define GLOBAL_LEVEL_VULKAN_FUNCTION( name ) extern PFN_##name 
      name; 
      #define INSTANCE_LEVEL_VULKAN_FUNCTION( name ) extern PFN_##name 
      name; 
      #define INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( name,
      extension ) extern PFN_##name name; 
      #define DEVICE_LEVEL_VULKAN_FUNCTION( name ) extern PFN_##name 
      name; 
      #define DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( name, 
      extension ) extern PFN_##name name; 

      #include "ListOfVulkanFunctions.inl" 

      } // namespace VulkanCookbook
  1. Create a new file with a source code named VulkanFunctions.cpp.
  2. Insert the following contents into the file:
      #include "VulkanFunctions.h" 

      namespace VulkanCookbook { 

      #define EXPORTED_VULKAN_FUNCTION( name ) PFN_##name name; 
      #define GLOBAL_LEVEL_VULKAN_FUNCTION( name ) PFN_##name name; 
      #define INSTANCE_LEVEL_VULKAN_FUNCTION( name ) PFN_##name name; 
      #define INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( name, 
      extension ) PFN_##name name; 
      #define DEVICE_LEVEL_VULKAN_FUNCTION( name ) PFN_##name name; 
      #define DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( name, 
      extension ) PFN_##name name; 

      #include "ListOfVulkanFunctions.inl" 

      } // namespace VulkanCookbook

How it works...

The preceding set of files may seem unnecessary, or even overwhelming, at first. VulkanFunctions.h and VulkanFunctions.cpp files are used to declare and define variables in which we will store pointers to Vulkan API functions. Declarations and definitions are done through a convenient macro definition and an inclusion of a ListOfVulkanFunctions.inl file. We will update this file and add the names of many Vulkan functions, from various levels. This way, we don't need to repeat the names of functions multiple times, in multiple places, which helps us avoid making mistakes and typos. We can just write the required names of Vulkan functions only once, in the ListOfVulkanFunctions.inl file, and include it when it's needed.

How do we know the types of variables for storing pointers to Vulkan API functions? It's quite simple. The type of each function's prototype is derived directly from the function's name. When a function is named <name>, its type is PFN_<name>. For example, a function that creates an image is called vkCreateImage(), so the type of this function is PFN_vkCreateImage. That's why macros defined in the presented set of files have just one parameter for function name, from which the type can be easily derived.

Last, but not least, remember that declarations and definitions of variables, in which we will store addresses of the Vulkan functions, should be placed inside a namespace, a class, or a structure. This is because, if they are made global, this could lead to problems on some operating systems. It's better to remember about namespaces and increase the portability of our code.

Place declarations and definitions of variables containing Vulkan API function pointers inside a structure, class, or namespace.

Now that we are prepared, we can start loading Vulkan functions.

See also

The following recipes in this chapter:

  • Loading function exported from a Vulkan Loader library
  • Loading global-level functions
  • Loading instance-level functions
  • Loading device-level functions