Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Vulkan Cookbook

You're reading from   Vulkan Cookbook Work through recipes to unlock the full potential of the next generation graphics API—Vulkan

Arrow left icon
Product type Paperback
Published in Apr 2017
Publisher Packt
ISBN-13 9781786468154
Length 700 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Pawel Lapinski Pawel Lapinski
Author Profile Icon Pawel Lapinski
Pawel Lapinski
Arrow right icon
View More author details
Toc

Table of Contents (13) Chapters Close

Preface 1. Instance and Devices FREE CHAPTER 2. Image Presentation 3. Command Buffers and Synchronization 4. Resources and Memory 5. Descriptor Sets 6. Render Passes and Framebuffers 7. Shaders 8. Graphics and Compute Pipelines 9. Command Recording and Drawing 10. Helper Recipes 11. Lighting 12. Advanced Rendering Techniques

Loading instance-level functions

We have created a Vulkan Instance object. The next step is to enumerate physical devices, choose one of them, and create a logical device from it. These operations are performed with instance-level functions, of which we need to acquire the addresses.

How to do it...

  1. Take the handle of a created Vulkan Instance. Provide it in a variable of type VkInstance named instance.
  2. Choose the name (denoted as <function name>) of an instance-level function you want to load.
  3. Create a variable of type PFN_<function name> named <function name>.
  4. Call vkGetInstanceProcAddr( instance, "<function name>" ). Provide a handle for the created Instance in the first parameter and a function name in the second. Cast the result of this operation onto a PFN_<function name> type and store it in a <function name> variable.
  5. Confirm that this operation succeeded by checking if a value of a <function name> variable is not equal to nullptr.

How it works...

Instance-level functions are used mainly for operations on physical devices. There are multiple instance-level functions, with vkEnumeratePhysicalDevices(), vkGetPhysicalDeviceProperties(), vkGetPhysicalDeviceFeatures(), vkGetPhysicalDeviceQueueFamilyProperties(), vkCreateDevice(), vkGetDeviceProcAddr(), vkDestroyInstance() or vkEnumerateDeviceExtensionProperties() among them. However, this list doesn't include all instance-level functions.

How can we tell if a function is instance- or device-level? All device-level functions have their first parameter of type VkDevice, VkQueue, or VkCommandBuffer. So, if a function doesn't have such a parameter and is not from the global level, it is from an instance level. As mentioned previously, instance-level functions are used for manipulating with physical devices, checking their properties, abilities and, creating logical devices.

Remember that extensions can also introduce new functions. You need to add their functions to the function loading code in order to be able to use the extension in the application. However, you shouldn't load functions introduced by a given extension without enabling the extension first during Instance creation. If these functions are not supported on a given platform, loading them will fail (it will return a null pointer).

So, in order to load instance-level functions, we should update the ListOfVulkanFunctions.inl file as follows:

#ifndef INSTANCE_LEVEL_VULKAN_FUNCTION 
#define INSTANCE_LEVEL_VULKAN_FUNCTION( function ) 
#endif 

INSTANCE_LEVEL_VULKAN_FUNCTION( vkEnumeratePhysicalDevices ) 
INSTANCE_LEVEL_VULKAN_FUNCTION( vkGetPhysicalDeviceProperties ) 
INSTANCE_LEVEL_VULKAN_FUNCTION( vkGetPhysicalDeviceFeatures ) 
INSTANCE_LEVEL_VULKAN_FUNCTION( vkCreateDevice ) 
INSTANCE_LEVEL_VULKAN_FUNCTION( vkGetDeviceProcAddr ) 
//... 

#undef INSTANCE_LEVEL_VULKAN_FUNCTION 

// 

#ifndef INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION 
#define INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( function, extension ) 
#endif 

INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( vkGetPhysicalDeviceSurfaceSupportKHR, VK_KHR_SURFACE_EXTENSION_NAME ) 
INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( vkGetPhysicalDeviceSurfaceCapabilitiesKHR, VK_KHR_SURFACE_EXTENSION_NAME ) 
INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( vkGetPhysicalDeviceSurfaceFormatsKHR, VK_KHR_SURFACE_EXTENSION_NAME ) 

#ifdef VK_USE_PLATFORM_WIN32_KHR 
INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( vkCreateWin32SurfaceKHR, VK_KHR_WIN32_SURFACE_EXTENSION_NAME ) 
#elif defined VK_USE_PLATFORM_XCB_KHR 
INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( vkCreateXcbSurfaceKHR, VK_KHR_XLIB_SURFACE_EXTENSION_NAME ) 
#elif defined VK_USE_PLATFORM_XLIB_KHR 
INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( vkCreateXlibSurfaceKHR, VK_KHR_XCB_SURFACE_EXTENSION_NAME ) 
#endif 

#undef INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION

In the preceding code, we added the names of several (but not all) instance-level functions. Each of them is wrapped into an INSTANCE_LEVEL_VULKAN_FUNCTION or an INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION macro, and is placed between #ifndef and the #undef preprocessor definitions.

To implement the instance-level functions loading recipe using the preceding macros, we should write the following code:

#define INSTANCE_LEVEL_VULKAN_FUNCTION( name )                        \  
name = (PFN_##name)vkGetInstanceProcAddr( instance, #name );          \ 
if( name == nullptr ) {                                               \ 
  std::cout << "Could not load instance-level Vulkan function named: "\
    #name << std::endl;                                               \ 
  return false;                                                       \ 
} 

#include "ListOfVulkanFunctions.inl" 

return true;

The preceding macro calls a vkGetInstanceProcAddr() function. It's the same function used to load global-level functions, but this time, the handle of a Vulkan Instance is provided in the first parameter. This way, we can load functions that can work properly only when an Instance object is created.

This function returns a pointer to the function whose name is provided in the second parameter. The returned value is of type void*, which is why it is then cast onto a type appropriate for a function we acquire the address of.

The type of a given function's prototype is defined based on its name, with a PFN_ before it. So, in the example, the type of the vkEnumeratePhysicalDevices() function's prototype will be defined as PFN_vkEnumeratePhysicalDevices.

If the vkGetInstanceProcAddr() function cannot find an address of the requested procedure, it returns nullptr. That's why we should perform a check and log the appropriate message in case of any problems.

The next step is to load functions that are introduced by extensions. Our function loading code acquires pointers of all functions that are specified with a proper macro in the ListOfVulkanFunctions.inl file, but we can't provide extension-specific functions in the same way, because they can be loaded only when appropriate extensions are enabled. When we don't enable any extension, only the core Vulkan API functions can be loaded. That's why we need to distinguish core API functions from extension-specific functions. We also need to know which extensions are enabled and which function comes from which extension. That's why a separate macro is used for functions introduced by extensions. Such a macro specifies a function name, but also the name of an extension in which a given function is specified. To load such functions, we can use the following code:

#define INSTANCE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION( name, extension )                                                                 \
for( auto & enabled_extension : enabled_extensions ) {            \
  if( std::string( enabled_extension ) == std::string( extension ) ) 
{                                                                 \  
    name = (PFN_##name)vkGetInstanceProcAddr( instance, #name );  \
    if( name == nullptr ) {                                       \
      std::cout << "Could not load instance-level Vulkan function named: "                                                          \
        #name << std::endl;                                       \
      return false;                                               \
    }                                                             \
  }                                                               \
} 

#include "ListOfVulkanFunctions.inl" 

return true;

enabled_extensions is a variable of type std::vector<char const *>, which contains the names of all enabled instance-level extensions. We iterate over all its elements and check whether the name of a given extension matches the name of an extension that introduces the provided function. If it does, we load the function in the same way as a normal core API function. Otherwise, we skip the pointer-loading code. If we don't enable the given extension, we can't load functions introduced by it.

See also

The following recipes in this chapter:

  • Preparing for loading Vulkan API functions
  • Loading function exported from a Vulkan Loader library
  • Loading global-level functions
  • Loading device-level functions
You have been reading a chapter from
Vulkan Cookbook
Published in: Apr 2017
Publisher: Packt
ISBN-13: 9781786468154
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime