VulkanTutorial(3·Validation layer)

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设置为控制台输出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值