文章目录

  • 参考资料
  • 简述
  • 一. 创建 vulkan 实例
  • 1.1 创建 Window 实例
  • 1.2 创建 Vulkan 实例
  • 二. 验证层(Validation layers)
  • 2.1 什么是 Validation layers?
  • 2.2 Validation layers示例
  • 2.3 启用验证层
  • 2.4 消息回调
  • 2.4.1 调试回调函数 debugCallback
  • 2.4.2 注册调试回调
  • 三. 代码
  • 3.1 Makefile
  • 3.2 main.cpp


参考资料

  1. https://github.com/KhronosGroup/Vulkan-Docs
  2. https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#vkCreateInstance
  3. https://github.com/SaschaWillems/Vulkan/blob/master/base/vulkanexamplebase.h

简述

本文主要是实现Vulkan Tutorial.pdf文档中的Base Code, Instance和Validation Layers部分。

一. 创建 vulkan 实例

1.1 创建 Window 实例

先创建个 window 窗口

void initWindow() {
        // 第一步一定是先初始化GLFW库.
        glfwInit();

        // 因为GLFW最初是为创建OpenGL上下文而设计的,
        // 所以我们需要告诉它不要通过后续调用创建OpenGL上下文:GLFW_NO_API
        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);

        // 因为处理大小可变的窗口比较复杂,暂时先让窗口不可变
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

        // 创建窗口
        // GLFWwindow *glfwCreateWindow(int width, int height, const char *title, GLFWmonitor *monitor, GLFWwindow *share)
        window = glfwCreateWindow(WIDTH, HEIGHT, WINDOW_TITLE, nullptr, nullptr);

    }

1.2 创建 Vulkan 实例

  1. 创建 VkApplicationInfo 结构体变量,参数可选
  2. 创建 VkInstanceCreateInfo 结构体变量,必须指明
  1. 使用 GLFW (glfwGetRequiredInstanceExtensions) 创建 glfwExtensions, 以便为GLFW窗口创建Vulkan surface
  1. 调用 vkCreateInstance, 创建 vulkan 实例
void createInstance() {
        // 创建一个实例首先必须填写一个包含有关我们应用程序的信息的结构: VkApplicationInfo
        // 这些数据在技术上是可选的,但它可以为驱动程序提供一些有用的信息,以便针对我们的特定应用进行优化
        VkApplicationInfo appInfo = {};

        // Vulkan中的许多结构要求在sType成员中明确指定类型。
        // 这也是具有pNext成员的许多结构中的一个,该成员可以在将来指向扩展信息。
        appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;

        appInfo.pApplicationName = "Hello Triangle";
        appInfo.applicationVersion = VK_MAKE_VERSION(1, 1, 0);
        appInfo.pEngineName = "No Engine";
        appInfo.engineVersion = VK_MAKE_VERSION(1, 1, 0);
        appInfo.apiVersion = VK_API_VERSION_1_1;

        // Vulkan中的很多信息都是通过结构而不是函数参数传递的,
        // 我们必须再填充一个结构体 VkInstanceCreateInfo 来为创建实例提供足够的信息。
        // VkInstanceCreateInfo结构是必须指明的,它告诉Vulkan驱动程序我们想要使用哪些全局扩展和验证层。
        VkInstanceCreateInfo createInfo = {};
        createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        createInfo.pNext = nullptr;
        createInfo.pApplicationInfo = &appInfo;

        // 前两个参数很简单。
        // Vulkan是一个与平台无关的API,这意味着需要一个与窗口系统接口的扩展。
        // GLFW有一个方便的内置函数,它返回它需要做的扩展,我们可以传递给结构体:VkInstanceCreateInfo
        uint32_t glfwExtensionCount = 0;
        const char** glfwExtensions;
        // 返回GLFW所需的Vulkan实例扩展。
        // 此函数返回GLFW所需的Vulkan实例扩展名的数组,以便为GLFW窗口创建Vulkan surface。
        // 如果成功,列表将始终包含`VK_KHR_surface`,因此如果您不需要任何其他扩展,则可以将此列表直接传递给`VkInstanceCreateInfo`结构。
        // 如果机器上没有Vulkan,则此函数返回“NULL”并生成 GLFW_API_UNAVAILABLE错误。
        glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

        createInfo.enabledExtensionCount = glfwExtensionCount;

        // createInfo.ppEnabledLayerNames = glfwExtensions;
        // 笔者曾在这里栽了个跟头,写错了。如上写,编译不出问题,但运行时会报 Segmentation fault (core dumped)
        // 打印堆栈,看了半天,才发现的,引以为戒,不过这两变量名字很像。
        createInfo.ppEnabledExtensionNames = glfwExtensions;

        // 结构体的最后两个成员确定要启用的全局验证层。
        createInfo.enabledLayerCount = 0;

        // VkResult vkCreateInstance(const VkInstanceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkInstance *pInstance)
        VkResult res = vkCreateInstance(&createInfo, nullptr, &instance);
        if (res != VK_SUCCESS) {
            throw std::runtime_error("failed to create instance!");
        }
    }

二. 验证层(Validation layers)

2.1 什么是 Validation layers?

Vulkan API围绕最小驱动程序开销的想法而设计,该目标的一个表现形式是默认情况下API中的错误检查非常有限。
即使是将枚举设置为不正确的值或将空指针传递给所需参数这样简单的错误通常也不会被显式处理,只会导致崩溃或未定义的行为。
这一点,笔者已经深刻体会到了,(╯﹏╰)
因为Vulkan要求你对你所做的一切都非常明确,所以很容易犯很多小错误,例如使用新的GPU功能而忘记在逻辑设备创建时请求它。

但是,这并不意味着无法将这些检查添加到API中。
Vulkan为这种称为验证层的系统引入了一个优雅的系统: Validation layers.
验证层是可选组件,它挂接到Vulkan函数调用以应用其他操作。

验证层中的常见操作是:

  1. 根据规范检查参数值以检测误用
  2. 跟踪对象的创建和销毁以查找资源泄漏
  3. 通过跟踪调用的线程来检查线程安全性
  4. 将每个调用及其参数记录到标准输出方便调试
  5. 跟踪Vulkan要求进行性能分析和重放

2.2 Validation layers示例

VkResult vkCreateInstance(
    const VkInstanceCreateInfo* pCreateInfo,
    const VkAllocationCallbacks* pAllocator,
    VkInstance* instance) {

    if (pCreateInfo == nullptr || instance == nullptr) {
        log("Null pointer passed to required parameter!");
        return VK_ERROR_INITIALIZATION_FAILED;
    }

    return real_vkCreateInstance(pCreateInfo, pAllocator, instance);
}

比如上面的vkCreateInstance中的if语句。官方文档上如此说:

这些验证层可以自由堆叠,以包含您感兴趣的所有调试功能。
您可以简单地为调试版本启用验证层,并为发布版本完全禁用它们,这将为您提供两全其美的优势!
Vulkan没有内置任何验证层,但LunarG Vulkan SDK提供了一组很好的层来检查常见错误。
它们也是完全开源的,因此您可以检查它们检查和贡献的错误类型。
使用验证层是避免应用程序因意外依赖未定义行为而破坏不同驱动程序的最佳方法。
验证层只有在已安装到系统上时才能使用。
例如,LunarG验证层仅适用于安装了Vulkan SDK的PC。
Vulkan中以前有两种不同类型的验证层:实例和设备特定。
我们的想法是,实例层只会检查与全局Vulkan对象(如实例)相关的调用,而设备特定层只会检查与特定GPU相关的调用。
现在已弃用特定于设备的层,这意味着实例验证层适用于所有Vulkan调用。
规范文档仍建议您在设备级别启用验证层以及兼容性,这是某些实现所需的。

2.3 启用验证层

如何启用Vulkan SDK提供的标准诊断层? 就像扩展一样,需要通过指定其名称来启用验证层。
所有有用的标准验证都捆绑在SDK中包含的层中,称为VK_LAYER_KHRONOS_validation。
让我们首先向程序添加两个配置变量,以指定要启用的层以及是否启用它们。 我已经选择将该值作为程序是否在调试模式下编译。
NDEBUG宏是C ++标准的一部分,意味着“不调试”。

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

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

2.4 消息回调

仅启用这些层并没有多大帮助,因为它们目前无法将调试消息中继回我们的程序。
要接收这些消息,我们必须设置一个带回调的调试信使,这需要VK_EXT_debug_utils扩展。
我们将首先创建一个getRequiredExtensions函数,根据是否启用验证层返回所需的扩展名列表:

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

GLFW指定的扩展始终是必需的,但有条件地添加了调试信使扩展(VK_EXT_debug_utils)。
请注意,在这里使用了VK_EXT_DEBUG_UTILS_EXTENSION_NAME宏,它等于文字字符串“VK_EXT_debug_utils”。 使用此宏可以避免拼写错误。
我们现在可以在createInstance中使用此函数:

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

2.4.1 调试回调函数 debugCallback

现在让我们看一下调试回调函数的样子。
使用PFN_vkDebugUtilsMessengerCallbackEXT原型添加一个名为debugCallback的新静态成员函数。
VKAPI_ATTR和VKAPI_CALL确保该函数具有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;

    return VK_FALSE;
}
  1. 第一个参数: VkDebugUtilsMessageSeverityFlagBitsEXT 指明了消息的严重程度
    •VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:诊断消息
    •VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:信息性消息,如创建资源
    •VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:有关行为的消息不一定是错误,但很可能是应用程序中的错误
    •VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:有关无效行为的消息,可能导致崩溃
    可以根据这个参数过滤所需的信息。
  2. 第二个参数: VkDebugUtilsMessageTypeFlagsEXT 指明了消息的类型
    •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
  3. 第三个参数: VkDebugUtilsMessengerCallbackDataEXT 这个结构体包含了消息更多的细节内容
    •pMessage:调试消息为以空字符结尾的字符串
    •pObjects:与消息相关的Vulkan对象句柄数组
    •object Count:数组中的对象数
  4. 第四个参数: pUserData 包含在回调设置期间指定的指针,并允许您将自己的数据传递给它。

回调返回一个布尔值,指示是否应该中止触发验证层消息的Vulkan调用。
如果回调返回true,则调用将因VK_ERROR_VALIDATION_FAILED_EXT错误而中止。
这通常仅用于测试验证层本身,因此应始终返回VK_FALSE。

2.4.2 注册调试回调

在Vulkan中, 调试回调也是通过需要显式创建和销毁的句柄来管理的。 这样的回调是debug message的一部分,可以根据需要设置尽可能多的回调。

// 在成员变量中添加:VkDebugUtilsMessengerEXT
VkDebugUtilsMessengerEXT debugMessenger;

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

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

    VkDebugUtilsMessengerCreateInfoEXT 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;
    createInfo.pUserData = nullptr; // 可选
}

messageSeverity字段允许指定要为其调用回调的所有类型的严重性。
在这里指定了除VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT之外的所有类型,以接收有关可能问题的通知,同时省略详细的一般调试信息。
类似地,messageType字段可以过滤通知回调的消息类型。 在这里启用了所有类型。
最后,pfnUserCallback字段指定回调函数的指针。可以选择将指针传递给pUserData字段,该字段将通过pUserData参数传递给回调函数。
例如,可以使用它来传递指向HelloTriangleApplication类的指针。

应该将结构体 VkDebugUtilsMessengerEXT 传递给vkCreateDebugUtilsMessengerEXT函数以创建VkDebugUtilsMessengerEXT对象。
然而因为这个function是一个扩展函数,它不会自动加载。
必须使用vkGetInstanceProcAddr查找其地址。 创建代理函数,然后在处理它,如下:

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

现在我们可以在 setupDebugMessenger 中调用此函数:

void setupDebugMessenger() {
        if (!enableValidationLayers) return;
        ......
        // 实例化DebugUtilsMessengerEXT
        if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr,
            &debugMessenger) != VK_SUCCESS) {

            throw std::runtime_error("failed to set up debug messenger!");
        }
    }

最后注意,既然有 vkCreateXXX, 就需要显示调用 vkDestroyXXX 哦!

void DestroyDebugUtilsMessengerEXT(VkInstance instance,
    VkDebugUtilsMessengerEXT debugMessenger, const
    VkAllocationCallbacks* pAllocator) {

    auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)
    vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
    if (func != nullptr) {
        func(instance, debugMessenger, pAllocator);
    }
}

如果你忘了调用DestroyDebugUtilsMessengerEXT去销毁debugMessenger,在关闭窗口的时候就会打印如下信息:

vulkan接口调用 Android framework vulkan hook vulkan开发实战详解_API

三. 代码

3.1 Makefile

VULKAN_SDK_PATH = /home/jh/Program/vulkan/1.1.160.0/x86_64

CFLAGS = -std=c++17 -I$(VULKAN_SDK_PATH)/include
LDFLAGS = -L$(VULKAN_SDK_PATH)/lib -lvulkan `pkg-config --static --libs glfw3`
LDFLAGS += -ldl

HelloTriangleApplication: main.cpp
    g++ $(CFLAGS) -o HelloTriangleApplication main.cpp $(LDFLAGS)

.PHONY: test clean

test: HelloTriangleApplication
    LD_LIBRARY_PATH=$(VULKAN_SDK_PATH)/lib
        VK_LAYER_PATH=$(VULKAN_SDK_PATH)/etc/explicit_layer.d ./HelloTriangleApplication

clean:rm -f HelloTriangleApplication

3.2 main.cpp

/**
 * #include <vulkan/vulkan.h> // vulkan 头文件
 * 使用下面两行替换 vulkan头文件
 *   #define GLFW_INCLUDE_VULKAN
 *   #include <GLFW/glfw3.h>
 *
 *  GLFW 会自动加载 vulkan 头文件的。
 * GLFW是一个开源,多平台的库,用于桌面上的OpenGL,OpenGL ES和Vulkan开发。
 * 它提供了一个简单的API,用于创建窗口,上下文和曲面,接收输入和事件。
 * GLFW是用C语言编写的,并且使用X Window系统(例如Linux和FreeBSD)对Windows,macOS和许多类Unix系统提供原生支持。
 */
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <iostream>
#include <stdexcept> // 包含用于报告错误的头文件
#include <functional> // 用于资源管理部分中的lambda函数
#include <cstdlib> // cstdlib: EXIT_FAILURE, EXIT_SUCCESS
#include <vector>
#include <string.h>

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

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

    const int WIDTH = 800;
    const int HEIGHT = 600;
    const char * WINDOW_TITLE = "Vulkan";
private:
    void initWindow() {
        // 第一步一定是先初始化GLFW库.
        glfwInit();

        // 因为GLFW最初是为创建OpenGL上下文而设计的,
        // 所以我们需要告诉它不要通过后续调用创建OpenGL上下文:GLFW_NO_API
        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);

        // 因为处理大小可变的窗口比较复杂,暂时先让窗口不可变
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

        // 创建窗口
        // GLFWwindow *glfwCreateWindow(int width, int height, const char *title, GLFWmonitor *monitor, GLFWwindow *share)
        window = glfwCreateWindow(WIDTH, HEIGHT, WINDOW_TITLE, nullptr, nullptr);

    }

    void initVulkan() {
        checkAvailableExtensions();
        createInstance();
        // 创建DEBUG消息回调
        setupDebugMessenger();
    }

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

        VkDebugUtilsMessengerCreateInfoEXT 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;
        createInfo.pUserData = nullptr; // 可选

        // 实例化DebugUtilsMessengerEXT
        if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr,
            &debugMessenger) != VK_SUCCESS) {

            throw std::runtime_error("failed to set up debug messenger!");
        }
    }

    void checkAvailableExtensions() {
        uint32_t extensionCount = 0;
        vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
        std::vector<VkExtensionProperties> extensions(extensionCount);
        vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());

        std::cout << "available extensions:" << std::endl;
        for (const auto& extension : extensions) {
            std::cout << "\t" << extension.extensionName << std::endl;
        }
    }

    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;
                    return true;
                }
            }
        }
        return false;
    }

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

        // 创建一个实例首先必须填写一个包含有关我们应用程序的信息的结构: VkApplicationInfo
        // 这些数据在技术上是可选的,但它可以为驱动程序提供一些有用的信息,以便针对我们的特定应用进行优化
        VkApplicationInfo appInfo = {};

        // Vulkan中的许多结构要求在sType成员中明确指定类型。
        // 这也是具有pNext成员的许多结构中的一个,该成员可以在将来指向扩展信息。
        appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;

        appInfo.pApplicationName = "Hello Triangle";
        appInfo.applicationVersion = VK_MAKE_VERSION(1, 1, 0);
        appInfo.pEngineName = "No Engine";
        appInfo.engineVersion = VK_MAKE_VERSION(1, 1, 0);
        appInfo.apiVersion = VK_API_VERSION_1_1;

        // Vulkan中的很多信息都是通过结构而不是函数参数传递的,
        // 我们必须再填充一个结构体 VkInstanceCreateInfo 来为创建实例提供足够的信息。
        // VkInstanceCreateInfo结构是必须指明的,它告诉Vulkan驱动程序我们想要使用哪些全局扩展和验证层。
        VkInstanceCreateInfo createInfo = {};
        createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        createInfo.pNext = nullptr;
        createInfo.pApplicationInfo = &appInfo;

        /*
        // 前两个参数很简单。
        // Vulkan是一个与平台无关的API,这意味着需要一个与窗口系统接口的扩展。
        // GLFW有一个方便的内置函数,它返回它需要做的扩展,我们可以传递给结构体:VkInstanceCreateInfo
        uint32_t glfwExtensionCount = 0;
        const char** glfwExtensions;
   
        // 返回GLFW所需的Vulkan实例扩展。
        // 此函数返回GLFW所需的Vulkan实例扩展名的数组,以便为GLFW窗口创建Vulkan surface。
        // 如果成功,列表将始终包含`VK_KHR_surface`,因此如果您不需要任何其他扩展,则可以将此列表直接传递给`VkInstanceCreateInfo`结构。
        // 如果机器上没有Vulkan,则此函数返回“NULL”并生成 GLFW_API_UNAVAILABLE错误。
        glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

        // 笔者曾在这里栽了个跟头,写错了。如上写,编译不出问题,但运行时会报 Segmentation fault (core dumped)
        // 打印堆栈,看了半天,才发现的,引以为戒,不过这两变量名字很像。
        createInfo.ppEnabledLayerNames = glfwExtensions;
        createInfo.ppEnabledExtensionNames = glfwExtensions;
        createInfo.enabledExtensionCount = glfwExtensionCount;
        */
       // 返回GLFW所需的Vulkan实例扩展, 支持消息回调
        auto extensions = getRequiredExtensions();
        createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
        createInfo.ppEnabledExtensionNames = extensions.data();

        // 结构体的最后两个成员确定要启用的全局验证层。
        if (enableValidationLayers) {
            createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
            createInfo.ppEnabledLayerNames = validationLayers.data();
        } else {
            createInfo.enabledLayerCount = 0;
        }

        // VkResult vkCreateInstance(const VkInstanceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkInstance *pInstance)
        VkResult res = vkCreateInstance(&createInfo, nullptr, &instance);
        if (res != VK_SUCCESS) {
            throw std::runtime_error("failed to create instance!");
        }
    }

    void mainLoop() {
        // 添加一个事件循环, 使应用程序保持运行直到发生错误或窗口关闭
        while (!glfwWindowShouldClose(window)) {
            glfwPollEvents();
        }
    }

    // 在 vulkan 中推荐在创建的资源不需要后主动释放
    void cleanup() {
        // 释放debugMessenger(VkDebugUtilsMessengerEXT, 用于打印调试信息)
        if (enableValidationLayers) {
            DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
        }
        // 在 vulkan 中资源一般都是 vkCreateXXX 创建,由 vkDestroyXXX 或 vkFreeXXX 释放.
        vkDestroyInstance(instance, nullptr);

        // 此函数会破坏指定的窗口及其上下文。 在调用此函数时,不会为该窗口调用其他回调。
        // 如果指定窗口的上下文在主线程上是最新的,则在销毁之前将其分离。
        glfwDestroyWindow(window);

        // 此功能会释放所有剩余的窗口和光标并释放任何其他已分配的资源。
        // 调用此函数后,必须再次成功调用@ref glfwInit,然后才能使用大多数GLFW函数。
        glfwTerminate();
    }

    // 用于消息回调Message Callback
    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;
    }

    // 打印debug信息
    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;
    }

    // 创建 VkDebugUtilsMessengerEXT 对象debugMessenger
    VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const
        VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const
        VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT*
        pDebugMessenger) {

        // 将结构体 VkDebugUtilsMessengerEXT 传递给vkCreateDebugUtilsMessengerEXT函数以创建VkDebugUtilsMessengerEXT对象。
        // 然而因为这个function是一个扩展函数,它不会自动加载。
        // 必须使用vkGetInstanceProcAddr查找其地址。
        auto func = (PFN_vkCreateDebugUtilsMessengerEXT)
        vkGetInstanceProcAddr(instance,"vkCreateDebugUtilsMessengerEXT");
        if (func != nullptr) {
            return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
        } else {
            return VK_ERROR_EXTENSION_NOT_PRESENT;
        }
    }

    // 销毁VkDebugUtilsMessengerEXT
    void DestroyDebugUtilsMessengerEXT(VkInstance instance,
        VkDebugUtilsMessengerEXT debugMessenger, const
        VkAllocationCallbacks* pAllocator) {

        auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)
        vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
        if (func != nullptr) {
            func(instance, debugMessenger, pAllocator);
        }
    }

    // Window实例
    GLFWwindow* window;
    // Vulkan实例
    VkInstance instance;
    // DEBUG消息回调
    VkDebugUtilsMessengerEXT debugMessenger;
    // 验证层
    const std::vector<const char*> validationLayers = {
        "VK_LAYER_KHRONOS_validation"
    };
};

int main() {
    HelloTriangleApplication app;

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

    return EXIT_SUCCESS;
}