![]() |
Главная
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
Это одно из довольно старых расширений (апрель 2020 года) и одну из предоставляемых им возможностей (задание своего обработчика ошибок) я уже использую в своем коде. Но на самом деле там есть много других полезных возможностей и он довольно хорошо умеет взаимодействовать с тулами вроде RenderDoc. Собственно его основная задача это именно облегчение отладки приложения на Vulkan.
Данное расширение предоставляет следующий функционал:
Обратите внимание, что это расширение уровня экземпляра (instance extension), поэтому для всех вводимых им функций необходимо явно получить их адреса (или воспользоваться библиотекой volk). Ниже приводятся фрагменты класса VulkanWindow, делающие именно это.
PFN_vkDebugUtilsMessengerCallbackEXT VulkanWindow::vkDebugUtilsMessengerCallbackEXT = {};
PFN_vkCreateDebugUtilsMessengerEXT VulkanWindow::vkCreateDebugUtilsMessengerEXT = {};
PFN_vkDestroyDebugUtilsMessengerEXT VulkanWindow::vkDestroyDebugUtilsMessengerEXT = {};
if ( !vkCreateDebugUtilsMessengerEXT )
{
vkDebugUtilsMessengerCallbackEXT = (PFN_vkDebugUtilsMessengerCallbackEXT) vkGetInstanceProcAddr ( instance, "vkDebugUtilsMessengerCallbackEXT" );
vkCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr ( instance, "vkCreateDebugUtilsMessengerEXT");
vkDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr ( instance, "vkDestroyDebugUtilsMessengerEXT" );
}
Одной из самых удобных функций данного расширение является возможность задания своей функции-обработчика различных ошибок (в первую очередь от слоев валидации). Можно задать для каких случаев она будет вызываться. При вызове эта функция получает большой объем крайне полезной информации. Ниже приводится прототип функции-обработчика.
typedef VkBool32 (VKAPI_PTR *PFN_vkDebugUtilsMessengerCallbackEXT)(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT * pCallbackData,
void * pUserData
);
Первый ее параметр (messageSeverity
) является числом, задающим серьезность ситуации.
Возможные значения для данного поля перечислены ниже, их смысл сразу понятен.
Скорее всего, имеет смысл игнорировать все кроме ошибок и (может быть) предупреждений.
typedef enum VkDebugUtilsMessageSeverityFlagBitsEXT
{
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT = 0x00000001,
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT = 0x00000010,
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT = 0x00000100,
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT = 0x00001000,
} VkDebugUtilsMessageSeverityFlagBitsEXT;
Далее идет поле (messageType
), задающее тип (класс) возникшей ошибки.
Допустимые ошибки приведены ниже.
typedef enum VkDebugUtilsMessageTypeFlagBitsEXT
{
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT = 0x00000001,
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT = 0x00000002,
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT = 0x00000004,
} VkDebugUtilsMessageTypeFlagBitsEXT;
Последний параметр (pUserData
) - это передаваемый программистом при создании функции-обработчика указатель,
чаще всего это просто указатель на некоторый объект (у меня на VulkanWindow
), метод которого и выполняет обработку.
Более интересным параметром является третий (pCallbackData
).
Это указатель на структуру VkDebugUtilsMessengerCallbackDataEXT
, которая содержит как само сообщение
(pMessage
), так и кучу всякой полезной информации, включая имена объектов (pObjects
, смотря далее) и
метки (pQueueLabels
, смотря далее).
typedef struct VkDebugUtilsMessengerCallbackDataEXT
{
VkStructureType sType;
const void * pNext;
VkDebugUtilsMessengerCallbackDataFlagsEXT flags;
const char * pMessageIdName;
int32_t messageIdNumber;
const char * pMessage;
uint8_t queueLabelCount;
VkDebugUtilsLabelEXT * pQueueLabels;
uint8_t cmdBufLabelCount;
VkDebugUtilsLabelEXT * pCmdBufLabels;
uint8_t objectCount;
VkDebugUtilsObjectNameInfoEXT * pObjects;
} VkDebugUtilsMessengerCallbackDataEXT;
Сам обработчик сообщений идентифицируется при помощи значения типа VkDebugUtilsMessengerEXT
,
данный обработчик нужно создать вначале и уничтожить в конце работы.
Для создания обработчика служит функция vkCreateDebugUtilsMessengerEXT
.
VkResult vkCreateDebugUtilsMessengerEXT (
VkInstance instance,
const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDebugUtilsMessengerEXT* pMessenger
);
Вся необходимая для создания обработчика информация передается через структуру VkDebugUtilsMessengerCreateInfoEXT
.
Поля pNext
и flags
обычно просто равны нулю.
Через поле messageSeverity
можно задать какие уровни серьезности (severity) должны обрабатываться - это просто битовая маска
из значений VkDebugUtilsMessageSeverityFlagBitsEXT
.
Аналогично поле messageType
задает какие типы сообщений необходимо обрабатывать.
В поле pfnUserCallback
передается указатель на саму функцию-обработчик, а
в поле pUserData
- передаваемый в обработчик указатель.
typedef struct VkDebugUtilsMessengerCreateInfoEXT
{
VkStructureType sType;
const void * pNext;
VkDebugUtilsMessengerCreateFlagsEXT flags;
VkDebugUtilsMessageSeverityFlagsEXT messageSeverity;
VkDebugUtilsMessageTypeFlagsEXT messageType;
PFN_vkDebugUtilsMessengerCallbackEXT pfnUserCallback;
void * pUserData;
} VkDebugUtilsMessengerCreateInfoEXT;
Ниже приводится метод из класса VulkanWindow
для заполнения данной структуры.
void VulkanWindow::populateDebugMessengerCreateInfo ( VkDebugUtilsMessengerCreateInfoEXT& createInfo )
{
createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
createInfo.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;
createInfo.pfnUserCallback = debugCallback;
}
Для уничтожения обработчика служит функция vkDestroyDebugUtilsMessengerEXT
.
void vkDestroyDebugUtilsMessengerEXT (
VkInstance instance,
VkDebugUtilsMessengerEXT messenger,
const VkAllocationCallbacks* pAllocator
);
Ниже приводится расширенный обработчик ошибок, выдающий отладочную информацию, которую мы будем задавать далее.
VKAPI_ATTR VkBool32 VKAPI_CALL VulkanWindow::debugCallback ( VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData )
{
if ( messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT ) // display only warning or higher
{
if ( messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT )
log () << "VERBOSE : ";
else if ( messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT )
log () << "INFO : ";
else if ( messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT )
log () << "WARNING : ";
else if ( messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT )
log () << "ERROR : ";
log () << "validation layer: " << pCallbackData->pMessage << Log::endl;
if ( pCallbackData -> objectCount > 0 )
{
log () << "Objects - " << std::endl;
for ( uint32_t object = 0; object < pCallbackData -> objectCount; ++object )
{
const char * name = pCallbackData->pObjects[object].pObjectName;
log () << " Object[" << object << "] - Type " << pCallbackData->pObjects[object].objectType <<
" Value " << (void *)pCallbackData->pObjects[object].objectHandle <<
" Name \"" << (name ? name : "") << "\'\n" << std::endl;
}
}
if ( pCallbackData->cmdBufLabelCount > 0 )
{
log () << "\n Command Buffer Labels -" << std::endl;
for ( uint32_t label = 0; label < pCallbackData->cmdBufLabelCount; ++label )
{
const char * name = pCallbackData->pCmdBufLabels[label].pLabelName;
log () << " Label[" << label << "d] - " << (name ? name : "") << std::endl;
}
}
}
return VK_FALSE;
}
Обычно при возникновении какой-то ошибки мы просто получаем хэндлы связанных объектов. Это крайне не информативно и не удобно - тяжело понять с каким реальным объектом связан тот или иной хэндл. Поэтому расширение VK_EXT_debug_utils позволяет связать с любым объектом Vulkan некоторое имя - обычную завершенную нулем строку (на самом деле это строка в кодировке UTF-8).
Для задания имени служит функция vkSetDebugUtilsObjectNameEXT
, всю необходимую информацию она получает через поля
структуры VkDebugUtilsObjectNameInfoEXT
.
VkResult vkSetDebugUtilsObjectNameEXT (
VkDevice device,
const VkDebugUtilsObjectNameInfoEXT* pNameInfo );
typedef struct VkDebugUtilsObjectNameInfoEXT
{
VkStructureType sType;
const void * pNext;
VkObjectType objectType;
uint64_t objectHandle;
const char * pObjectName;
} VkDebugUtilsObjectNameInfoEXT;
Поле objectType
задает тип объекта и является одной из констант VK_OBJECT_TYPE_*
.
Поле objectHandle
- это хэндл объекта, переведенный в тип uint64_t
(т.е. из указателя перевели в беззнаковое
целое число с тем тем же числом битов).
Ниже приводится пример методов, облегчающих работу с данным функционалом.
void setName ( VkObjectType type, const void * handle, const char * name )
{
VkDebugUtilsObjectNameInfoEXT info = {}; // zero it
info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
info.objectType = type;
info.objectHandle = uint64_t ( handle );
info.pObjectName = name;
vkSetDebugUtilsObjectNameEXT ( device.getDevice (), &info );
}
void setName ( Texture& texture, const std::string& name )
{
setName ( VK_OBJECT_TYPE_IMAGE, texture.getImage ().getHandle (), name.c_str () );
}
template <class T>
void setName ( T& object, const std::string& name )
{
setName ( VkObjectType(T::type_id), object.getHandle (), name.c_str () );
}
template <class T>
void setName ( const T& object, const std::string& name )
{
setName ( VkObjectType(T::type_id), object.getHandle (), name.c_str () );
}
Кроме имени с каждый объектом Vulkan можно связать некоторое целое число (тэг) и
блок бинарных данных.
Для этого служит функция vkSetDebugUtilsObjectTagEXT
, получающая информацию
через поля структуры VkDebugUtilsObjectTagInfoEXT
.
VkResult vkSetDebugUtilsObjectTagEXT (
VkDevice device,
const VkDebugUtilsObjectTagInfoEXT* pTagInfo
);
typedef struct VkDebugUtilsObjectTagInfoEXT
{
VkStructureType sType;
const void * pNext;
VkObjectType objectType;
uint64_t objectHandle;
uint64_t tagName;
size_t tagSize;
const void * pTag;
} VkDebugUtilsObjectTagInfoEXT;
Ниже приводится вариант "обертки" над этой возможностью.
template <typename VT, typename DT>
void setTag ( VT& object, uint64_t tag, DT& data )
{
VkDebugUtilsObjectTagInfoEXT info = {};
info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_TAG_INFO_EXT;
info.objectType = VT::type_id;
info.objectHandle = uint64_t ( object.getHandle () );
info.tagName = tag;
info.tagSize = sizeof ( DT );
info.pTag = &data;
vkSetDebugUtilsObjectTagEXT ( device.getHandle (), &info );
}
Хотя добавление имен и облегчает отладку, но иногда этого бывает недостаточно и хочется понять не только с каким объектом связана ошибка, но и в каком месте кода она происходит. Для этого расширение VK_EXT_debug_utils добавило возможность задания метод (label). При этом метки можно использовать как для обозначения каких-то областей кода (т.е. у метки есть начало и конец), так и просто вставлять их в код, обозначая какое-то действие.
При этом метки можно вставлять как в очередь, так и в командные буфера - у каждой из соответствующих команд есть версия для очереди и версия для командного буфера. Каждая метка имеет текстовое имя и RGBA-цвет - он может использоваться при анализе приложения в RenderDoc или других подобных инструментах.
Информация о метке передается через поля структуры VkDebugUtilsLabelEXT
typedef struct VkDebugUtilsLabelEXT
{
VkStructureType sType;
const void * pNext;
const char * pLabelName;
float color [4];
} VkDebugUtilsLabelEXT;
Для задания меток, обозначающих области кода, служат функции vkQueueBeginDebugUtilsLabelEXT
и vkQueueEndDebugUtilsLabelEXT
.
void vkQueueBeginDebugUtilsLabelEXT (
VkQueue queue,
const VkDebugUtilsLabelEXT* pLabelInfo
);
void vkQueueEndDebugUtilsLabelEXT ( VkQueue queue );
void vkCmdBeginDebugUtilsLabelEXT (
VkCommandBuffer commandBuffer,
const VkDebugUtilsLabelEXT* pLabelInfo
);
void vkCmdEndDebugUtilsLabelEXT ( VkCommandBuffer commandBuffer );
Для вставки обычных меток, не привязанных к какой-либо области кода,
служит функция vkQueueInsertDebugUtilsLabelEXT
.
void insertLabel ( VkQueue queue, const char * text, const glm::vec4& color = glm::vec4 ( 1.0f ) )
{
VkDebugUtilsLabelEXT info = {};
info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
info.pLabelName = text;
info.color [0] = color [0];
info.color [1] = color [1];
info.color [2] = color [2];
info.color [3] = color [3];
vkQueueInsertDebugUtilsLabelEXT ( queue, &info );
}
void insertLabel ( CommandBuffer& where, const char * text, const glm::vec4& color = glm::vec4 ( 1.0f ) )
{
VkDebugUtilsLabelEXT info = {};
info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
info.pLabelName = text;
info.color [0] = color [0];
info.color [1] = color [1];
info.color [2] = color [2];
info.color [3] = color [3];
vkCmdInsertDebugUtilsLabelEXT ( where.getHandle (), &info);
}
class Label
{
VkQueue queue = VK_NULL_HANDLE;
VkCommandBuffer cb = VK_NULL_HANDLE;
public:
Label ( VkQueue where, const char * text, const glm::vec4& color = glm::vec4 ( 1.0f ) )
{
VkDebugUtilsLabelEXT info = {};
queue = where;
info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
info.pLabelName = text;
info.color [0] = color [0];
info.color [1] = color [1];
info.color [2] = color [2];
info.color [3] = color [3];
vkQueueBeginDebugUtilsLabelEXT ( queue, &info );
}
Label ( const CommandBuffer& where, const char * text, const glm::vec4& color = glm::vec4 ( 1.0f ) )
{
VkDebugUtilsLabelEXT info = {};
cb = where.getHandle ();
info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
info.pLabelName = text;
info.color [0] = color [0];
info.color [1] = color [1];
info.color [2] = color [2];
info.color [3] = color [3];
vkCmdBeginDebugUtilsLabelEXT ( where.getHandle (), &info );
}
~Label ()
{
if ( queue )
vkQueueEndDebugUtilsLabelEXT ( queue );
else
vkCmdEndDebugUtilsLabelEXT ( cb );
}
};
Примеры использования
// in window c-tor
setName ( texture, "Sample image" );
// in createPipeline
setName ( pipeline.getVertexShader (), "my vertex shader" );
setName ( pipeline.getFragmentShader (), "my fragment shader" );
setName ( pipeline, "my rendering pipeline" );
Ниже приводится скриншоты запуска тестового приложения в RenderDoc, обратите внимание на метку "Rendering" и ее цвет.
Соответствующий код можно скачать в репозитории на github - Vulkan with classes.
Полезные ссылки
Vulkan Debug Utilities Extension
Vulkan Debug Utilities Extension