简述
本文主要是实现Vulkan Tutorial.pdf文档中的Base Code, Instance和Validation Layers部分。
参考资料
- https://github.com/KhronosGroup/Vulkan-Docs
- https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#vkCreateInstance
- https://github.com/SaschaWillems/Vulkan/blob/master/base/vulkanexamplebase.h
一. 创建 vulkan 实例
1.1 创建 Window 实例
先创建个 window 窗口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void initWindow() { glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
window = glfwCreateWindow(WIDTH, HEIGHT, WINDOW_TITLE, nullptr, nullptr);
}
|
1.2 创建 Vulkan 实例
- 创建 VkApplicationInfo 结构体变量,参数可选
- 创建 VkInstanceCreateInfo 结构体变量,必须指明
- 使用 GLFW (glfwGetRequiredInstanceExtensions) 创建 glfwExtensions, 以便为GLFW窗口创建Vulkan surface
- 调用 vkCreateInstance, 创建 vulkan 实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| void createInstance() { VkApplicationInfo appInfo = {};
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;
VkInstanceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pNext = nullptr; createInfo.pApplicationInfo = &appInfo;
uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;
createInfo.enabledLayerCount = 0;
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函数调用以应用其他操作。
验证层中的常见操作是:
- 根据规范检查参数值以检测误用
- 跟踪对象的创建和销毁以查找资源泄漏
- 通过跟踪调用的线程来检查线程安全性
- 将每个调用及其参数记录到标准输出方便调试
- 跟踪Vulkan要求进行性能分析和重放
2.2 Validation layers示例
1 2 3 4 5 6 7 8 9 10 11 12
| 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语句。官方文档上如此说:
1 2 3 4 5 6 7 8 9 10 11
| 这些验证层可以自由堆叠,以包含您感兴趣的所有调试功能。 您可以简单地为调试版本启用验证层,并为发布版本完全禁用它们,这将为您提供两全其美的优势! Vulkan没有内置任何验证层,但LunarG Vulkan SDK提供了一组很好的层来检查常见错误。 它们也是完全开源的,因此您可以检查它们检查和贡献的错误类型。 使用验证层是避免应用程序因意外依赖未定义行为而破坏不同驱动程序的最佳方法。 验证层只有在已安装到系统上时才能使用。 例如,LunarG验证层仅适用于安装了Vulkan SDK的PC。 Vulkan中以前有两种不同类型的验证层:实例和设备特定。 我们的想法是,实例层只会检查与全局Vulkan对象(如实例)相关的调用,而设备特定层只会检查与特定GPU相关的调用。 现在已弃用特定于设备的层,这意味着实例验证层适用于所有Vulkan调用。 规范文档仍建议您在设备级别启用验证层以及兼容性,这是某些实现所需的。
|
2.3 启用验证层
如何启用Vulkan SDK提供的标准诊断层? 就像扩展一样,需要通过指定其名称来启用验证层。
所有有用的标准验证都捆绑在SDK中包含的层中,称为VK_LAYER_KHRONOS_validation。
让我们首先向程序添加两个配置变量,以指定要启用的层以及是否启用它们。 我已经选择将该值作为程序是否在调试模式下编译。
NDEBUG宏是C ++标准的一部分,意味着“不调试”。
1 2 3 4 5 6 7 8 9
| 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函数,根据是否启用验证层返回所需的扩展名列表:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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中使用此函数:
1 2 3
| 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调用它的正确签名。
1 2 3 4 5 6 7 8 9 10
| 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; }
|
- 第一个参数: 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:有关无效行为的消息,可能导致崩溃
可以根据这个参数过滤所需的信息。
- 第二个参数: 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
- 第三个参数: VkDebugUtilsMessengerCallbackDataEXT 这个结构体包含了消息更多的细节内容
•pMessage:调试消息为以空字符结尾的字符串
•pObjects:与消息相关的Vulkan对象句柄数组
•object Count:数组中的对象数
- 第四个参数: pUserData 包含在回调设置期间指定的指针,并允许您将自己的数据传递给它。
回调返回一个布尔值,指示是否应该中止触发验证层消息的Vulkan调用。
如果回调返回true,则调用将因VK_ERROR_VALIDATION_FAILED_EXT错误而中止。
这通常仅用于测试验证层本身,因此应始终返回VK_FALSE。
2.4.2 注册调试回调
在Vulkan中, 调试回调也是通过需要显式创建和销毁的句柄来管理的。 这样的回调是debug message的一部分,可以根据需要设置尽可能多的回调。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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查找其地址。 创建代理函数,然后在处理它,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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 中调用此函数:
1 2 3 4 5 6 7 8 9 10
| void setupDebugMessenger() { if (!enableValidationLayers) return; ...... if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
throw std::runtime_error("failed to set up debug messenger!"); } }
|
最后注意,既然有 vkCreateXXX, 就需要显示调用 vkDestroyXXX 哦!
1 2 3 4 5 6 7 8 9 10
| 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,在关闭窗口的时候就会打印如下信息:
三. 代码
3.1 Makefile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
|
#define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h>
#include <iostream> #include <stdexcept> #include <functional> #include <cstdlib> #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() { glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
window = glfwCreateWindow(WIDTH, HEIGHT, WINDOW_TITLE, nullptr, nullptr);
}
void initVulkan() { checkAvailableExtensions(); 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;
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) { for (const auto& layerProperties : availableLayers) { if (strcmp(layerName, layerProperties.layerName) == 0) { return true; } } } return false; }
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, 1, 0); appInfo.pEngineName = "No Engine"; appInfo.engineVersion = VK_MAKE_VERSION(1, 1, 0); appInfo.apiVersion = VK_API_VERSION_1_1;
VkInstanceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pNext = nullptr; createInfo.pApplicationInfo = &appInfo;
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 res = vkCreateInstance(&createInfo, nullptr, &instance); if (res != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } }
void mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } }
void cleanup() { if (enableValidationLayers) { DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); } vkDestroyInstance(instance, nullptr);
glfwDestroyWindow(window);
glfwTerminate(); }
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; }
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; }
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); } }
GLFWwindow* window; VkInstance instance; 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; }
|