原文链接:Vulkan-tutorial
Validation layers 验证层
为什么要用Validation layers ?
Vulkan的设计理念是:使驱动(driver)的负担最小化。一个明显的表现就是它有限的错误检测,像设置错误的枚举值或者将必须的函数参数传递为空指针(NULL)这类简单的操作都没有明确的处理,Vulkan只是简单的Crash或者产生一些未定义的行为(undefined behavior)。因为Vulkan要求你必须知道你正在做什么。但这并不意味着我们就无从下手了,相反,Vulakn引入了一个非常优雅的方法:Validation layers.Validation layers是一个可选的组件(optional components) ,它连接Vulkan的方法调用。Validation layers 的基本功能有以下几个:
- 检查参数是否被勿用。
- 追踪对象的创建与销毁,检测是否与有资源泄露。
- 追踪线程(thread)调用的源头,检测线程是否安全。
- 将方法调用的参数打印到标准输出。
- Tracing Vulkan calls for profiling and replaying。
vulkan.h
中有定义:
#define VK_EXT_DEBUG_REPORT_EXTENSION_NAME "VK_EXT_debug_report" //这是验证所需支持的扩展(extension)。
GLFW提供了一个获取extension的简便方法:
const char** glfwGetRequiredInstanceExtensions(uint32_t* count);
获取extension列表,并添加 VK_EXT_DEBUG_REPORT_EXTENSION_NAME
(我觉得extension 列表里应该已有此扩展):
std::vector<const char*> getRequiredExtensions() {
std::vector<const char*> extensions;
unsigned int glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
for (unsigned int i = 0; i < glfwExtensionCount; i++) {
extensions.push_back(glfwExtensions[i]);
}
if (enableValidationLayers) {
extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
}
return extensions;
}
然后修改createInfo
,为它添加extensions:
auto extensions = getRequiredExtensions();
createInfo.enabledExtensionCount = extensions.size();
createInfo.ppEnabledExtensionNames=extensions.data();
LunarG validation layers 只有在安装了LunarG SDK的PC上才能使用,这里我们不需要所有的layers,LunarG SDK 只需要VK_LAYER_LUNARG_standard_validation
即可。我们首先获取所有的layers , 然后检查是否支持VK_LAYER_LUNARG_standard_validation
。
VkResult vkEnumerateInstanceLayerProperties(uint32_t* pPropertyCount, VkLayerProperties* pProperties);
vkEnumerateInstanceLayerProperties()
可以获取所有支持的VkLayerProperties
,当pProperties
为nullptr
时,pPropertyCount将被赋予所被支持的VkLayerProperties
数量;pProperties
不为nullptr
时,pPropertyCount
将被赋予所被支持的VkLayerProperties
数量的同时,pProperties
指向pPropertyCount
个VkLayerProperties
的数组。
//我们需要的layer
const std::vector<const char*> validationLayers = {
"VK_LAYER_LUNARG_standard_validation"
};
//检查是否支持
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;
}
然后修改createInfo
, 为它添加layers:
bool enableValidationLayers = checkValidationLayerSupport();
if (enableValidationLayers) {
createInfo.enabledLayerCount = validationLayers.size();
createInfo.ppEnabledLayerNames = validationLayers.data();
}else{
createInfo.enabledLayerCount = 0;
}
//创建具有DEBUG的Instance
kResult result = vkCreateInstance(&inst_info,nullptr,&instance);
做了这些以后我们就可验证错误了吗?别急,我们还需要定义一个回调函数(callback),将debug信息带回到我们的程序中,然后显示出来。现在,让我们看看这个回调函数长什么样。定义一个具有PFN_vkDebugReportCallbackEXT
原型(prototype)的静态(static)成员函数debugCallbak:
//注意这里只是windows下的实现。
#ifndef WIN32
#define __stdcall
#endif
static VkBool32 __stdcall debugCallback( VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t obj, size_t location, int32_t code, const char* layerPrefix, const char* msg, void* userData) {
std::cerr << "validation layer: " << msg << std::endl;
return VK_FALSE;
}
第一个参数VkDebugReportFlagsEXT可以取值如下(可以组合使用):
VK_DEBUG_REPORT_INFORMATION_BIT_EXT VK_DEBUG_REPORT_WARNING_BIT_EXT VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT VK_DEBUG_REPORT_ERROR_BIT_EXT VK_DEBUG_REPORT_DEBUG_BIT_EXT
Msg 参数就是我们需要的debug信息,userData是我们要传给debugCallback的数据。
VkDebugReportCallbackEXT callback; //声明
和创建VkInstance一样,需要填充一个VkDebugReportCallbackCreateInfoEXT结构体:
typedef struct VkDebugReportCallbackCreateInfoEXT {
VkStructureType sType;
const void* pNext;
VkDebugReportFlagsEXT flags;
PFN_vkDebugReportCallbackEXT pfnCallback;
void* pUserData;
} VkDebugReportCallbackCreateInfoEXT;
填充:
VkDebugReportCallbackCreateInfoEXT createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
createInfo.pfnCallback = debugCallback;//pUserData 也可以指定数据,类似userData
接下来会和VkInstance
类似,调用 vkCreateDebugReportCallbackEXT
(…)函数来创建我们的callback吗?并不尽然,因为vkCreateDebugReportCallbackEXT
(…)是扩展函数,它并不会随vulkan 核心API一同加载,所以需要使用:
PFN_vkVoidFunction vkGetInstanceProcAddr(VkInstance instance, const char* pName);
函数来加载vkCreateDebugReportCallbackEXT(...)
的地址:
VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) {
auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
if (func != nullptr) {
return func(instance, pCreateInfo, pAllocator, pCallback);
} else {
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
创建 callback:
if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) {
throw std::runtime_error("failed to set up debug callback!");
}
好了,如果你运行程序,会出现两个窗体,一个是用来显示我们的三角形的,现在它是空的,另一个是命令提示窗,它显示debug信息。现在关闭窗口,会发现:
糟糕,有错误!怎么可能? 它提示callbacks没有被清除。 VkDebugReportCallbackEXT 对象需要被vkDestroyDebugReportCallbackEXT清除,现在用VDeleter包裹callbacks,并添加vkDestroyDebugReportCallbackEXT函数:
VDeleter<VkDebugReportCallbackEXT> callback{instance, DestroyDebugReportCallbackEXT};
void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) {
auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");
if (func != nullptr) {
func(instance, callback, pAllocator);
}
}
在Vulkan SDK 的目录下有个Config文件夹,这个文件下有个vk_layer_settings.txt文件,是你对Validation layers进行配置的文件,你可以对这个问文件进行配置。但在后续的教程里,我都认为你采用的是默认配置。
源码 :
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <iostream>
#include <stdexcept>
#include <functional>
#include <vector>
#include <cstring>
const int WIDTH = 800;
const int HEIGHT = 600;
const std::vector<const char*> validationLayers = {
"VK_LAYER_LUNARG_standard_validation"
};
#ifdef NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif
VkResult CreateDebugReportCallbackEXT(VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback) {
auto func = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT");
if (func != nullptr) {
return func(instance, pCreateInfo, pAllocator, pCallback);
} else {
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
void DestroyDebugReportCallbackEXT(VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator) {
auto func = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT");
if (func != nullptr) {
func(instance, callback, pAllocator);
}
}
template <typename T>
class VDeleter {
public:
VDeleter() : VDeleter([](T, VkAllocationCallbacks*) {}) {}
VDeleter(std::function<void(T, VkAllocationCallbacks*)> deletef) {
this->deleter = [=](T obj) {
deletef(obj, nullptr);
};
}
VDeleter(const VDeleter<VkInstance>& instance, std::function<void(VkInstance, T, VkAllocationCallbacks*)> deletef) {
this->deleter = [&instance, deletef](T obj) {
deletef(instance, obj, nullptr);
};
}
VDeleter(const VDeleter<VkDevice>& device, std::function<void(VkDevice, T, VkAllocationCallbacks*)> deletef) {
this->deleter = [&device, deletef](T obj) {
deletef(device, obj, nullptr);
};
}
~VDeleter() {
cleanup();
}
T* operator &() {
cleanup();
return &object;
}
operator T() const {
return object;
}
private:
T object {VK_NULL_HANDLE};
std::function<void(T)> deleter;
void cleanup() {
if (object != VK_NULL_HANDLE) {
deleter(object);
}
object = VK_NULL_HANDLE;
}
};
class HelloTriangleApplication {
public:
void run() {
initWindow();
initVulkan();
mainLoop();
}
private:
GLFWwindow* window;
VDeleter<VkInstance> instance {vkDestroyInstance};
VDeleter<VkDebugReportCallbackEXT> callback {instance, DestroyDebugReportCallbackEXT};
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();
setupDebugCallback();
}
void mainLoop() {
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
}
}
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 = extensions.size();
createInfo.ppEnabledExtensionNames = extensions.data();
if (enableValidationLayers) {
createInfo.enabledLayerCount = validationLayers.size();
createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
createInfo.enabledLayerCount = 0;
}
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
throw std::runtime_error("failed to create instance!");
}
}
void setupDebugCallback() {
if (!enableValidationLayers) return;
VkDebugReportCallbackCreateInfoEXT createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
createInfo.pfnCallback = debugCallback;
if (CreateDebugReportCallbackEXT(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) {
throw std::runtime_error("failed to set up debug callback!");
}
}
std::vector<const char*> getRequiredExtensions() {
std::vector<const char*> extensions;
unsigned int glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
for (unsigned int i = 0; i < glfwExtensionCount; i++) {
extensions.push_back(glfwExtensions[i]);
}
if (enableValidationLayers) {
extensions.push_back(VK_EXT_DEBUG_REPORT_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(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t obj, size_t location, int32_t code, const char* layerPrefix, const char* msg, void* userData) {
std::cerr << "validation layer: " << msg << std::endl;
return VK_FALSE;
}
};
int main() {
HelloTriangleApplication app;
try {
app.run();
} catch (const std::runtime_error& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}