前言:本教程为笔者依据教程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 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;
}
回调函数工作原理:
- 异步调用:验证层在检测到问题时异步调用此函数
- 丰富信息:pCallbackData包含详细的错误描述、对象信息等
- 控制流程:返回值决定是否中断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;
}
欢迎学习和交流,欢迎指正!🌹🌹