Validation layer
介绍:
vulkan API 是围绕最小驱动程序开销的理念设计的,因此最简单的错误通常不是 显式处理,只会导致崩溃或未定义的行为
Vulkan 没有内置任何验证层,但 LunarG Vulkan SDK 提供了一组很好的层来检查常见错误Validation layers验证层(可选),。使用 验证层是避免应用程序中断的最佳方法 ,验证层中的常见操作包括:
- 根据规范检查参数值以检测误用
- 跟踪对象的创建和销毁以查找资源泄漏
- 通过跟踪调用源自
- 将每个调用及其参数记录到标准输出中
- 跟踪 Vulkan 需要进行分析和重放
Vulkan 中以前有两种不同类型的验证层:instance and device specific实例 和特定设备,实例层只会检查 与全局 Vulkan 对象(如实例和设备特定层)相关的调用 将仅检查与特定 GPU 相关的调用,但是目前,device specific以弃用
启用:
首先定义两个global变量,验证层的名称(所有有用的标准验证都在这个层中)和是否启用它们
const std::vector<const char*> validationLayers = {
"VK_LAYER_KHRONOS_validation"
};
#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif
首先定义一个检查是否支持验证层的函数,首先通过vkEnumerateInstanceLayerProperties列出所有可用层,然后for循环检查"VK_LAYER_KHRONOS_validation"是否存在,并返回bool
bool checkValidationLayerSupport() {
uint32_t layerCount;
vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
std::vector<VkLayerProperties> availableLayers(layerCount);
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
for (const char* layerName : validationLayers) {//第一个const char*的字符数组
bool layerFound = false;
for (const auto& layerProperties : availableLayers) {
if (strcmp(layerName, layerProperties.layerName) == 0) {
layerFound = true;
break;
}
}
if (!layerFound) {
return false;
}
}
return true;
}
现在可以首先检查 Validation layers,如果启用了验证层,但未找到Validation layers,则抛出异常
if (enableValidationLayers && !checkValidationLayerSupport()) {
throw std::runtime_error("validation layers requested, but not available!");
}
这里设置 VkInstanceCreateInfoVkInstanceCreateInfo实例的其启用层数量,和启用层的名称(如果启用的话)VkInstanceCreateInfo
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
}
else {
createInfo.enabledLayerCount = 0;
}
运行程序,不应该返回任何错误
消息回调
介绍
验证层会将调试消息打印到标准输出中,但我们也可以通过在程序中提供显式回调Callback(向函数传入可调用类型:比如函数指针)来处理消息,从而可以决定希望看到哪种类型的消息,而并非所有信息(比如仅输出致命错误)
扩展
要在程序中设置消息回调,我们必须使用带有回调VK_EXT_DEBUG_UTILS_EXTENSION_NAME(调式信息工具)的扩展
首先在createInstance的glfwGetRequiredInstanceExtensions放在一个函数中,并在获取的所有扩展数组中添加push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)
std::vector<const char*> getRequiredExtensions() {
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
if (enableValidationLayers) {
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
return extensions;
}
回调返回一个布尔值,指示是否触发了 Vulkan 调用 应中止验证层消息。如果回调返回 true,则 ,则调用将中止并显示错误
设置自定义回调函数:
第一个参数消息严重程度指定消息的严重性,这是以下Flag标志(ENUM:0,1,2,3)
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT
:诊断消息VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
:信息性消息,如创建资源VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
:有关不一定是错误但很可能是应用程序中 bug 的行为的消息VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
:有关无效且可能导致崩溃的行为的消息
过滤方式:可以通过if (messageSeverity >= Flag)来检查消息是否等于或与某个严重性级别相比更差
第二个参数消息类型可以具有以下值:
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
:发生了一些与规格或性能无关的事件VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
:发生了违反规范或表明可能错误的事情VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT
:Vulkan 的潜在非最佳用途
第三个参数回调数据:
pMessage
:调试消息作为以 null 结尾的字符串pObjects
:与消息相关的 Vulkan 对象句柄数组objectCount
:数组中的对象数
最后一个参数包含在设置回调期间指定的指针,并允许您将自己的数据传递给它
函数体为所执行的内容
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(//
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData) {
std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;
return VK_FALSE;
}
设置调式INFO信息:
指定了希望调用回调的消息严重性,省略了INFO冗长的一般调试信息
此处启用了所有类型消息类型,
pfnUserCallback指向回调函数的指针,其中PFN是vk内置的指针前缀
void 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;
}
创建调试信息工具
VkDebugUtilsMessengerCreateInfoEXT结构体应该传递给函数vkCreateDebugUtilsMessengerEXT()来创建调试信息工具,
但是vkCreateDebugUtilsMessengerEXT(extension)是一个扩展函数,它不会自动加载。我们必须 使用 vkGetInstanceProcAddr
自行查找扩展函数地址,它返回PFN_vkVoidFunction函数指针类型
我们将这一查找部分,写到单独的函数中,如果找到了地址,就调用它
我们应该指定VkInstance,因为调试信使特定于我们的 VulkanInstance
其中VkAllocationCallbacks是可选的分配器回调
我们需要在createInstance后CreateDebugUtilsMessengerEXT
VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) {
auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
if (func != nullptr) {
return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
}
else {
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
同样这里 PFN_vkCreateDebugUtilsMessengerEXT也是一个指针,如下是vulkan中的定义,它是一个函数指针名,别名为VkResult,返回类型是VKAPI_PTR,右边的()中是形参数列表
typedef VkResult (VKAPI_PTR *PFN_vkCreateDebugUtilsMessengerEXT)(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pMessenger);
销毁调试信息工具
与创建相反,我们也需要 销毁调试信息工具
与vkCreateDebugUtilsMessengerEXT对应vkDestroyDebugUtilsMessengerEXT
我们需要在vkDestroyInstance前DestroyDebugUtilsMessengerEXT
void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
if (func != nullptr) {
func(instance, debugMessenger, pAllocator);
}
}
调试VkInstance
很明显上述create和destory都是在instance的前后的,没有覆盖它,因此就没办法调试它
但其实有一种方法:将指针传递到 VkInstanceCreateInfo的扩展字段中createInfo.pNext,我们应把VkDebugUtilsMessengerCreateInfoEXT放在语句块外,以保证vkCreateInstance
调用之前不会销毁。
测试
比如尝试不调用DestroyDebugUtilsMessengerEXT并运行关闭程序后
如果使用的项目模板,没有调试输出,可以通过右击priject->properties->linker->system->subsystem->console设置为控制台输出