Vulkan 验证概述

本节的目的是全面概述 Vulkan 如何处理 API 的有效使用

有效使用 (VU)

VUVulkan 规范中被明确定义为

为了在应用程序中实现明确定义的运行时行为而必须满足的一组条件。

Vulkan 作为显式 API 的主要优点之一是,实现(驱动程序)不会浪费时间检查有效输入。在 OpenGL 中,实现必须始终检查有效使用情况,这会增加明显的开销。Vulkan 中没有 glGetError 等效项。

有效使用情况将在规范中列在每个函数和结构之后。例如,如果 VUID 在 VkBindImageMemory 中检查无效的 VkImage,则规范中的有效使用情况会在 VkBindImageMemory 下找到。这是因为验证层只会在应用程序执行期间的 VkBindImageMemory 处知道所有信息。

未定义行为

当应用程序根据规范中的有效使用情况提供无效输入时,结果是未定义行为。在这种状态下,Vulkan 不做任何保证,因为 未定义行为可能会导致任何事情

非常重要:虽然未定义行为可能在一种实现中似乎有效,但在另一种实现中很有可能会失败。

有效使用 ID (VUID)

VUID 是为每个有效使用情况赋予的唯一 ID。这允许一种在规范中轻松指向有效使用情况的方法。

VUID-vkBindImageMemory-memoryOffset-01046 为例,只需将 VUID 添加到规范的 HMTL 版本中的锚点 (vkspec.html#VUID-vkBindImageMemory-memoryOffset-01046),它将直接跳转到 VUID。

Khronos 验证层

由于 Vulkan 不进行任何错误检查,因此在开发时立即启用 验证层以帮助捕获无效行为非常重要。应用程序也不应将验证层与其应用程序一起发布,因为它们会明显降低性能并且是为开发阶段设计的。

Khronos 验证层过去由多个层组成,但现在已统一为单个 VK_LAYER_KHRONOS_validation 层。LunarG 的白皮书中解释了更多详细信息

获取验证层

验证层在不断更新和改进,因此始终可以获取源代码并自行构建。如果您需要预构建版本,则所有受支持的平台都有多种选择

分解验证错误消息

验证层会在发生错误时尝试提供尽可能多的有用信息。以下示例旨在帮助说明如何从验证层中获取最多信息

示例 1 - 隐式有效使用

此示例显示了触发 隐式 VU 的情况。VUID 的末尾不会有数字。

Validation Error: [ VUID-vkBindBufferMemory-memory-parameter ] Object 0: handle =
0x20c8650, type = VK_OBJECT_TYPE_INSTANCE; | MessageID = 0xe9199965 | Invalid
VkDeviceMemory Object 0x60000000006. The Vulkan spec states: memory must be a valid
VkDeviceMemory handle (https://registry.khronos.org/vulkan/specs/1.1-extensions/
html/vkspec.html#VUID-vkBindBufferMemory-memory-parameter)
  • 首先要注意的是 VUID 列在消息的开头 (VUID-vkBindBufferMemory-memory-parameter)

    • 消息的末尾还有一个指向规范中 VUID 的链接

  • Vulkan 规范声明:是规范中引用的 VUID。

  • VK_OBJECT_TYPE_INSTANCEVkObjectType

  • Invalid VkDeviceMemory Object 0x60000000006可调度句柄,用于帮助显示哪个 VkDeviceMemory 句柄是错误的起因。

示例 2 - 显式有效使用

此示例显示了尝试将某个 VkImage 绑定到 2 个不同的 VkDeviceMemory 对象时发生的错误

Validation Error: [ VUID-vkBindImageMemory-image-01044 ] Object 0: handle =
0x90000000009, name = myTextureMemory, type = VK_OBJECT_TYPE_DEVICE_MEMORY; Object 1:
handle = 0x70000000007, type = VK_OBJECT_TYPE_IMAGE; Object 2: handle = 0x90000000006,
name = myIconMemory, type = VK_OBJECT_TYPE_DEVICE_MEMORY; | MessageID = 0x6f3eac96 |
In vkBindImageMemory(), attempting to bind VkDeviceMemory 0x90000000009[myTextureMemory]
to VkImage 0x70000000007[] which has already been bound to VkDeviceMemory
0x90000000006[myIconMemory]. The Vulkan spec states: image must not already be
backed by a memory object (https://registry.khronos.org/vulkan/specs/1.1-extensions/
html/vkspec.html#VUID-vkBindImageMemory-image-01044)
  • 示例 2 与示例 1 几乎相同,区别在于附加到对象上的 namename = myTextureMemory)。这是使用 VK_EXT_debug_util 扩展(如何使用该扩展的示例)完成的。请注意,在不支持 VK_EXT_debug_util 的旧设备上,可能需要使用 VK_EXT_debug_report 的旧方法。

  • 导致此错误涉及 3 个对象。

    • 对象 0 是一个名为 myTextureMemoryVkDeviceMemory

    • 对象 1 是一个没有名称的 VkImage

    • 对象 2 是一个名为 myIconMemoryVkDeviceMemory

  • 有了这些名称,很容易看出 “在 vkBindImageMemory() 中,myTextureMemory 内存试图绑定到一个已经绑定到 myIconMemory 内存的图像”。

每个错误消息都包含统一的日志记录模式。这允许在任何错误中轻松找到信息。模式如下

  • 日志状态(例如,Error:Warning: 等)

  • VUID

  • 涉及的对象数组

    • 数组的索引

    • Dispatch Handle 值

    • 可选名称

    • 对象类型

  • 发生错误的函数或结构体

  • 层创建的用于帮助描述问题的消息

  • 规范中的完整有效使用

  • 有效使用的链接

多个 VUID

以下内容并不理想,正在研究如何使其更简单

目前,规范的设计仅根据 构建规范的版本和扩展来显示 VUID。简而言之,扩展和版本的添加可能会改变 VU 语言(从添加的新 API 项来看),从而创建一个单独的 VUID。

以下是一个来自 Vulkan-Docs 的示例,规范是从这里生成的

  * [[VUID-VkPipelineLayoutCreateInfo-pSetLayouts-00287]]
    ...

这会创建两个非常相似的 VUID

在此示例中,两个 VUID 非常相似,唯一的区别是其中一个引用了 VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT,而另一个没有引用。这是因为枚举是在添加 VK_EXT_descriptor_indexing 时添加的,而它现在是 Vulkan 1.2 的一部分。

这意味着 2 个有效的 规范的 html 链接将如下所示

  • 1.1/html/vkspec.html#VUID-VkPipelineLayoutCreateInfo-pSetLayouts-00287

  • 1.2/html/vkspec.html#VUID-VkPipelineLayoutCreateInfo-descriptorType-03016

验证层使用应用程序的设备属性来决定显示哪个 VUID。因此,在这种情况下,如果您在 Vulkan 1.2 实现或支持 VK_EXT_descriptor_indexing 的设备上运行,它将显示 VUID 03016

特殊使用标签

当应用程序尝试使用任何带有 特殊使用标签的扩展时,最佳实践层会产生警告。此类扩展的一个示例是 VK_EXT_transform_feedback,它仅设计用于模拟层。如果应用程序的预期用途与特殊用例之一相对应,则以下方法将允许您忽略警告。

使用 VK_EXT_debug_report 忽略特殊使用警告

VkBool32 DebugReportCallbackEXT(/* ... */ const char* pMessage /* ... */)
{
    // If pMessage contains "specialuse-extension", then exit
    if(strstr(pMessage, "specialuse-extension") != NULL) {
        return VK_FALSE;
    }

    // Handle remaining validation messages
}

使用 VK_EXT_debug_utils 忽略特殊使用警告

VkBool32 DebugUtilsMessengerCallbackEXT(/* ... */ const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData /* ... */)
{
    // If pMessageIdName contains "specialuse-extension", then exit
    if(strstr(pCallbackData->pMessageIdName, "specialuse-extension") != NULL) {
        return VK_FALSE;
    }

    // Handle remaining validation messages
}