[译]Vulkan教程(06)验证层
What are validation layers? 什么是验证层?
The Vulkan API is designed around the idea of minimal driver overhead and one of the manifestations of that goal is that there is very limited error checking in the API by default. Even mistakes as simple as setting enumerations to incorrect values or passing null pointers to required parameters are generally not explicitly handled and will simply result in crashes or undefined behavior. Because Vulkan requires you to be very explicit about everything you're doing, it's easy to make many small mistakes like using a new GPU feature and forgetting to request it at logical device creation time.
Vulkan API的核心设计思想是最小化driver开销,其表现之一就是,API本身的错误检查很少。设置错误的枚举值,向必选参数传入空指针,即使是这样简单的错误,也不会被显式地处理,而只是简单地崩溃,或做出未定义的行为。因为Vulkan要求你对你做的所有事都清楚明白,很容易犯各种小错误,例如,想使用新GPU特性,却忘记在逻辑设备创建时请求它。
However, that doesn't mean that these checks can't be added to the API. Vulkan introduces an elegant system for this known as validation layers. Validation layers are optional components that hook into Vulkan function calls to apply additional operations. Common operations in validation layers are:
- Checking the values of parameters against the specification to detect misuse
- Tracking creation and destruction of objects to find resource leaks
- Checking thread safety by tracking the threads that calls originate from
- Logging every call and its parameters to the standard output
- Tracing Vulkan calls for profiling and replaying
但是,这不意味着这些检查无法加入到API。Vulkan引入了一个优雅的系统,即验证层。验证层是可选组件,他挂钩进Vulkan函数调用,以实施额外操作。验证层的常见操作有:
- 根据说明书检查参数值,以检测误用
- 跟踪对象的创建和销毁过程,以查找资源泄漏
- 通过追踪线程调用源头来检查线程安全性
- 将所有调用及其参数保存到标准输出
- 追踪Vulkan调用,用于剖析和重演
Here's an example of what the implementation of a function in a diagnostics validation layer could look like:
下面展示了诊断验证层的函数可能的样子:
1 VkResult vkCreateInstance( 2 const VkInstanceCreateInfo* pCreateInfo, 3 const VkAllocationCallbacks* pAllocator, 4 VkInstance* instance) { 5 6 if (pCreateInfo == nullptr || instance == nullptr) { 7 log("Null pointer passed to required parameter!"); 8 return VK_ERROR_INITIALIZATION_FAILED; 9 } 10 11 return real_vkCreateInstance(pCreateInfo, pAllocator, instance); 12 }
These validation layers can be freely stacked to include all the debugging functionality that you're interested in. You can simply enable validation layers for debug builds and completely disable them for release builds, which gives you the best of both worlds!
这些验证层可以被栈入进各种你感兴趣的调试功能。你可以简单地在debug阶段启用验证层,在release阶段彻底禁用它们。这对开发和应用世界都是最好的!
Vulkan does not come with any validation layers built-in, but the LunarG Vulkan SDK provides a nice set of layers that check for common errors. They're also completely open source, so you can check which kind of mistakes they check for and contribute. Using the validation layers is the best way to avoid your application breaking on different drivers by accidentally relying on undefined behavior.
Vulkan没有任何内建的验证层,但是LunarG的Vulkan SDK提供了一个不错的验证层集合,它们能够检查常见错误。它们也是完全开源的,所以你可以看看它们检查哪种错误,并贡献自己的才智。你的app在不同的driver上可能因为意外的未定义行为而中止,使用这些验证层是避免这一问题的最好方法。
Validation layers can only be used if they have been installed onto the system. For example, the LunarG validation layers are only available on PCs with the Vulkan SDK installed.
验证层只能在被安装到系统上后才能使用。例如,只有安装了Vulkan SDK,LunarG的验证层才能在你的PC上用。
There were formerly two different types of validation layers in Vulkan: instance and device specific. The idea was that instance layers would only check calls related to global Vulkan objects like instances, and device specific layers would only check calls related to a specific GPU. Device specific layers have now been deprecated, which means that instance validation layers apply to all Vulkan calls. The specification document still recommends that you enable validation layers at device level as well for compatibility, which is required by some implementations. We'll simply specify the same layers as the instance at logical device level, which we'll see later on.
以前Vulkan中有两种不同类型的验证层:针对instance的和针对device的。Instance层只检查与全局Vulkan对象(例如instance)相关的调用;device层只检查与特定CPU相关的调用。Device层现在已经被废弃了,这意味着instance验证层对所有Vulkan调用都有用。为了兼容某些实现,说明书里仍旧推荐你启用device水平的验证层。我们将简单地标明一个层同时属于instance水平和逻辑device水平,详见后续。
Using validation layers 使用验证层
In this section we'll see how to enable the standard diagnostics layers provided by the Vulkan SDK. Just like extensions, validation layers need to be enabled by specifying their name. All of the useful standard validation is bundled into a layer included in the SDK that is known as VK_LAYER_KHRONOS_validation
.
这一节我们看看如何启用Vulkan SDK提供的标准诊断层。像扩展一样,验证层需要通过标明它们的名字来启用。所有有用的标准验证都被绑进了这个被称为VK_LAYER_KHRONOS_validation
的SDK。
Let's first add two configuration variables to the program to specify the layers to enable and whether to enable them or not. I've chosen to base that value on whether the program is being compiled in debug mode or not. The NDEBUG
macro is part of the C++ standard and means "not debug".
我们首先添加2个配置变量,标明要启用的层,以及是否启用它们。我已经选择让这个值基于程序的编译模式是不是debug。宏NDEBUG
是C++标准的一部分,意思是“不是debug”。
1 const int WIDTH = 800; 2 const int HEIGHT = 600; 3 4 const std::vector<const char*> validationLayers = { 5 "VK_LAYER_KHRONOS_validation" 6 }; 7 8 #ifdef NDEBUG 9 const bool enableValidationLayers = false; 10 #else 11 const bool enableValidationLayers = true; 12 #endif
We'll add a new function checkValidationLayerSupport
that checks if all of the requested layers are available. First list all of the available layers using the vkEnumerateInstanceLayerProperties
function. Its usage is identical to that of vkEnumerateInstanceExtensionProperties
which was discussed in the instance creation chapter.
我们将添加一个新函数checkValidationLayerSupport
,它检查是否所有请求的层都可用。首先用vkEnumerateInstanceLayerProperties
函数列出所有可以用的层,它的用法和之前的创建instance章节的vkEnumerateInstanceExtensionProperties
函数相同。
1 bool checkValidationLayerSupport() { 2 uint32_t layerCount; 3 vkEnumerateInstanceLayerProperties(&layerCount, nullptr); 4 5 std::vector<VkLayerProperties> availableLayers(layerCount); 6 vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); 7 8 return false; 9 }
Next, check if all of the layers in validationLayers
exist in the availableLayers
list. You may need to include <cstring>
for strcmp
.
下一步,检查是否validationLayers
中所有的层都存在于availableLayers
列表中。你可能需要include一下<cstring>
,以使用strcmp
函数。
1 for (const char* layerName : validationLayers) { 2 bool layerFound = false; 3 4 for (const auto& layerProperties : availableLayers) { 5 if (strcmp(layerName, layerProperties.layerName) == 0) { 6 layerFound = true; 7 break; 8 } 9 } 10 11 if (!layerFound) { 12 return false; 13 } 14 } 15 16 return true;
We can now use this function in createInstance
:
现在我们可以在createInstance
中使用这个函数了:
1 void createInstance() { 2 if (enableValidationLayers && !checkValidationLayerSupport()) { 3 throw std::runtime_error("validation layers requested, but not available!"); 4 } 5 6 ... 7 }
Now run the program in debug mode and ensure that the error does not occur. If it does, then have a look at the FAQ.
现在在debug模式下运行现在,确保没有错误出现。如果出现错误,就看一下FAQ。
Finally, modify the VkInstanceCreateInfo
struct instantiation to include the validation layer names if they are enabled:
最后,修改VkInstanceCreateInfo
结构体,如果启用了验证层,就将它们的名字包含进来:
1 if (enableValidationLayers) { 2 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); 3 createInfo.ppEnabledLayerNames = validationLayers.data(); 4 } else { 5 createInfo.enabledLayerCount = 0; 6 }
If the check was successful then vkCreateInstance
should not ever return a VK_ERROR_LAYER_NOT_PRESENT
error, but you should run the program to make sure.
如果if判断成功,那么vkCreateInstance
应该就不会再返回VK_ERROR_LAYER_NOT_PRESENT
错误,但是你应该运行程序以确保之。
Message callback 消息回调
Unfortunately just enabling the layers doesn't help much, because they currently have no way to relay the debug messages back to our program. To receive those messages we have to set up a debug messenger with a callback, which requires the VK_EXT_debug_utils
extension.
不幸的是,仅仅启用层,并没有什么帮助,因为它们现在没办法将调试消息转回给程序。为了接收这些消息,我们不得不用回调设置一个debug信使,这就要使用VK_EXT_debug_utils
扩展。
We'll first create a getRequiredExtensions
function that will return the required list of extensions based on whether validation layers are enabled or not:
我们首先创建一个getRequiredExtensions
函数,它将依据验证层是否被启用,返回请求的扩展的列表:
1 std::vector<const char*> getRequiredExtensions() { 2 uint32_t glfwExtensionCount = 0; 3 const char** glfwExtensions; 4 glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); 5 6 std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); 7 8 if (enableValidationLayers) { 9 extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); 10 } 11 12 return extensions; 13 }
The extensions specified by GLFW are always required, but the debug messenger extension is conditionally added. Note that I've used the VK_EXT_DEBUG_UTILS_EXTENSION_NAME
macro here which is equal to the literal string "VK_EXT_debug_utils". Using this macro lets you avoid typos.
用GLFW标明的扩展,总是要用到,但是debug信使扩展是在一定条件下太会添加的。注意,这里我用VK_EXT_DEBUG_UTILS_EXTENSION_NAME
宏,等于用字符串"VK_EXT_debug_utils"。用这个宏让你避免打字了。
We can now use this function in createInstance
:
现在我们可以在createInstance
中用这个函数了:
1 auto extensions = getRequiredExtensions(); 2 createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size()); 3 createInfo.ppEnabledExtensionNames = extensions.data();
Run the program to make sure you don't receive a VK_ERROR_EXTENSION_NOT_PRESENT
error. We don't really need to check for the existence of this extension, because it should be implied by the availability of the validation layers.
运行程序,确保你没有收到VK_ERROR_EXTENSION_NOT_PRESENT
错误。其实我们并不需要检查这个扩展的存在性,因为这已经被验证层的可用而暗示出了。
Now let's see what a debug callback function looks like. Add a new static member function called debugCallback
with the PFN_vkDebugUtilsMessengerCallbackEXT
prototype. The VKAPI_ATTR
and VKAPI_CALL
ensure that the function has the right signature for Vulkan to call it.
现在我们看看debug回调函数长什么样。添加一个新的静态成员函数debugCallback
,以PFN_vkDebugUtilsMessengerCallbackEXT
为原型。VKAPI_ATTR
和VKAPI_CALL
确保函数有正确的签名,以供Vulkan调用。
1 static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( 2 VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, 3 VkDebugUtilsMessageTypeFlagsEXT messageType, 4 const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, 5 void* pUserData) { 6 7 std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl; 8 9 return VK_FALSE; 10 }
The first parameter specifies the severity of the message, which is one of the following flags:
-
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT
: Diagnostic message -
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
: Informational message like the creation of a resource -
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
: Message about behavior that is not necessarily an error, but very likely a bug in your application -
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
: Message about behavior that is invalid and may cause crashes
第一个参数标明消息的严重性,具体如下:
-
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT
:
诊断信息 -
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
:例如创建资源这种消息 -
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
:关于不像是错误,更像是app里的bug的行为的消息 -
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
:关于无效且可能造成崩溃的行为的消息
The values of this enumeration are set up in such a way that you can use a comparison operation to check if a message is equal or worse compared to some level of severity, for example:
这些枚举值是这样设计的:你可以用比较操作来检查一个消息是等于某个严重等级,还是更糟糕。例如:
1 if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { 2 // Message is important enough to show 3 }
The messageType
parameter can have the following values:
-
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
: Some event has happened that is unrelated to the specification or performance -
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
: Something has happened that violates the specification or indicates a possible mistake -
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT
: Potential non-optimal use of Vulkan
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
:潜在的不够理想的运用Vulkan
The pCallbackData
parameter refers to a VkDebugUtilsMessengerCallbackDataEXT
struct containing the details of the message itself, with the most important members being:
-
pMessage
: The debug message as a null-terminated string -
pObjects
: Array of Vulkan object handles related to the message -
objectCount
: Number of objects in array
参数pCallbackData
指向一个VkDebugUtilsMessengerCallbackDataEXT
结构体,它包含消息的细节,其最重要的成员是:
-
pMessage
:以\0结尾的字符串 -
pObjects
:处理相关消息的Vulkan对象的数组 -
objectCount
:对象数组的长度
Finally, the pUserData
parameter contains a pointer that was specified during the setup of the callback and allows you to pass your own data to it.
最后,参数pUserData
包含在设置回调函数的指针,允许你传入自己的数据。
The callback returns a boolean that indicates if the Vulkan call that triggered the validation layer message should be aborted. If the callback returns true, then the call is aborted with the VK_ERROR_VALIDATION_FAILED_EXT
error. This is normally only used to test the validation layers themselves, so you should always return VK_FALSE
.
回调函数返回一个bool值,表明触发了验证层消息的Vulkan调用,是否应该被中止。如果返回true,那么调用就被中止,给出VK_ERROR_VALIDATION_FAILED_EXT
错误。这一般仅用于测试验证层,所以你应该永远让它返回VK_FALSE
。
All that remains now is telling Vulkan about the callback function. Perhaps somewhat surprisingly, even the debug callback in Vulkan is managed with a handle that needs to be explicitly created and destroyed. Such a callback is part of a debug messenger and you can have as many of them as you want. Add a class member for this handle right under instance
:
剩下的就是告诉Vulkan,回调函数是谁。这可能有点惊人,Vulkan中,即使debug回调函数也受一个句柄管理,这个句柄需要被显式地创建和销毁。这样的回调函数是debug信使的一部分,而且你想要多少都可以。在instance
之后为这个句柄添加一个成员:
VkDebugUtilsMessengerEXT debugMessenger;
Now add a function setupDebugMessenger
to be called from initVulkan
right after createInstance
:
现在添加setupDebugMessenger
函数,在initVulkan
中继createInstance
之后调用它:
1 void initVulkan() { 2 createInstance(); 3 setupDebugMessenger(); 4 } 5 6 void setupDebugMessenger() { 7 if (!enableValidationLayers) return; 8 9 }
We'll need to fill in a structure with details about the messenger and its callback:
我们需要给一个struct填入信息(关于信使及其回调函数):
1 VkDebugUtilsMessengerCreateInfoEXT createInfo = {}; 2 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; 3 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; 4 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; 5 createInfo.pfnUserCallback = debugCallback; 6 createInfo.pUserData = nullptr; // Optional
The messageSeverity
field allows you to specify all the types of severities you would like your callback to be called for. I've specified all types except for VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
here to receive notifications about possible problems while leaving out verbose general debug info.
字段messageSeverity
允许你标明所有你想让你的回调函数响应的严重性类型。我标明了除VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
外的所有类型,以接收可能的问题的提醒,且不接收冗长的debug信息。
Similarly the messageType
field lets you filter which types of messages your callback is notified about. I've simply enabled all types here. You can always disable some if they're not useful to you.
类似的,messageType
字段让你过滤掉你不想让回调函数被通知到的消息类型。我简单地启用了所有类型。如果有的类型没用,你可以禁用它们。
Finally, the pfnUserCallback
field specifies the pointer to the callback function. You can optionally pass a pointer to the pUserData
field which will be passed along to the callback function via the pUserData
parameter. You could use this to pass a pointer to the HelloTriangleApplication
class, for example.
最后,pfnUserCallback
字段标明回调函数的指针。你可以(可选地)向pUserData
字段传入一个指针,它会随着pUserData
参数传给回调函数。例如,你可以用它传入一个HelloTriangleApplication
类的指针。
Note that there are many more ways to configure validation layer messages and debug callbacks, but this is a good setup to get started with for this tutorial. See the extension specification for more info about the possibilities.
注意,还有很多其他方式,可以配置验证层消息和debug回调函数,但是在本教程中用这样的方式是个好的开始。参考扩展说明 可得更多可能的信息。
This struct should be passed to the vkCreateDebugUtilsMessengerEXT
function to create the VkDebugUtilsMessengerEXT
object. Unfortunately, because this function is an extension function, it is not automatically loaded. We have to look up its address ourselves using vkGetInstanceProcAddr
. We're going to create our own proxy function that handles this in the background. I've added it right above the HelloTriangleApplication
class definition.
这个struct应当被传入vkCreateDebugUtilsMessengerEXT
函数,以创建VkDebugUtilsMessengerEXT
对象。不幸的是,因为这个函数是个扩展函数,它不是被自动加载的。我们不得不用vkGetInstanceProcAddr
函数自己查找它的地址。我们要创建自己的代理函数来在后台处理这件事。我已经将它放到HelloTriangleApplication
类定义的上面。
1 VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { 2 auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); 3 if (func != nullptr) { 4 return func(instance, pCreateInfo, pAllocator, pDebugMessenger); 5 } else { 6 return VK_ERROR_EXTENSION_NOT_PRESENT; 7 } 8 }
The vkGetInstanceProcAddr
function will return nullptr
if the function couldn't be loaded. We can now call this function to create the extension object if it's available:
如果函数不能被加载,vkGetInstanceProcAddr
函数会返回nullptr
。我们现在可以调用这个函数来创建扩展对象了(如果可用的话)。
1 if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { 2 throw std::runtime_error("failed to set up debug messenger!"); 3 }
The second to last parameter is again the optional allocator callback that we set to nullptr
, other than that the parameters are fairly straightforward. Since the debug messenger is specific to our Vulkan instance and its layers, it needs to be explicitly specified as first argument. You will also see this pattern with other child objects later on. Let's see if it works... Run the program and close the window once you're fed up with staring at the blank window. You'll see that the following messages are printed to the command prompt:
第二个到最后一个参数,又是可选的分配器回调函数,我们设置为nullptr
即可,除此之外,其他参数都很直观。由于debug信使是我们的Vulkan instance及其层专用的,它需要被显式地标明为第一个参数。你将在其他子对象上也看到这样的模式。我们看看它能否工作。运行程序,一旦出现空窗口就关闭窗口。你将看到下述信息打印到命令行:
If you don't see any messages then check your installation.
如果你没有看到任何消息,那么检查你的安装过程。
Oops, it has already spotted a bug in our program! The VkDebugUtilsMessengerEXT
object needs to be cleaned up with a call to vkDestroyDebugUtilsMessengerEXT
. Similarly to vkCreateDebugUtilsMessengerEXT
the function needs to be explicitly loaded. Note that it is normal for this message to be printed multiple times. This happens because multiple validation layers check for the deletion of the debug messenger.
哎呀,它点出了我们程序中的一个bug!VkDebugUtilsMessengerEXT
对象需要被vkDestroyDebugUtilsMessengerEXT
函数清理。与vkCreateDebugUtilsMessengerEXT
相似,这个函数需要被显式地加载。注意,如果这个消息被打印很多次,那是很正常的。这是因为多个验证层检查了debug信使的删除操作。
Create another proxy function right below CreateDebugUtilsMessengerEXT
:
在CreateDebugUtilsMessengerEXT
下面创建另一个代理函数:
1 void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { 2 auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); 3 if (func != nullptr) { 4 func(instance, debugMessenger, pAllocator); 5 } 6 }
Make sure that this function is either a static class function or a function outside the class. We can then call it in the cleanup
function:
确保这个函数要么是个静态类函数,要么是类外部的函数。然后我们可以在cleanup
函数里调用它:
1 void cleanup() { 2 if (enableValidationLayers) { 3 DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr); 4 } 5 6 vkDestroyInstance(instance, nullptr); 7 8 glfwDestroyWindow(window); 9 10 glfwTerminate(); 11 }
When you run the program again you'll see that the error message has disappeared. If you want to see which call triggered a message, you can add a breakpoint to the message callback and look at the stack trace.
当你再次运行程序时,你会看到错误消息消失了。如果你想看看,是什么触发了消息,你可以添加一个断点到消息回调函数,观察stack trace。
Debugging instance creation and destruction 调试instance的创建过程和销毁过程
Although we've now added debugging with validation layers to the program we're not covering everything quite yet. The vkCreateDebugUtilsMessengerEXT
call requires a valid instance to have been created and vkDestroyDebugUtilsMessengerEXT
must be called before the instance is destroyed. This currently leaves us unable to debug any issues in the vkCreateInstance
and vkDestroyInstance
calls.
尽管我们现在加入了用验证层进行调试的功能,我们还没有搞定所有问题。vkCreateDebugUtilsMessengerEXT
函数要求一个有效的instance已经创建完毕,vkDestroyDebugUtilsMessengerEXT
必须在instance销毁之前调用。这让我们无法在vkCreateInstance
和vkDestroyInstance
的调用中调试任何问题。
However, if you closely read the extension documentation, you'll see that there is a way to create a separate debug utils messenger specifically for those two function calls. It requires you to simply pass a pointer to a VkDebugUtilsMessengerCreateInfoEXT
struct in the pNext
extension field of VkInstanceCreateInfo
. First extract population of the messenger create info into a separate function:
但是,如果你仔细阅读扩展文档,你会看到有个办法可以专门为这2个函数创建一个单独的debug工具信使。它要求你向VkInstanceCreateInfo
的pNext
扩展字段传入一个VkDebugUtilsMessengerCreateInfoEXT
结构体。首先提取信使创建信息到一个单独函数:
1 void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) { 2 createInfo = {}; 3 createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; 4 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; 5 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; 6 createInfo.pfnUserCallback = debugCallback; 7 } 8 9 ... 10 11 void setupDebugMessenger() { 12 if (!enableValidationLayers) return; 13 14 VkDebugUtilsMessengerCreateInfoEXT createInfo; 15 populateDebugMessengerCreateInfo(createInfo); 16 17 if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) { 18 throw std::runtime_error("failed to set up debug messenger!"); 19 } 20 }
We can now re-use this in the createInstance
function:
现在我们可以在createInstance
函数中复用它:
1 void createInstance() { 2 ... 3 4 VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo; 5 if (enableValidationLayers) { 6 createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); 7 createInfo.ppEnabledLayerNames = validationLayers.data(); 8 9 populateDebugMessengerCreateInfo(debugCreateInfo); 10 createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; 11 } else { 12 createInfo.enabledLayerCount = 0; 13 14 createInfo.pNext = nullptr; 15 } 16 17 ... 18 }
The debugCreateInfo
variable is placed outside the if statement to ensure that it is not destroyed before the vkCreateInstance
call. By creating an additional debug messenger this way it will automatically be used during vkCreateInstance
and vkDestroyInstance
and cleaned up after that.
变量debugCreateInfo
被置于if语句之外,以确保它在vkCreateInstance
调用之前不会被销毁。通过以这样的方式创建一个额外的debug信使,它会被自动地用在vkCreateInstance
和vkDestroyInstance
中,且在那之后被清理掉。
Configuration 配置
There are a lot more settings for the behavior of validation layers than just the flags specified in the VkDebugUtilsMessengerCreateInfoEXT
struct. Browse to the Vulkan SDK and go to the Config
directory. There you will find a vk_layer_settings.txt
file that explains how to configure the layers.
除在VkDebugUtilsMessengerCreateInfoEXT
结构体中标明的flag外,严重层的行为配置项还有很多。浏览Vulkan SDK,找到Config
文件夹。在这里你会发现一个vk_layer_settings.txt
文件,它解释了如何配置层。
To configure the layer settings for your own application, copy the file to the Debug
and Release
directories of your project and follow the instructions to set the desired behavior. However, for the remainder of this tutorial I'll assume that you're using the default settings.
为了给你的app配置层,将此文件复制到你项目的Debug
和Release
文件夹下,遵循其指示来设置你想要的行为。然而,在本教程最后,我将假设你在使用默认配置。
Throughout this tutorial I'll be making a couple of intentional mistakes to show you how helpful the validation layers are with catching them and to teach you how important it is to know exactly what you're doing with Vulkan. Now it's time to look at Vulkan devices in the system.
在本教程中我将故意弄几个内部错误,以展示验证层对于捕捉错误的益处,以及懂得你在用Vulkan时保持头脑清楚的重要性。现在是时候看一下系统中的Vulkan设备了。
C++ code C++完整源代码
- Previous上一章
- Next下一章