Vulkan入门教程 | 第三部分:使用验证层

 前言:本教程为笔者依据教程https://docs.vulkan.net.cn/spec/latest/index.html#_about进行Vulkan学习并结合自己的理解整理的笔记,供大家学习和参考。

(注意:代码仅为片段,非完整程序)

学习前提:

支持Vulkan的显卡以及驱动程序(NVIDIA,AMD,Intel)
具备C++编程经验(熟悉RAII,初始化列表)
支持C++的编译器(Visual Studio 2017+,GCC 7+)

往期回顾:
Vulkan入门教程 | 第一部分:Vulkan简介

Vulkan入门教程 | 第二部分:创建实例

一、概述

Vulkan验证层是一套可插拔的调试和验证组件,它们采用分层架构设计,位于应用程序和Vulkan驱动程序之间。验证层的核心设计理念是通过拦截器模式工作,每当应用程序调用Vulkan API时,验证层会先接收这些调用,执行相应的验证逻辑,然后将调用传递给下一层或驱动程序。这种设计使得验证层能够在不修改应用程序代码的情况下,对所有Vulkan API调用进行监控和验证。

现代Vulkan验证层系统经历了重要的演进过程。早期版本使用多个独立的验证层分别处理不同类型的验证任务,如参数验证、对象生命周期跟踪、核心验证等。但从Vulkan 1.1开始,这些独立的验证层被合并成一个统一的验证层VK_LAYER_KHRONOS_validation,这个统一验证层包含了所有核心验证功能,简化了配置过程并提高了验证效率。

验证层的工作价值主要体现在开发阶段的错误检测和性能分析。它能够检测API参数的正确性、调用顺序的合理性、内存管理的安全性,以及多线程环境下的同步问题。同时,验证层还提供性能相关的建议,帮助开发者识别潜在的性能瓶颈和优化机会。架构设计如下:
应用程序
    ↓
验证层1 (Core Validation)
    ↓  
验证层2 (Parameter Validation)
    ↓
验证层3 (Object Tracker)
    ↓
Vulkan驱动程序
    ↓
GPU硬件

二、完整流程

1、添加头文件

#include <vector>
#include <cstring>

(1)#include <vector>std::vector 是C++标准模板库(STL)中的动态数组容器,具有以下特点:

  • 动态大小:可以在运行时动态增长和缩小
  • 连续内存:元素在内存中连续存储,访问效率高
  • 自动管理:自动处理内存分配和释放
  • 类型安全:模板化,支持任意类型

常用方法:

  • size():获取元素个数
  • data():获取指向数据的指针
  • push_back():在末尾添加元素
  • empty():检查是否为空

Vulkan需要处理动态数量的扩展和层,编译时无法确定具体数量,为方便后续遍历和传递给Vulkan API,需要与Vulkan C API接口兼容(通过data()和size())

(2)#include <cstring>cstring 头文件提供了C风格字符串操作函数,这些函数来自C标准库的<string.h>,Vulkan API是C语言接口,使用char*类型,必须使用C字符串函数进行比较。

主要功能:

  • 字符串比较:strcmp, strncmp等
  • 字符串复制:strcpy, strncpy等
  • 字符串长度:strlen
  • 内存操作:memset, memcpy, memcmp等

2、验证层声明与条件编译

// 定义需要使用的验证层
const std::vector<const char*> validationLayers = {
    "VK_LAYER_KHRONOS_validation"  // 统一验证层,包含所有核心验证功能
};

// 使用条件编译控制验证层启用状态
#ifdef NDEBUG
const bool enableValidationLayers = false;  // Release版本禁用
#else
const bool enableValidationLayers = true;   // Debug版本启用
#endif

validationLayers向量定义了需要启用的验证层列表。VK_LAYER_KHRONOS_validation是Khronos组织提供的统一验证层,它整合了之前分散的多个验证层功能,包括参数验证、对象生命周期跟踪、内存使用检查、线程安全验证等核心功能。

条件编译机制的工作原理基于C++预处理器的宏展开功能。NDEBUG宏是C/C++标准定义的调试控制宏,通常由编译器在Release配置中自动定义。当编译器处理这段代码时,预处理器会根据NDEBUG宏的定义状态选择相应的代码分支。如果NDEBUG已定义(Release模式),enableValidationLayers被设置为false;如果NDEBUG未定义(Debug模式),enableValidationLayers被设置为true。

这种设计的关键优势在于编译时优化。由于enableValidationLayers是编译时常量,编译器在优化阶段会进行死代码消除,完全移除未使用的代码分支。在Release版本中,所有与验证层相关的代码都会被编译器删除,不会对最终程序的大小和性能产生任何影响。

3、验证层支持检查

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;  // 所有验证层都可用
}

这个函数实现了Vulkan验证层的动态检测机制。函数采用了Vulkan API典型的两阶段查询模式,这是Vulkan设计中的一个重要模式,用于处理动态大小的数据集合。

第一阶段查询通过调用vkEnumerateInstanceLayerProperties并传入nullptr作为数据指针来获取可用验证层的数量。Vulkan驱动程序会将可用层的数量写入layerCount变量,但不返回实际的层信息。这种设计允许应用程序根据实际需要分配适当大小的内存空间。

第二阶段查询分配足够的存储空间后,再次调用相同的函数来获取实际的验证层信息。每个VkLayerProperties结构体包含了一个验证层的详细信息,包括层名称、版本信息、描述等。这些信息由Vulkan运行时或驱动程序提供,反映了当前系统环境中实际可用的验证层。

验证过程采用嵌套循环进行字符串匹配。外层循环遍历应用程序需要的每个验证层,内层循环在系统可用的验证层中查找匹配项。字符串比较使用strcmp函数进行精确匹配,确保层名称完全一致。这种严格的匹配策略防止了因名称相似但功能不同的层被错误启用。

4、扩展函数动态加载

// 创建调试信使的扩展函数
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);
    }
}

这段代码展示了Vulkan扩展函数的动态加载机制,这是Vulkan API设计中的一个核心特性。由于调试工具扩展不属于Vulkan核心API,相关函数不会静态链接到应用程序中,而是需要在运行时动态获取。

vkGetInstanceProcAddr是Vulkan提供的函数指针获取接口,它的工作原理基于符号查找机制。当调用这个函数时,Vulkan运行时会在已加载的扩展模块中查找指定名称的函数。查找过程涉及遍历实例相关的所有扩展,检查每个扩展是否导出了指定名称的函数。

函数指针的类型转换使用了Vulkan预定义的函数指针类型PFN_vkCreateDebugUtilsMessengerEXT。这个类型定义包含了函数的完整签名信息,包括参数类型、返回值类型和调用约定。类型转换确保了获取的函数指针具有正确的签名,编译器可以进行类型检查,防止参数不匹配的错误。

如果扩展函数成功获取,代码会直接调用该函数并传递所有参数。如果获取失败(函数指针为nullptr),则返回VK_ERROR_EXTENSION_NOT_PRESENT错误码,表示所需的扩展功能不可用。这种错误处理机制允许应用程序优雅地处理扩展不可用的情况。

5、调试信息使配置

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

这个函数实现了调试消息传递器的精细化配置,它定义了验证层如何与应用程序进行通信。配置过程涉及多个关键参数的设置,每个参数都控制着调试信息传递的不同方面。

结构体初始化首先将整个结构清零,然后设置sType字段。sType是Vulkan结构体的标准字段,用于运行时类型识别。Vulkan API使用这个字段来确定结构体的具体类型,支持类型安全的操作和向后兼容性。

消息严重程度配置使用位标志组合来指定要接收的消息级别。

  • VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT包括最详细的调试信息,通常用于深度调试;
  • VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT包括可能导致问题但不会立即失败的情况;
  • VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT包括严重错误,通常会导致不正确的行为或崩溃。

位标志的组合使用位或操作实现。每个标志占用一个独立的位位置,通过位或操作可以同时设置多个标志。这种设计允许精确控制消息过滤,开发者可以根据调试需要选择接收特定级别的消息。

消息类型配置同样使用位标志指定要接收的消息类别。

  • VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT表示一般性的调试信息,不特定于验证或性能;
  • VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT表示API使用验证相关的消息,如参数错误、状态不一致等;
  • VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT表示性能相关的建议和警告。

配置详解: 

严重程度标志

用途

建议使用场景

VERBOSE诊断信息深度调试
INFO信息性消息一般开发
WARNING警告推荐启用
ERROR错误必须启用

消息类型标志

内容

价值

GENERAL一般性事件里哦阿姐执行流程
VALIDATION规范违反核心价值
PERFORMANCE性能建议优化指导

6、调试回调函数实现

static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
    VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,    // 消息严重程度
    VkDebugUtilsMessageTypeFlagsEXT messageType,               // 消息类型
    const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, // 回调数据
    void* pUserData) {                                         // 用户数据
    
    // 输出验证层消息
    std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;
    
    // 返回VK_FALSE表示不中断程序执行
    // 返回VK_TRUE会导致调用被中止并返回VK_ERROR_VALIDATION_FAILED_EXT
    return VK_FALSE;
}

回调函数工作原理

  1. 异步调用:验证层在检测到问题时异步调用此函数
  2. 丰富信息:pCallbackData包含详细的错误描述、对象信息等
  3. 控制流程:返回值决定是否中断API调用

调试回调函数是验证层消息处理的核心组件,它定义了应用程序如何响应验证层发现的问题。函数签名使用了Vulkan特定的调用约定和属性修饰符,确保与Vulkan运行时的正确交互。

VKAPI_ATTR和VKAPI_CALL是Vulkan定义的宏,用于指定正确的调用约定。在不同的平台上,这些宏会展开为不同的编译器特定属性。例如,在Windows上可能展开为__stdcall,在其他平台上可能是默认的调用约定。这种抽象确保了跨平台的兼容性。

函数参数提供了丰富的上下文信息。messageSeverity参数指示消息的严重程度,应用程序可以根据这个信息决定如何处理消息。messageType参数指示消息的类别,帮助分类和过滤不同类型的调试信息。

pCallbackData参数是最重要的信息载体,它指向一个VkDebugUtilsMessengerCallbackDataEXT结构体,包含了详细的调试信息。这个结构体不仅包含消息文本,还可能包含相关对象的句柄、源码位置信息、建议的解决方案等。pMessage字段包含了人类可读的消息文本,描述了检测到的问题。

pUserData参数允许传递用户自定义的上下文数据。在创建调试消息传递器时,可以指定一个用户数据指针,这个指针会在每次回调时传递给回调函数。这种机制允许回调函数访问应用程序的特定上下文,如日志系统、配置信息等。

函数的返回值控制Vulkan的后续行为。返回VK_FALSE表示Vulkan应该继续正常执行,这是大多数情况下的期望行为。返回VK_TRUE会导致当前的API调用被中断,并返回VK_ERROR_VALIDATION_FAILED_EXT错误。这种机制允许应用程序在检测到严重问题时主动中断执行。

7、扩展需求获取

std::vector<const char*> getRequiredExtensions() {
    uint32_t glfwExtensionCount = 0;
    const char** glfwExtensions;
    
    // 获取GLFW需要的Vulkan扩展
    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扩展需求的动态管理,它根据应用程序的实际需要组装扩展列表。扩展管理是Vulkan应用程序初始化的关键步骤,因为不同的功能需要不同的扩展支持,扩展管理逻辑:

  • 基础扩展:GLFW窗口系统所需扩展
  • 调试扩展:验证层需要的调试工具扩展
  • 条件添加:只在启用验证层时添加调试扩展

条件扩展添加展示了扩展管理的灵活性。只有在启用验证层时,才会添加VK_EXT_DEBUG_UTILS_EXTENSION_NAME扩展。这种条件添加确保了扩展列表与实际功能需求的一致性,避免了不必要的扩展启用。

VK_EXT_DEBUG_UTILS_EXTENSION_NAME扩展提供了现代化的调试消息传递功能。它定义了创建调试消息传递器、配置消息过滤、处理调试标签等功能的API。这个扩展是调试工具链的基础,没有它就无法使用验证层的消息传递功能。

8、实例创建核心流程

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

    // 创建Vulkan实例
    if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
        throw std::runtime_error("failed to create instance!");
    }
}

验证层支持检查是一个关键的防护措施。如果应用程序尝试启用不可用的验证层,vkCreateInstance调用会失败,导致程序无法正常运行。通过提前检查,程序可以提供更友好的错误信息,帮助开发者快速识别和解决问题。

验证层配置采用条件分支处理。当启用验证层时,函数设置enabledLayerCount和ppEnabledLayerNames字段,指定要启用的验证层。同时,它还配置了一个特殊的调试消息传递器,用于接收实例创建过程中的调试消息。

pNext字段的使用展示了Vulkan的可扩展设计。通过将debugCreateInfo结构体链接到创建信息中,可以在实例创建的同时配置调试消息传递器。这种设计允许在对象创建期间就开始接收调试消息,包括创建过程本身的验证信息。

类型转换为VkDebugUtilsMessengerCreateInfoEXT指针确保了正确的类型匹配。虽然pNext字段的类型是void指针,但Vulkan运行时会根据结构体的sType字段进行类型识别和处理。

 9、调试信息使设置

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!");
    }
}

调试信使设置函数负责创建运行时调试消息传递器,它是验证层消息传递系统的核心组件。这个函数在Vulkan实例创建之后调用,建立持久的调试消息传递通道。

函数首先进行条件检查,只有在启用验证层时才执行实际的设置操作。这种早期退出策略避免了不必要的函数调用和资源分配,同时确保代码的一致性。

调试消息传递器的配置复用了前面定义的populateDebugMessengerCreateInfo函数。这种复用设计确保了实例创建期间和运行时的调试配置保持一致,避免了配置不匹配导致的问题。

CreateDebugUtilsMessengerEXT函数调用使用了前面定义的动态加载包装函数。这种间接调用机制隐藏了扩展函数动态加载的复杂性,提供了与核心API一致的调用体验。

成功创建调试消息传递器后,debugMessenger变量会被设置为有效的句柄。这个句柄代表了一个活跃的调试消息传递器,验证层会使用它来发送调试消息。失败时函数抛出异常,表示调试系统初始化失败。

10、资源清理

void cleanup() {
    // 清理调试信使
    if (enableValidationLayers) {
        DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
    }

    // 销毁Vulkan实例
    vkDestroyInstance(instance, nullptr);

    // 清理GLFW资源
    glfwDestroyWindow(window);
    glfwTerminate();
}

调试消息传递器的清理采用条件执行,只有在验证层启用时才进行。这种条件清理与创建时的条件逻辑保持一致,确保不会尝试销毁不存在的对象。

DestroyDebugUtilsMessengerEXT函数调用使用了动态加载的包装函数。调试信使理操作必须在Vulkan实例销毁之前完成,因为调试消息传递器依赖于实例的存在。如果顺序颠倒,可能导致访问已释放内存的问题。

总结

Vulkan验证层是开发高质量Vulkan应用程序不可或缺的工具。验证层通过以下机制实现强大的调试功能:所有API调用都经过验证层检查、调试扩展函数在运行时动态获取、验证问题通过回调函数异步报告、开发和生产环境的灵活切换。通过正确配置和使用验证层,开发者可以:

  • 及早发现API使用错误
  • 优化应用程序性能
  • 提高代码质量和稳定性
  • 减少调试时间

验证层是Vulkan开发的必备工具,能显著提高开发效率,生产环境必须禁用验证层以避免性能损,建议启用所有消息类型和严重程度以获得完整的验证覆盖。对验证层报告的每个问题都应该认真对待并及时修复。

该部分的完整代码如下:
(1)无注释版:

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

(2)详细注释版:

// 引入Vulkan头文件,GLFW_INCLUDE_VULKAN宏确保GLFW包含Vulkan定义
#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;

// 验证层列表 - Vulkan的调试和验证功能
// VK_LAYER_KHRONOS_validation是Khronos官方提供的标准验证层
const std::vector<const char*> validationLayers = {
    "VK_LAYER_KHRONOS_validation"
};

// 根据编译模式决定是否启用验证层
// 在Debug模式下启用验证层,Release模式下禁用以提高性能
#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif

/**
 * 创建调试工具消息器的扩展函数
 * 由于这是扩展功能,需要手动获取函数指针
 * @param instance Vulkan实例
 * @param pCreateInfo 创建信息结构体
 * @param pAllocator 内存分配器(可为空)
 * @param pDebugMessenger 输出的调试消息器句柄
 * @return Vulkan结果代码
 */
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;
    }
}

/**
 * 销毁调试工具消息器的扩展函数
 * @param instance Vulkan实例
 * @param debugMessenger 要销毁的调试消息器
 * @param pAllocator 内存分配器(可为空)
 */
void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
    // 获取扩展函数的地址
    auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
    if (func != nullptr) {
        func(instance, debugMessenger, pAllocator);
    }
}

/**
 * Hello Triangle应用程序主类
 * 封装了整个Vulkan应用程序的生命周期管理
 */
class HelloTriangleApplication {
public:
    /**
     * 应用程序主运行函数
     * 按顺序执行初始化、主循环和清理工作
     */
    void run() {
        initWindow();    // 初始化GLFW窗口
        initVulkan();    // 初始化Vulkan
        mainLoop();      // 运行主循环
        cleanup();       // 清理资源
    }

private:
    // GLFW窗口句柄
    GLFWwindow* window;

    // Vulkan核心对象
    VkInstance instance;                    // Vulkan实例 - 应用程序与Vulkan库的连接
    VkDebugUtilsMessengerEXT debugMessenger; // 调试消息器 - 用于接收验证层消息

    /**
     * 初始化GLFW窗口
     * 设置窗口属性并创建窗口
     */
    void initWindow() {
        // 初始化GLFW库
        glfwInit();

        // 告诉GLFW不要创建OpenGL上下文,因为我们使用Vulkan
        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
        // 暂时禁用窗口大小调整,简化教程
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

        // 创建窗口:宽度、高度、标题、监视器(全屏用)、共享上下文
        window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
    }

    /**
     * 初始化Vulkan
     * 创建Vulkan实例和设置调试功能
     */
    void initVulkan() {
        createInstance();        // 创建Vulkan实例
        setupDebugMessenger();   // 设置调试消息器
    }

    /**
     * 应用程序主循环
     * 处理窗口事件,直到用户关闭窗口
     */
    void mainLoop() {
        // 循环直到用户请求关闭窗口
        while (!glfwWindowShouldClose(window)) {
            // 处理窗口事件(键盘、鼠标等)
            glfwPollEvents();
        }
    }

    /**
     * 清理函数
     * 按相反的创建顺序销毁所有资源
     */
    void cleanup() {
        // 如果启用了验证层,销毁调试消息器
        if (enableValidationLayers) {
            DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
        }

        // 销毁Vulkan实例
        vkDestroyInstance(instance, nullptr);

        // 销毁GLFW窗口
        glfwDestroyWindow(window);

        // 终止GLFW
        glfwTerminate();
    }

    /**
     * 创建Vulkan实例
     * Vulkan实例是应用程序与Vulkan库之间的连接
     */
    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;               // 使用的Vulkan API版本

        // 实例创建信息结构体
        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;
        }

        // 创建Vulkan实例
        if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
            throw std::runtime_error("failed to create instance!");
        }
    }

    /**
     * 填充调试消息器创建信息
     * @param createInfo 要填充的创建信息结构体
     */
    void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
        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;
        
        // 指定要接收的消息类型
        // GENERAL: 一般信息, VALIDATION: 验证消息, PERFORMANCE: 性能警告
        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!");
        }
    }

    /**
     * 获取所需的扩展列表
     * 包括GLFW所需的扩展和调试扩展(如果启用)
     * @return 扩展名称列表
     */
    std::vector<const char*> getRequiredExtensions() {
        // 获取GLFW需要的扩展
        uint32_t glfwExtensionCount = 0;
        const char** glfwExtensions;
        glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

        // 将GLFW扩展添加到列表中
        std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);

        // 如果启用验证层,添加调试工具扩展
        if (enableValidationLayers) {
            extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
        }

        return extensions;
    }

    /**
     * 检查验证层支持
     * 验证系统是否支持所请求的验证层
     * @return 如果所有请求的验证层都可用则返回true
     */
    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;
                }
            }

            // 如果找不到某个层,返回false
            if (!layerFound) {
                return false;
            }
        }

        // 所有层都找到了
        return true;
    }

    /**
     * 调试回调函数
     * 当验证层检测到问题时会调用此函数
     * @param messageSeverity 消息严重性
     * @param messageType 消息类型
     * @param pCallbackData 回调数据,包含消息详细信息
     * @param pUserData 用户数据指针(未使用)
     * @return VK_FALSE表示不中断触发此回调的Vulkan调用
     */
    static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
        VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
        VkDebugUtilsMessageTypeFlagsEXT messageType,
        const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
        void* pUserData) {
        
        // 将验证层消息输出到标准错误流
        std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;

        // 返回VK_FALSE表示不应中断触发此回调的Vulkan调用
        // 只有在测试验证层本身时才应该返回VK_TRUE
        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;
}


 欢迎学习和交流,欢迎指正!🌹🌹 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值