Book Image

Vulkan Cookbook

By : Lapinski
Book Image

Vulkan Cookbook

By: 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)

Loading functions exported from a Vulkan Loader library

When we load (connect with) a Vulkan Loader library, we need to load its functions to be able to use the Vulkan API in our application. Unfortunately, different operating systems have different ways of acquiring the addresses of functions exported from dynamic libraries (.dll files on Windows or .so files on Linux). However, the Vulkan API strives to be portable across many operating systems. So, to allow developers to load all functions available in the API, no matter what operating system they are targeting, Vulkan introduced a function which can be used to load all other Vulkan API functions. However, this one single function can only be loaded in an OS specific way.

How to do it...

On the Windows operating system family:

  1. Create a variable of type PFN_vkGetInstanceProcAddr named vkGetInstanceProcAddr.
  2. Call GetProcAddress( vulkan_library, "vkGetInstanceProcAddr" ), cast the result of this operation onto a PFN_vkGetInstanceProcAddr type, and store it in the vkGetInstanceProcAddr variable.
  3. Confirm that this operation succeeded by checking if a value of the vkGetInstanceProcAddr variable does not equal to nullptr.

On the Linux operating system family:

  1. Create a variable of type PFN_vkGetInstanceProcAddr named vkGetInstanceProcAddr.
  2. Call dlsym( vulkan_library, "vkGetInstanceProcAddr" ), cast the result of this operation onto a PFN_vkGetInstanceProcAddr type, and store it in the vkGetInstanceProcAddr variable.
  3. Confirm that this operation succeeded by checking if a value of the vkGetInstanceProcAddr variable does not equal to nullptr.

How it works...

GetProcAddress() is a function available on Windows operating systems. dlsym() is a function available on Linux operating systems. They both acquire an address of a specified function from an already loaded dynamic-link library. The only function that must be publicly exported from all Vulkan implementations is called vkGetInstanceProcAddr(). It allows us to load any other Vulkan function in a way that is independent of the operating system we are working on.

To ease and automate the process of loading multiple Vulkan functions, and to lower the probability of making mistakes, we should wrap the processes of declaring, defining, and loading functions into a set of convenient macro definitions, as described in the Preparing for loading Vulkan API functions recipe. This way, we can keep all Vulkan API functions in just one file which contains a list of macro-wrapped names of all Vulkan functions. We can then include this single file in multiple places and get use of the C/C++ preprocessor. By redefining macros, we can declare and define the variables in which we will store function pointers, and we can also load all of them.

Here is the updated fragment of the ListOfVulkanFunctions.inl file:

#ifndef EXPORTED_VULKAN_FUNCTION 
#define EXPORTED_VULKAN_FUNCTION( function ) 
#endif 

EXPORTED_VULKAN_FUNCTION( vkGetInstanceProcAddr ) 

#undef EXPORTED_VULKAN_FUNCTION

The rest of the files (VulkanFunctions.h and VulkanFunctions.h) remain unchanged. Declarations and definitions are automatically performed with preprocessor macros. However, we still need to load functions exported from the Vulkan Loader library. The implementation of the preceding recipe may look as follows:

#if defined _WIN32 
#define LoadFunction GetProcAddress 
#elif defined __linux 
#define LoadFunction dlsym 
#endif 

#define EXPORTED_VULKAN_FUNCTION( name )                          \
name = (PFN_##name)LoadFunction( vulkan_library, #name );         \
if( name == nullptr ) {                                           \
  std::cout << "Could not load exported Vulkan function named: "  \
    #name << std::endl;                                           \
  return false;                                                   \
} 

#include "ListOfVulkanFunctions.inl" 

return true;

First we define a macro that is responsible for acquiring an address of a vkGetInstanceProcAddr() function. It gets it from the library represented by the vulkan_library variable, casts the result of this operation onto a PFN_kGetInstanceProcAddr type, and stores it in a variable named vkGetInstanceProcAddr. After that, the macro checks whether the operation succeeded, and displays the proper message on screen in the case of a failure.

All the preprocessor "magic" is done when the ListOfVulkanFunctions.inl file is included and the preceding operations are performed for each function defined in this file. In this case, it is performed for only the vkGetInstanceProcAddr() function, but the same behavior is achieved for functions from other levels.

Now, when we have a function loading function, we can acquire pointers to other Vulkan procedures in an OS-independent way.

See also

The following recipes in this chapter:

  • Connecting with a Vulkan Loader library
  • Preparing for loading Vulkan API functions
  • Loading global-level functions
  • Loading instance-level functions
  • Loading device-level functions