[Vulkan 学习之路] 03 - 你的守护天使:校验层 (Validation Layers)

欢迎回到 Vulkan 学习之旅!

在上一篇中,我们成功创建了一个 Vulkan 实例。如果你当时试着故意传错一些参数(比如把扩展数量填成 0),你会发现程序可能直接崩溃,或者什么都不显示,但控制台里没有任何报错信息

这就是 Vulkan 的"冷酷"之处。为了追求极致的性能,Vulkan 的驱动程序在默认情况下几乎不做任何错误检查

  • 你传了空指针?驱动:好吧,我读。-> Crash

  • 你用了不支持的纹理格式?驱动:行,我画。-> 黑屏

为了不让我们在 Debug 时发疯,Vulkan 引入了 Validation Layers (校验层)。它像是一个中间人,拦截你的所有 API 调用,检查参数是否合规。如果发现问题,它会像严厉的老师一样详细地告诉你:"嘿,你在第几行犯了错,根据规范你应该这样做..."。

今天,我们就把这位"守护天使"请进我们的代码里。

策略:Debug 开启,Release 关闭

校验层虽然好,但它会消耗额外的 CPU 资源。因此,我们的策略是:开发时开启,发布时关闭

HelloVulkanApp 类定义的上方,我们添加一些全局常量来控制这个逻辑:

cpp 复制代码
// 如果定义了 NDEBUG (通常在 Release 模式下),则关闭校验层
#ifdef NDEBUG
    const bool enableValidationLayers = false;
#else
    const bool enableValidationLayers = true;
#endif

// 我们需要请求的标准校验层名称
const std::vector<const char*> validationLayers = {
    "VK_LAYER_KHRONOS_validation"
};

小知识: VK_LAYER_KHRONOS_validation 是一个"全家桶"校验层,包含了参数检查、线程安全检查、内存泄漏检测等所有标准功能。Vulkan SDK 已经自带了它。

检查校验层是否可用

虽然我们装了 SDK,但为了代码的健壮性,我们得先问问 Vulkan:"这个校验层真的存在吗?"

在类中添加一个辅助函数 checkValidationLayerSupport

cpp 复制代码
bool checkValidationLayerSupport() {
    uint32_t layerCount;
    vkEnumerateInstanceLayerProperties(&layerCount, nullptr);

    std::vector<VkLayerProperties> availableLayers(layerCount);
    vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());

    // 遍历我们需要的所有 layer,看看能不能在 availableLayers 里找到
    for (const char* layerName : validationLayers) {
        bool layerFound = false;

        for (const auto& layerProperties : availableLayers) {
            if (strcmp(layerName, layerProperties.layerName) == 0) {
                layerFound = true;
                break;
            }
        }

        if (!layerFound) {
            return false;
        }
    }

    return true;
}

升级 createInstance

现在我们需要修改上一节[Vulkan 学习之路] 02 - 万物起源:创建 Vulkan 实例 (Instance)-CSDN博客写的 createInstance 函数。

我们需要做两件事:

  1. 如果启用了校验层,将其添加到 VkInstanceCreateInfo 中。

  2. 为了接收校验层发出的报错信息,我们需要启用一个额外的扩展:VK_EXT_debug_utils

为了代码整洁,我们把获取扩展的逻辑抽离出来:

cpp 复制代码
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) {
        // 如果开启调试,必须加上 Debug Utils 扩展
        extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
    }

    return extensions;
}

然后更新 createInstance

cpp 复制代码
void createInstance() {
    // 1. 先检查支持
    if (enableValidationLayers && !checkValidationLayerSupport()) {
        throw std::runtime_error("validation layers requested, but not available!");
    }

    // ... appInfo 设置保持不变 ...

    VkInstanceCreateInfo createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    createInfo.pApplicationInfo = &appInfo;

    // 2. 使用新函数获取扩展
    auto extensions = getRequiredExtensions();
    createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
    createInfo.ppEnabledExtensionNames = extensions.data();

    // 3. 设置校验层
    if (enableValidationLayers) {
        createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
        createInfo.ppEnabledLayerNames = validationLayers.data();
    } else {
        createInfo.enabledLayerCount = 0;
    }

    if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
        throw std::runtime_error("failed to create instance!");
    }
}

建立信使 (Debug Messenger)

现在校验层已经开启了,但如果它发现了错误,它该怎么告诉我们呢?我们需要注册一个回调函数(Callback)。

定义回调函数

这是一个静态函数,格式由 Vulkan 规定。我们简单地把错误信息打印到控制台:

cpp 复制代码
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
    VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
    VkDebugUtilsMessageTypeFlagsEXT messageType,
    const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
    void* pUserData) {

    // messageSeverity 可以用来判断是 警告(Warning) 还是 错误(Error)
    std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;

    // 返回 VK_FALSE 表示不要中断 Vulkan 调用(除非你想让程序崩溃)
    return VK_FALSE;
}

加载扩展函数 (这里的坑比较深)

vkCreateDebugUtilsMessengerEXT 是一个扩展函数,不是 Vulkan 核心加载器的一部分。这意味着我们不能直接调用它,需要动态获取它的函数地址。

我们在类定义之前添加这两个代理函数:

cpp 复制代码
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;
    }
}

void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
    auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
    if (func != nullptr) {
        func(instance, debugMessenger, pAllocator);
    }
}

配置 Messenger

在类中添加 setupDebugMessenger 函数。我们在这里告诉 Vulkan:我们关心哪些级别的日志(比如警告和错误),以及出了问题去找哪个回调函数。

cpp 复制代码
// 成员变量
VkDebugUtilsMessengerEXT debugMessenger;

void setupDebugMessenger() {
    if (!enableValidationLayers) return;

    VkDebugUtilsMessengerCreateInfoEXT createInfo{};
    createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
    
    // 过滤日志级别:我们要看 详细信息(Verbose)、警告(Warning) 和 错误(Error)
    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;

    if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
        throw std::runtime_error("failed to set up debug messenger!");
    }
}

组装与清理

最后一步,把这一切串起来。

修改 initVulkan

cpp 复制代码
void initVulkan() {
    createInstance();
    setupDebugMessenger(); // <--- 必须在 createInstance 之后
}

修改 cleanup

cpp 复制代码
void cleanup() {
    if (enableValidationLayers) {
        DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
    }

    vkDestroyInstance(instance, nullptr);
    glfwDestroyWindow(window);
    glfwTerminate();
}

完整代码:

cpp 复制代码
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <iostream>
#include <stdexcept>
#include <vector>
#include <cstring>
#include <cstdlib>

const uint32_t WIDTH = 800;
const uint32_t HEIGHT = 600;

const std::vector<const char*> validationLayers = {
    "VK_LAYER_KHRONOS_validation"
};

#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif

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;
    }
}

void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
    auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
    if (func != nullptr) {
        func(instance, debugMessenger, pAllocator);
    }
}

class HelloTriangleApplication {
public:
    void run() {
        initWindow();
        initVulkan();
        mainLoop();
        cleanup();
    }

private:
    GLFWwindow* window;

    VkInstance instance;
    VkDebugUtilsMessengerEXT debugMessenger;

    void initWindow() {
        glfwInit();

        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

        window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
    }

    void initVulkan() {
        createInstance();
        setupDebugMessenger();
    }

    void mainLoop() {
        while (!glfwWindowShouldClose(window)) {
            glfwPollEvents();
        }
    }

    void cleanup() {
        if (enableValidationLayers) {
            DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
        }

        vkDestroyInstance(instance, nullptr);

        glfwDestroyWindow(window);

        glfwTerminate();
    }

    void createInstance() {
        if (enableValidationLayers && !checkValidationLayerSupport()) {
            throw std::runtime_error("validation layers requested, but not available!");
        }

        VkApplicationInfo appInfo{};
        appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
        appInfo.pApplicationName = "Hello Triangle";
        appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.pEngineName = "No Engine";
        appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
        appInfo.apiVersion = VK_API_VERSION_1_0;

        VkInstanceCreateInfo createInfo{};
        createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        createInfo.pApplicationInfo = &appInfo;

        auto extensions = getRequiredExtensions();
        createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
        createInfo.ppEnabledExtensionNames = extensions.data();

        VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
        if (enableValidationLayers) {
            createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
            createInfo.ppEnabledLayerNames = validationLayers.data();

            populateDebugMessengerCreateInfo(debugCreateInfo);
            createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo;
        } else {
            createInfo.enabledLayerCount = 0;

            createInfo.pNext = nullptr;
        }

        if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
            throw std::runtime_error("failed to create instance!");
        }
    }

    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;
    }

    void setupDebugMessenger() {
        if (!enableValidationLayers) return;

        VkDebugUtilsMessengerCreateInfoEXT createInfo;
        populateDebugMessengerCreateInfo(createInfo);

        if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
            throw std::runtime_error("failed to set up debug messenger!");
        }
    }

    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;
    }

    bool checkValidationLayerSupport() {
        uint32_t layerCount;
        vkEnumerateInstanceLayerProperties(&layerCount, nullptr);

        std::vector<VkLayerProperties> availableLayers(layerCount);
        vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());

        for (const char* layerName : validationLayers) {
            bool layerFound = false;

            for (const auto& layerProperties : availableLayers) {
                if (strcmp(layerName, layerProperties.layerName) == 0) {
                    layerFound = true;
                    break;
                }
            }

            if (!layerFound) {
                return false;
            }
        }

        return true;
    }

    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;
    }
};

int main() {
    HelloTriangleApplication app;

    try {
        app.run();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

见证奇迹:故意犯个错

现在,编译并运行你的程序。如果是 Debug 模式,一切应该正常运行(黑窗口)。

怎么确认它生效了?

我们在 cleanup 里的 vkDestroyInstance 之前,故意加一行错误代码:

cpp 复制代码
vkDestroyInstance(nullptr, nullptr);

再次运行,查看 Visual Studio 的"输出"窗口(Output)或者控制台窗口。你应该会看到类似这样的红色警告:

validation layer: Validation Error: [ VUID-vkDestroyInstance-instance-parameter ] Object 0: VK_NULL_HANDLE, type = VK_OBJECT_TYPE_INSTANCE; | MessageID = 0x8a62378b | Invalid VkInstance Object 0x0.

这行报错简直是天籁之音!它告诉了我们错误的位置、类型以及原因。

从今天起,你不再是一个人在战斗。Validation Layers 将是你学习 Vulkan 路上最忠实的伙伴。

下一步

环境安全了,是时候干点硬核的了。下一篇,我们将开始挑选物理设备 (Physical Devices),看看你的显卡到底支持哪些 Vulkan 特性!

详见:Validation layers - Vulkan Tutorial

相关推荐
冰暮流星2 小时前
c语言如何实现字符串复制替换
c语言·c++·算法
txinyu的博客2 小时前
C++内存池的内存对齐问题
c++
无限进步_2 小时前
【C语言&数据结构】二叉树链式结构完全指南:从基础到进阶
c语言·开发语言·数据结构·c++·git·算法·visual studio
脏脏a2 小时前
STL stack/queue 底层模拟实现与典型算法场景实践
开发语言·c++·stl_stack·stl_queue
deng-c-f2 小时前
Linux C/C++ 学习日记(63):Redis(四):事务
linux·c语言·c++
DYS_房东的猫2 小时前
《 C++ 零基础入门教程》第8章:多线程与并发编程 —— 让程序“同时做多件事”
开发语言·c++·算法
NimoXie2 小时前
Windows CUDA + cuDNN + TensorFlow + PyTorch 识别 GPU 的简单整合
pytorch·windows·tensorflow
REDcker2 小时前
AIGCJson 库介绍与使用指南
c++·json·aigc·c
love530love3 小时前
突破 ComfyUI 环境枷锁:RTX 3090 强行开启 comfy-kitchen 官方全后端加速库实战
人工智能·windows·python·cuda·comfyui·triton·comfy-kitchen