2019年11月18日 星期一

開啟Vulkan的 Debug mode

開啟Vulkan的 Debug mode

前言

  最近在入門 Vulkan ,發現開啟 Debug mode 有一些麻煩,不像 Direct3D 只要設定旗標就可以開啟, Vulkan 需要比較多的步驟,在此把學習的過程做個紀錄。

內容

  在創建 VkInstance 會透過 VkInstanceCreateInfo 來輸入參數, VkInstanceCreateInfo 的定義如下
typedef struct VkInstanceCreateInfo {
    VkStructureType             sType;
    const void*                 pNext;
    VkInstanceCreateFlags       flags;
    const VkApplicationInfo*    pApplicationInfo;
    uint32_t                    enabledLayerCount;
    const char* const*          ppEnabledLayerNames;
    uint32_t                    enabledExtensionCount;
    const char* const*          ppEnabledExtensionNames;
} VkInstanceCreateInfo;

在 ppEnabledLayerNames 的陣列裡要加入"VK_LAYER_KHRONOS_validation",而 ppEnabledExtensionNames 的陣列要加入"VK_EXT_debug_utils",這兩個缺一不可,由於剛入門所以有踩到坑,我踩得坑是指加入"VK_EXT_debug_utils",會發生什麼事呢?我會告訴你行為不可預期, VkInstance 雖然可以正常產出,但其它的 API 可能會出問題,我發生問題狀況是 vkCreateGraphicsPipelines() 喚起時一定 Access violation 。在 pNext 的參數要設定VkDebugUtilsMessengerCreateInfoEXT ,範例如下
VkInstanceCreateInfo createInstanceInfo;
//...
//
VkDebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfo;
debugUtilsMessengerCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
debugUtilsMessengerCreateInfo.pNext = NULL;
debugUtilsMessengerCreateInfo.flags = 0;
debugUtilsMessengerCreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT|VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
debugUtilsMessengerCreateInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
                                            VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
                                            VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
debugUtilsMessengerCreateInfo.pfnUserCallback = myDebugCallBack;
debugUtilsMessengerCreateInfo.pUserData = NULL;
//
createInstanceInfo.pNext = &debugUtilsMessengerCreateInfo;
//
//call vkCreateInstance() to create
//...

在設定 pfnUserCallback 時需要一個 Function pointer ,可以設定成以下( 在Windows 平台)
VkBool32 myDebugCallBack(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,VkDebugUtilsMessageTypeFlagsEXT messageType,const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData,void *pUserData)
{
  char prefix[64] = "";
  char *message = (char *)malloc(strlen(pCallbackData->pMessage) + 5000);
  //
  if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) 
  {
    strcat(prefix, "VERBOSE : ");
  } 
  else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) 
  {
    strcat(prefix, "INFO : ");
  } 
  else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) 
  {
    strcat(prefix, "WARNING : ");
  } 
  else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) 
  {
    strcat(prefix, "ERROR : ");
  }
  //
  if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) 
  {
    strcat(prefix, "GENERAL");
  } 
  else 
  {
    if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) 
    {
      strcat(prefix, "VALIDATION");  
    }
    if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) 
    {
      if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) 
      {
        strcat(prefix, "|");
      }
      strcat(prefix, "PERFORMANCE");
    }
  }
  sprintf(message, "%s - Message Id Number: %d | Message Id Name: %s\n\t%s\n", prefix, pCallbackData->messageIdNumber,
  pCallbackData->pMessageIdName, pCallbackData->pMessage);
  if (pCallbackData->objectCount > 0) 
  {
    char tmp_message[500];
    sprintf(tmp_message, "\n\tObjects - %d\n", pCallbackData->objectCount);
    strcat(message, tmp_message);
    for (uint32_t object = 0; object < pCallbackData->objectCount; ++object) 
    {
      if (NULL != pCallbackData->pObjects[object].pObjectName && strlen(pCallbackData->pObjects[object].pObjectName) > 0) 
      {
        sprintf(tmp_message, "\t\tObject[%d] - %s, Handle %p, Name \"%s\"\n", object,
                ConvertVkObjectType(pCallbackData->pObjects[object].objectType),
                (void *)(pCallbackData->pObjects[object].objectHandle), pCallbackData->pObjects[object].pObjectName);
      } 
      else 
      {
        sprintf(tmp_message, "\t\tObject[%d] - %s, Handle %p\n", object,
                ConvertVkObjectType(pCallbackData->pObjects[object].objectType),
                (void *)(pCallbackData->pObjects[object].objectHandle));
      }
      strcat(message, tmp_message);
    }
  }
  if (pCallbackData->cmdBufLabelCount > 0) 
  {
    char tmp_message[500];
    sprintf(tmp_message, "\n\tCommand Buffer Labels - %d\n", pCallbackData->cmdBufLabelCount);
    strcat(message, tmp_message);
    for (uint32_t cmd_buf_label = 0; cmd_buf_label < pCallbackData->cmdBufLabelCount; ++cmd_buf_label) 
    {
      sprintf(tmp_message, "\t\tLabel[%d] - %s { %f, %f, %f, %f}\n", cmd_buf_label,
              pCallbackData->pCmdBufLabels[cmd_buf_label].pLabelName, pCallbackData->pCmdBufLabels[cmd_buf_label].color[0],
              pCallbackData->pCmdBufLabels[cmd_buf_label].color[1], pCallbackData->pCmdBufLabels[cmd_buf_label].color[2],
              pCallbackData->pCmdBufLabels[cmd_buf_label].color[3]);
      strcat(message, tmp_message);
    }
  }

  ::OutputDebugStringA(message);
  fflush(stdout);
  //
  free(message);

  // Don't bail out, but keep going.
  return false;
}

這個 Function 是從 Vulkan SDK 的範例裡拔出來的,紅字的部分是顯示 Debug 訊息的地方,請依不同的平台需求來改造。設定 VkInstanceCreateInfo.pNext 的部分要指向 VkDebugUtilsMessengerCreateInfoEXT ,這個用法相當特別,正常的 pNext 都會是 NULL。

  在 VkInstanceCreateInfo 裡設好參數並確實的創建 VkInstance 後需要取得 Debug mode 需要的 Function ,因為是 Extension ,所以要另外取得可以理解,範例如下
fCreateDebugUtilsMessengerEXT =(PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(hInstance,"vkCreateDebugUtilsMessengerEXT");
fDestroyDebugUtilsMessengerEXT =(PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(hInstance,"vkDestroyDebugUtilsMessengerEXT");
fSubmitDebugUtilsMessageEXT =(PFN_vkSubmitDebugUtilsMessageEXT)vkGetInstanceProcAddr(hInstance,"vkSubmitDebugUtilsMessageEXT");
fCmdBeginDebugUtilsLabelEXT=(PFN_vkCmdBeginDebugUtilsLabelEXT)vkGetInstanceProcAddr(hInstance,"vkCmdBeginDebugUtilsLabelEXT");
fCmdEndDebugUtilsLabelEXT=(PFN_vkCmdEndDebugUtilsLabelEXT)vkGetInstanceProcAddr(hInstance,"vkCmdEndDebugUtilsLabelEXT");
fCmdInsertDebugUtilsLabelEXT=(PFN_vkCmdInsertDebugUtilsLabelEXT)vkGetInstanceProcAddr(hInstance,"vkCmdInsertDebugUtilsLabelEXT");
fSetDebugUtilsObjectNameEXT=(PFN_vkSetDebugUtilsObjectNameEXT)vkGetInstanceProcAddr(hInstance,"vkSetDebugUtilsObjectNameEXT");

這每個都是 Function pointer ,範例沒宣告型別名稱,要型別名稱可以看式子的右側都有 cast。取得 Function pointer 後再利用 fCreateDebugUtilsMessengerEXT() 來創建 VkDebugUtilsMessengerEXT  ,範例如下
VkDebugUtilsMessengerEXT hDebugUtilsMessenger;
//Create...
fCreateDebugUtilsMessengerEXT(hInstance,&debugUtilsMessengerCreateInfo,NULL,&hDebugUtilsMessenger);

//Destroy...
fDestroyDebugUtilsMessengerEXT(hInstance,hDebugUtilsMessenger,NULL);

在創建完後就代表 Debug mode 開啟了。既然創建了 VkDebugUtilsMessengerEXT ,在程式最後要記得透過fDestroyDebugUtilsMessengerEXT() 來銷毀 VkDebugUtilsMessengerEXT 。

  最後抱怨一下, 這個啟動過程的設計我覺得非常糟糕,流程只有兩個,創建 VkInstance 與創建 VkDebugUtilsMessengerEXT ,但兩個流程的參數都需要 VkDebugUtilsMessengerCreateInfoEXT ,設定的方法還很特別,不看範例很難記得。還有在啟動"VK_LAYER_KHRONOS_validation"與"VK_EXT_debug_utils"的部分也設計得很糟,Layer 與 Extension 的啟動有相依性,錯了也不報錯。Call back 的部分我覺得明明官方就可以給預設,但就是要自己寫一個,規格還有點複雜,這部份的設計也很奇怪。

參考資料

[ 書籍 ] Vulkan 應用開發指南,出版商:人民郵電,ISBN-13:9787115506801

沒有留言:

張貼留言