关注专栏 写文章
第 4 章. Vulkan 中的调试

第 4 章. Vulkan 中的调试

在上一章中,我们初始化了 Vulkan API 并知道了层和扩展的概念。 我们连接物理硬件设备并理解了它所暴露的不同类型的队列。 由于我们正在为实际具体的实现做前期的准备工作,因此了解 Vulkan 中的调试功能,从而避免不愉快的错误,就显得非常重要了。

Vulkan 允许您通过验证层执行调试。 这些验证层检查是可选的,可以在运行时注入到系统中。 传统的图形 API 会预先使用某种错误检查机制来执行验证,这是管线的所必需部分。 这在开发阶段确实很有用,但实际上,在发布阶段这却是一种开销,因为验证错误在开发阶段本身可能已经获得了修复。 这种强制性检查会导致 CPU 花费大量的时间进行错误检查。

另一方面,Vulkan 旨在提供最佳性能,其中可选的验证过程和调试模型起着至关重要的作用。 Vulkan 假定应用程序已经完成了它的功课 ------ 使用开发阶段提供的验证和调试功能,并且在发布阶段它是完全可以信赖的。

在本章中,我们将学习 Vulkan 应用程序的验证和调试过程。 我们将涵盖以下主题

  • 窥视 Vulkan 调试
  • 了解 LunarG 验证层及其功能
  • 在 Vulkan 中实现调试功能

窥视 VUlkan 调试

Vulkan 调试功能用于验证应用程序的实现。 它不仅表示错误,还表示一些其他的验证,例如正确的 API 使用。 它通过验证传递给它的每个参数来执行这项操作,警告使用中可能存在不正确和危险的 API 实践,并在 API 未得到最佳使用时报告任何与性能相关的警告。 默认情况下,是禁用调试功能的,并且启用调试功能是应用程序的责任。 调试仅适用于实例级、在实例(VkInstance)创建时明确启用的那些层。

启用调试功能时,会将其自身插入到层感兴趣的 Vulkan 命令的调用链中。对于每个命令,调试功能 debugging 会访问所有启用的层并验证它们是否存在任何潜在的错误、警告以及调试信息等。

在 Vulkan 中进行调试很简单。 以下概述描述了在应用程序中启用调试所需的步骤:

  1. 通过在实例级添加 VK_EXT_DEBUG_REPORT_EXTENSION_NAME 扩展来启用调试功能。
  2. 定义用于调试的一组验证层。 例如,我们对实例和设备级的以下层感兴趣。 有关这些层功能的更多信息,请参阅下一节:
  • VK_LAYER_GOOGLE_unique_objects
  • VK_LAYER_LUNARG_api_dump
  • VK_LAYER_LUNARG_core_validation
  • VK_LAYER_LUNARG_image
  • VK_LAYER_LUNARG_object_tracker
  • VK_LAYER_LUNARG_parameter_validation
  • VK_LAYER_LUNARG_swapchain
  • VK_LAYER_GOOGLE_threading
  1. Vulkan 调试 API 不是核心命令的一部分,核心命令可以通过加载程序静态加载。 这些调试 API 是以扩展 API 的形式存在的,可以在运行时检索并动态链接到预定义的函数指针。 因此,下一步,就是动态查询和链接调试扩展 API vkCreateDebugReportCallbackEXT 和 vkDestroyDebugReportCallbackEXT。 这两个 API 调用用于创建和销毁调试报告。
  2. 一旦成功检索到用于调试报告的函数指针,前一个 API(vkCreateDebugReportCallbackEXT)就会创建调试报告对象, Vulkan 会在用户自定义的回调中返回调试报告,这个回调必须链接到此 API。
  3. 不再需要调试时,销毁调试报告对象。

理解 LunarG 验证层以及它们的功能

LunarG Vulkan SDK 支持以下层用于调试和验证目的。 在以下几点中,我们描述了一些层,可以帮助您理解 Vulkan 提供的功能:

  • VK_LAYER_GOOGLE_unique_objects:不可分发的 Vulkan 对象句柄不必是唯一的;驱动程序可以为它认为等效的多个对象返回相同的句柄。 此行为使得跟踪对象变得困难,因为在删除时不清楚要引用哪个对象。 该层在创建时将 Vulkan 对象打包为一个唯一的标识符,并在应用程序使用时将其解包。 这确保了在验证时有适当的对象生命周期跟踪(object lifetime tracking)。 根据 LunarG 的建议,该层必须位于验证层链中的最后一个,使其更靠近显示驱动程序。
  • VK_LAYER_LUNARG_api_dump:该层有助于了解传递给 Vulkan API 的参数值。 它会打印所有的数据结构参数及其值。
  • VK_LAYER_LUNARG_core_validation:用于验证和打印来自描述符集、管线状态、动态状态等的重要信息。 该层跟踪并验证 GPU 内存、对象绑定和命令缓冲区。 此外,它还验证图形管线和计算管线。
  • VK_LAYER_LUNARG_image:该层可用于验证纹理格式、渲染目标格式等。 例如,它会验证设备上是否支持请求的格式。 它验证图像视图的创建参数对于创建视图的图像是否合理。
  • VK_LAYER_LUNARG_object_tracker:跟踪对象的创建及其使用和销毁,这有助于避免内存泄漏。 它还验证所引用的对象是否已正确创建并且当前有效。
  • VK_LAYER_LUNARG_parameter_validation:这个验证层确保传递给 API 的所有参数按照规范约定都是正确的,并且达到所需的期望。 它检查参数的值是否一致,并且符合 Vulkan 规范中定义的有效使用条件。 此外,它还会检查 Vulkan 控制结构的 type 字段是否包含与该类型结构所期望的、相同的值。
  • VK_LAYER_LUNARG_swapchain:该层验证 WSI 交换链扩展的使用。 例如,它会在使用其函数之前检查 WSI 扩展是否可用。 此外,它还验证图像索引是否在交换链中的图像数量之内,即检查是否超出了数量范围。
  • VK_LAYER_GOOGLE_threading:这对于线程安全来说是很有帮助的。 它检查多线程 API 使用的有效性。 该层确保在多线程环境下使用多个调用的若干对象的同时使用。 它报告线程规则的违规并为这些调用强制执行互斥锁。 此外,它允许应用程序继续运行而不会真正崩溃,尽管报告了线程存在的问题。
  • VK_LAYER_LUNARG_standard_validation:以正确的顺序启用所有标准层。

注意

有关验证层的更多信息,请访问 LunarG 的官方网站。 查看 vulkan.lunarg.com/doc/s,并特别参考“验证层详细信息”部分以获取更多详细信息。

Vulkan 中实现调试

由于调试是通过验证层暴露的,因此调试的大多数核心实现会在 VulkanLayerAndExtension 类(VulkanLEDer / .cpp)下完成。 在本节中,我们将学习有助于我们在 Vulkan 中启用调试过程的实现细节:

Vulkan 调试工具不是核心功能默认的一部分。 因此,为了启用调试和访问报告回调功能,我们需要添加一些必要的扩展和层:

  • 扩展:将 VK_EXT_DEBUG_REPORT_EXTENSION_NAME 扩展添加到实例级别。 这有助于将 Vulkan 调试 API 暴露给应用程序:
vector<const char *> instanceExtensionNames = {
. . . . // other extensios VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
};
  • 层:在实例级别定义以下层,以允许在这些层上进行调试:
vector<const char *> layerNames = { "VK_LAYER_GOOGLE_threading", "VK_LAYER_LUNARG_parameter_validation", "VK_LAYER_LUNARG_device_limits", "VK_LAYER_LUNARG_object_tracker", "VK_LAYER_LUNARG_image", "VK_LAYER_LUNARG_core_validation", "VK_LAYER_LUNARG_swapchain", "VK_LAYER_GOOGLE_unique_objects"
};

注意

除启用的验证层外,LunarG SDK 还提供了一个称为 VK_LAYER_LUNARG_standard_validation 的特殊层。 这样就可以按照此处提到的正确顺序启用基本的验证。 此外,此内置元数据层会以最佳顺序加载一组标准的验证层。 如果您对层没有特别指定的话,那么这是一个不错的选择。

a)VK_LAYER_GOOGLE_threading

b)VK_LAYER_LUNARG_parameter_validation

c)VK_LAYER_LUNARG_object_tracker

d)VK_LAYER_LUNARG_image

e)VK_LAYER_LUNARG_core_validation

f)VK_LAYER_LUNARG_swapchain

g)VK_LAYER_GOOGLE_unique_objects

然后将这些层提供给 vkCreateInstance()API,从而启用它们:

VulkanApplication* appObj = VulkanApplication::GetInstance(); appObj->createVulkanInstance(layerNames,
instanceExtensionNames, title);