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)

Creating a Vulkan Instance

A Vulkan Instance is an object that gathers the state of an application. It encloses information such as an application name, name and version of an engine used to create an application, or enabled instance-level extensions and layers.

Through the Instance, we can also enumerate available physical devices and create logical devices on which typical operations such as image creation or drawing are performed. So, before we proceed with using the Vulkan API, we need to create a new Instance object.

How to do it...

  1. Prepare a variable of type std::vector<char const *> named desired_extensions. Store the names of all extensions you want to enable in the desired_extensions variable.
  2. Create a variable of type std::vector<VkExtensionProperties> named available_extensions. Acquire the list of all available extensions and store it in the available_extensions variable (refer to the Checking available Instance extensions recipe).
  1. Make sure that the name of each extension from the desired_extensions variable is also present in the available_extensions variable.
  2. Prepare a variable of type VkApplicationInfo named application_info. Assign the following values for members of the application_info variable:
    1. VK_STRUCTURE_TYPE_APPLICATION_INFO value for sType.
    2. nullptr value for pNext.
    3. Name of your application for pApplicationName.
    4. Version of your application for the applicationVersion structure member; do that by using VK_MAKE_VERSION macro and specifying major, minor, and patch values in it.
    5. Name of the engine used to create an application for pEngineName.
    6. Version of the engine used to create an application for engineVersion; do that by using VK_MAKE_VERSION macro.
    7. VK_MAKE_VERSION( 1, 0, 0 ) for apiVersion.
  3. Create a variable of type VkInstanceCreateInfo named instance_create_info. Assign the following values for members of the instance_create_info variable:
    1. VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO value for sType.
    2. nullptr value for pNext.
    3. 0 value for flags.
    4. Pointer to the application_info variable in pApplicationInfo.
    5. 0 value for enabledLayerCount.
    6. nullptr value for ppEnabledLayerNames.
    7. Number of elements of the desired_extensions vector for enabledExtensionCount.
    8. Pointer to the first element of the desired_extensions vector (or nullptr if is empty) for ppEnabledExtensionNames.
  4. Create a variable of type VkInstance named instance.
  5. Call the vkCreateInstance( &instance_create_info, nullptr, &instance ) function. Provide a pointer to the instance_create_info variable in the first parameter, a nullptr value in the second, and a pointer to the instance variable in the third parameter.
  6. Make sure the operation was successful by checking whether the value returned by the vkCreateInstance() function call is equal to VK_SUCCESS.

How it works...

To create an Instance, we need to prepare some information. First, we need to create an array of names of instance-level extensions that we would like to enable. Next, we need to check if they are supported on a given hardware. This is done by acquiring the list of all available instance-level extensions and checking if it contains the names of all the extensions we want to enable:

std::vector<VkExtensionProperties> available_extensions; 
if( !CheckAvailableInstanceExtensions( available_extensions ) ) { 
  return false; 
} 

for( auto & extension : desired_extensions ) { 
  if( !IsExtensionSupported( available_extensions, extension ) ) { 
    std::cout << "Extension named '" << extension << "' is not supported." << std::endl; 
    return false; 
  } 
}

Next, we need to create a variable in which we will provide information about our application, such as its name and version, the name and version of an engine used to create an application, and the version of a Vulkan API we want to use (right now only the first version is supported by the API):

VkApplicationInfo application_info = { 
  VK_STRUCTURE_TYPE_APPLICATION_INFO, 
  nullptr, 
  application_name, 
  VK_MAKE_VERSION( 1, 0, 0 ), 
  "Vulkan Cookbook", 
  VK_MAKE_VERSION( 1, 0, 0 ), 
  VK_MAKE_VERSION( 1, 0, 0 ) 
};

The pointer to the application_info variable in the preceding code sample is provided in a second variable with the actual parameters used to create an Instance. In it, apart from the previously mentioned pointer, we provide information about the number and names of extensions we want to enable, and also the number and names of layers we want to enable. Neither extensions nor layers are required to create a valid Instance object and we can skip them. However, there are very important extensions, without which it will be hard to create a fully functional application, so it is recommended to use them. Layers may be safely omitted. Following is the sample code preparing a variable used to define Instance parameters:

VkInstanceCreateInfo instance_create_info = { 
  VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 
  nullptr, 
  0, 
  &application_info, 
  0, 
  nullptr, 
  static_cast<uint32_t>(desired_extensions.size()), 
  desired_extensions.size() > 0 ? &desired_extensions[0] : nullptr 
};

Finally, when we have prepared the preceding data, we can create an Instance object. This is done with the vkCreateInstance() function. Its first parameter must point to the variable of type VkInstanceCreateInfo. The third parameter must point to a variable of type VkInstance. The created Instance handle will be stored in it. The second parameter is very rarely used: It may point to a variable of type VkAllocationCallbacks, in which allocator callback functions are defined. These functions control the way host memory is allocated and are mainly used for debugging purposes. Most of the time, the second parameter defining allocation callbacks can be set to nullptr:

VkResult result = vkCreateInstance( &instance_create_info, nullptr, &instance ); 
if( (result != VK_SUCCESS) || 
    (instance == VK_NULL_HANDLE) ) { 
  std::cout << "Could not create Vulkan Instance." << std::endl; 
  return false; 
} 

return true;

See also

  • The following recipes in this chapter:
    • Checking available Instance extensions
    • Destroying a Vulkan Instance
  • The following recipe in Chapter 2, Image Presentation:
    • Creating a Vulkan Instance with WSI extensions enabled