Implementing debugging in Vulkan

Since debugging is exposed by validation layers, most of the core implementation of the debugging will be done under the VulkanLayerAndExtension class (VulkanLED.h/.cpp). In this section, we will learn about the implementation that will help us enable the debugging process in Vulkan:

The Vulkan debug facility is not part of the default core functionalities. Therefore, in order to enable debugging and access the report callback feature, we need to add the necessary extensions and layers:

  • Extension: Add the VK_EXT_DEBUG_REPORT_EXTENSION_NAME extension to the instance level. This will help in exposing the Vulkan debug APIs to the application:
      vector<const char *> instanceExtensionNames = { 
         . . . . // other extensios 
         VK_EXT_DEBUG_REPORT_EXTENSION_NAME, 
      }; 
  • Layer: Define the following layers at the instance level to allow debugging at these layers:
      vector<const char *> layerNames = { 
         "VK_LAYER_GOOGLE_threading",      
         "VK_LAYER_LUNARG_parameter_validation", 
         "VK_LAYER_LUNARG_device_limits",  
         "VK_LAYER_LUNARG_object_tracker", 
         "VK_LAYER_LUNARG_image",          
         "VK_LAYER_LUNARG_core_validation", 
         "VK_LAYER_LUNARG_swapchain",   
         "VK_LAYER_GOOGLE_unique_objects"    
      }; 
 

Note

In addition to the enabled validation layers, the LunarG SDK provides a special layer called VK_LAYER_LUNARG_standard_validation. This enables basic validation in the correct order as mentioned here. Also, this built-in metadata layer loads a standard set of validation layers in the optimal order. It is a good choice if you are not very specific when it comes to a layer.

a) VK_LAYER_GOOGLE_threading

b) VK_LAYER_LUNARG_parameter_validation

c) VK_LAYER_LUNARG_object_tracker

d) VK_LAYER_LUNARG_image

e) VK_LAYER_LUNARG_core_validation

f) VK_LAYER_LUNARG_swapchain

g) VK_LAYER_GOOGLE_unique_objects

These layers are then supplied to the vkCreateInstance() API to enable them:

VulkanApplication* appObj = VulkanApplication::GetInstance(); 
appObj->createVulkanInstance(layerNames,  
               instanceExtensionNames, title); 
 
// VulkanInstance::createInstance() 
VkResult VulkanInstance::createInstance(vector<const char *>&  
   layers, std::vector<const char *>& extensionNames, 
   char const*const appName) 
{ 
 
    . . . 
    VkInstanceCreateInfo instInfo      = {}; 

    // Specify the list of layer name to be enabled. 
    instInfo.enabledLayerCount   = layers.size(); 
    instInfo.ppEnabledLayerNames = layers.data();

    // Specify the list of extensions to

    // be used in the application. 
    instInfo.enabledExtensionCount     = extensionNames.size(); 
    instInfo.ppEnabledExtensionNames   = extensionNames.data(); 
    . . . 
 
    vkCreateInstance(&instInfo, NULL, &instance); 
} 

The validation layer is very specific to the vendor and SDK version. Therefore, it is advisable to first check whether the layers are supported by the underlying implementation before passing them to the vkCreateInstance() API. This way, the application remains portable throughout when run against another driver implementation. The areLayersSupported() function is a user-defined utility function that inspects the incoming layer names against system-supported layers. The unsupported layers are notified to the application and removed from the layer names before feeding them into the system:

// VulkanLED.cpp 
 
 VkBool32 VulkanLayerAndExtension::areLayersSupported 
         (vector<const     char *> &layerNames) 
{ 
    uint32_t checkCount = layerNames.size(); 
    uint32_t layerCount = layerPropertyList.size(); 
    std::vector<const char*> unsupportLayerNames; 
    for (uint32_t i = 0; i < checkCount; i++) { 
      VkBool32 isSupported = 0; 
      for (uint32_t j = 0; j < layerCount; j++) { 
      if (!strcmp(layerNames[i], layerPropertyList[j]. 
       properties.layerName)) { 
              isSupported = 1; 
          } 
      } 
 
      if (!isSupported) { 
          std::cout << "No Layer support found, removed" 
           " from layer: "<< layerNames[i] << endl; 
          unsupportLayerNames.push_back(layerNames[i]); 
      } 
    else { 
      cout << "Layer supported: " << layerNames[i] << endl; 
     } 
  } 
 
   for (auto i : unsupportLayerNames) { 
        auto it = std::find(layerNames.begin(),  
                     layerNames.end(), i); 
       if (it != layerNames.end()) layerNames.erase(it); 
    } 
 
   return true; 
  } 

The debug report is created using the vkCreateDebugReportCallbackEXT API. This API is not a part of Vulkan's core commands; therefore, the loader is unable to link it statically. If you try to access it in the following manner, you will get an undefined symbol reference error:

vkCreateDebugReportCallbackEXT(instance, NULL, NULL, NULL); 

All debug-related APIs need to be queried using the vkGetInstanceProcAddr() API and linked dynamically. The retrieved API reference is stored in a corresponding function pointer called PFN_vkCreateDebugReportCallbackEXT. The VulkanLayerAndExtension::createDebugReportCallback() function retrieves the create and destroy debug APIs, as shown in the following implementation:

/********* VulkanLED.h *********/

// Declaration of the create and destroy function pointers 
PFN_vkCreateDebugReportCallbackEXT  dbgCreateDebugReportCallback; 
PFN_vkDestroyDebugReportCallbackEXT dbgDestroyDebugReportCallback; 
 
 
/********* VulkanLED.cpp *********/ 
VulkanLayerAndExtension::createDebugReportCallback(){ 
  . . .  
 
// Get vkCreateDebugReportCallbackEXT API 
dbgCreateDebugReportCallback=(PFN_vkCreateDebugReportCallbackEXT)  
vkGetInstanceProcAddr(*instance,"vkCreateDebugReportCallbackEXT"); 
    
  if (!dbgCreateDebugReportCallback) { 
         std::cout << "Error: GetInstanceProcAddr unable to locate 
 vkCreateDebugReportCallbackEXT function.
"; 
         return VK_ERROR_INITIALIZATION_FAILED; 
     } 
   
  // Get vkDestroyDebugReportCallbackEXT API 
  dbgDestroyDebugReportCallback=  
 (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr 
 (*instance, "vkDestroyDebugReportCallbackEXT"); 
 
  if (!dbgDestroyDebugReportCallback) { 
   std::cout << "Error: GetInstanceProcAddr unable to locate  
         vkDestroyDebugReportCallbackEXT function.
"; 
   return VK_ERROR_INITIALIZATION_FAILED; 
  } 
  . . .  
 } 

The vkGetInstanceProcAddr() API obtains instance-level extensions dynamically; these extensions are not exposed statically on a platform and need to be linked through this API dynamically. The following is the signature of this API:

PFN_vkVoidFunction vkGetInstanceProcAddr( 
                             VkInstance     instance, 
                             const char*    name); 

The following table describes the API fields:

Parameters

Description

instance

This is a VkInstance variable. If this variable is NULL, then the name must be one of these: vkEnumerateInstanceExtensionProperties, vkEnumerateInstanceLayerProperties, or vkCreateInstance.

name

This is the name of the API that needs to be queried for dynamic linking.

Using the dbgCreateDebugReportCallback() function pointer, create the debugging report object and store the handle in debugReportCallback. The second parameter of the API accepts a VkDebugReportCallbackCreateInfoEXT control structure. This data structure defines the behavior of the debugging, such as what the debug information should include: errors, general warnings, information, performance-related warnings, debug information, and so on. In addition, it also takes the reference of a user-defined function (debugFunction); this helps filter and print the debugging information once it is retrieved from the system. Here's the syntax for creating a debugging report:

struct VkDebugReportCallbackCreateInfoEXT { 
         VkStructureType                 type; 
         const void*                     pNext; 
         VkDebugReportFlagsEXT           flags; 
         PFN_vkDebugReportCallbackEXT    fnCallback; 
         void*                           pUserData; 
}; 

The following table describes the purpose of the mentioned API fields:

Parameters

Description

type

This is the type information of this control structure. It must be specified as VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT.

flags

This is to define the kind of debugging information to be retrieved when debugging is on; the next table defines these flags.

fnCallback

This field refers to the function that filters and displays debug messages.

The VkDebugReportFlagBitsEXT control structure can exhibit a bitwise combination of the following flag values:

Flag values

Description

VK_DEBUG_REPORT_INFORMATION_BIT_EXT

This is to display user-friendly information describing the background activities in the currently running application, for example, resource details that may be useful when debugging an application.

VK_DEBUG_REPORT_WARNING_BIT_EXT

This is to provide a warning message for potentially incorrect or dangerous use of the API.

VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT

This indicates a potentially non-optimal use of Vulkan, which may result in performance loss.

VK_DEBUG_REPORT_ERROR_BIT_EXT

This refers to an error message specifying incorrect API usage, which may cause undefined results--for example, application crash.

VK_DEBUG_REPORT_DEBUG_BIT_EXT

This indicates diagnostic information from the loader and layer.

The createDebugReportCallback function implements the creation of the debug report. First, it creates the VulkanLayerAndExtension control structure object and fills it with relevant information. This primarily includes two things: first, assigning a user-defined function (pfnCallback) that will print the debug information received from the system (see the next point), and second, assigning the debugging flag (flags) in which the programmer is interested:

/********* VulkanLED.h *********/

// Handle of the debug report callback 
VkDebugReportCallbackEXT debugReportCallback; 
    
// Debug report callback create information control structure 
VkDebugReportCallbackCreateInfoEXT dbgReportCreateInfo = {}; 
 
 
/********* VulkanLED.cpp *********/ 
VulkanLayerAndExtension::createDebugReportCallback(){ 
   . . .  
   // Define the debug report control structure,
 
  // provide the reference of 'debugFunction',

   // this function prints the debug information on the console. 
   dbgReportCreateInfo.sType       = VK_STRUCTURE_TYPE_DEBUG
                                     _REPORT_CREATE_INFO_EXT; 
   dbgReportCreateInfo.pfnCallback = debugFunction; 
   dbgReportCreateInfo.pUserData   = NULL; 
   dbgReportCreateInfo.pNext       = NULL; 
   dbgReportCreateInfo.flags       = VK_DEBUG_REPORT_WARNING_BIT_EXT | 
                                     VK_DEBUG_REPORT_PERFORMANCE
                                     _WARNING_BIT_EXT | 
                                     VK_DEBUG_REPORT_ERROR_BIT_EXT | 
                                     VK_DEBUG_REPORT_DEBUG_BIT_EXT; 
 
   // Create the debug report callback and store the handle

   // into 'debugReportCallback' 
   result = dbgCreateDebugReportCallback 
       (*instance, &dbgReportCreateInfo, NULL, &debugReportCallback); 
 
   if (result == VK_SUCCESS) { 
    cout << "Debug report callback object created successfully
"; 
   } 
   return result; 
} 

Define the debugFunction() function that prints the retrieved debug information in a user-friendly way. It describes the type of debug information along with the reported message:

VKAPI_ATTR VkBool32 VKAPI_CALL 
VulkanLayerAndExtension::debugFunction( VkFlags msgFlags, 
             VkDebugReportObjectTypeEXT objType, uint64_t srcObject,  
             size_t location, int32_t msgCode, const char *pLayerPrefix, 
             const char *pMsg, void *pUserData) { 
    
       if (msgFlags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
         std::cout << "[VK_DEBUG_REPORT] ERROR: ["<<layerPrefix<<"] 
                             Code" << msgCode << ":" << msg << std::endl; 
 
       } 
       else if (msgFlags & VK_DEBUG_REPORT_WARNING_BIT_EXT) { 
         std::cout << "[VK_DEBUG_REPORT] WARNING: ["<<layerPrefix<<"]
                             Code" << msgCode << ":" << msg << std::endl; 
       } 
       else if (msgFlags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT) { 
         std::cout<<"[VK_DEBUG_REPORT] INFORMATION:[" <<layerPrefix<<"]
                             Code" << msgCode << ":" << msg << std::endl;
 
      } 
       else if(msgFlags& VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT){ 
         cout <<"[VK_DEBUG_REPORT] PERFORMANCE: ["<<layerPrefix<<"]
                             Code" << msgCode << ":" << msg << std::endl;
      } 
       else if (msgFlags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) { 
         cout << "[VK_DEBUG_REPORT] DEBUG: ["<<layerPrefix<<"] 
                            Code" << msgCode << ":" << msg <<  std::endl;
      } 
       else { 
       return VK_FALSE; 
       } 
 
       return VK_SUCCESS; 
     } 

The following table describes the various fields from the debugFunction() callback:

Parameters

Description

msgFlags

This specifies the type of debugging event that has triggered the call, for example, an error, warning, performance warning, and so on.

objType

This is the type object that is manipulated by the triggering call.

srcObject

This is the handle of the object that's being created or manipulated by the triggered call.

location

This refers to the location of the code describing the event.

msgCode

This refers to the message code.

layerPrefix

This is the layer responsible for triggering the debug event.

msg

This field contains the debug message text.

userData

Any application-specific user data is specified to the callback using this field.

Tip

The debugFunction callback has a Boolean return value. The true return value indicates the continuation of the command chain to subsequent validation layers even after an error has occurred.

However, the false value instructs the validation layer to abort the execution when an error occurs. It is advisable to stop the execution at the very first error.

The presence of an error itself indicates that something has occurred unexpectedly; letting the system run in these circumstances may lead to undefined results or further errors, which could be completely senseless sometimes. In the latter case, where the execution is aborted, it provides a better opportunity for the developer to concentrate and fix the reported error. In contrast, the former approach, where the system throws a bunch of errors, may be cumbersome leaving the developers in a confused state sometimes.

In order to enable debugging at vkCreateInstance, provide dbgReportCreateInfo to the pNext field of VkInstanceCreateInfo structure:

       VkInstanceCreateInfo instInfo   = {}; 
             . . .  
    instInfo.pNext = &layerExtension.dbgReportCreateInfo; 
    vkCreateInstance(&instInfo, NULL, &instance); 

Finally, once the debug is no longer in use, destroy the debug callback object:

void VulkanLayerAndExtension::destroyDebugReportCallback(){ 
     VulkanApplication* appObj = VulkanApplication::GetInstance(); 
     dbgDestroyDebugReportCallback(instance,debugReportCallback,NULL); 
    } 

The following is the output from the implemented debug report. Your output may differ from this based on the GPU vendor and SDK provider. Also, the explanations of the errors or warnings reported are very specific to the SDK itself. But at a higher level, the specification will hold; this means you can expect to see a debug report with a warning, information, debugging help, and so on, based on the debugging flag you have turned on.

Implementing debugging in Vulkan

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.217.5