開啟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