内容创建/渲染

通过对 Vulkan 描述符堆的端到端支持简化资源绑定

着色器是处理光线、像素、几何图形和纹理等视觉数据以产生特定渲染效果的 GPU 程序。着色器通过称为资源绑定的过程查找必要的数据。CPU 代码会编排 GPU 资源 (例如纹理和内存缓冲区) 的创建,然后仔细安排着色器代码,以便通过绑定协议访问这些代码。

Vulkan 中的新描述符堆功能从头开始重构了这一过程,解决了用户的长期反馈问题,从而简化了资源绑定,并使其在 Direct3D 12 (D3D12) 中的工作方式更加一致。描述符堆可直接控制描述符内存管理,更适合现代硬件,并可简化资源管理的性能优化。它们对于使用动态纹理索引的渲染器、复杂的光线追踪着色器或支持 D3D12 的共享后端尤为有用。本文重点介绍添加的描述符堆、它们与描述符集的比较,以及如何开始。

描述符堆 API 由 VK_EXT_descriptor_heap 定义,Khronos 为此发布了 参考手册使用指南。EXT 前缀表示扩展程序已获得跨供应商支持。Khronos 正在 积极寻求反馈 关于当前界面的反馈,目标是完善界面并将其推广到 KHR 级别的扩展程序。

NVIDIA 驱动 610 及更高版本为 VK_EXT_descriptor_heap 提供扩展到工具和代码示例的支持。NVIDIA Nsight Graphics 2026.2 通过相同的帧捕获和检查工作流引入了描述符堆支持,以快速缩小与描述符相关的问题。开源 descriptor_heap 示例应用是了解如何在实践中使用扩展程序的一个很好的起点。

如需试用,请下载最新版本的Nsight Graphics,并在“Help”(帮助)>“Samples”(示例)下查看描述符堆示例。请注意,此菜单仅在Windows上可用。在Linux上,您需要下载并构建示例。

从描述符集迁移到堆有哪些好处?

描述符是供应商特定的字节,用于描述缓冲区和纹理等资源。例如,描述符可能由低级指针和一些元数据组成,但对于着色器而言,它只是一个不透明内存块。资源绑定过程主要包括管理描述符以及在 CPU 和着色器代码中访问这些描述符的方法。

Vulkan 最初仅提供描述符集,这些描述符集是可由不同着色器组成和重用的描述符数组。这些操作需要繁琐的描述符集布局、额外的分组和池分配。VK_EXT_descriptor_buffer 扩展程序为描述符引入了应用管理内存,但保留了基于集的绑定模型、布局和其他所需的基础架构层。 

相比之下,VK_EXT_descriptor_heap 大大简化了概念,使其只在用户分配的内存“堆”中使用用户编写的描述符 (请勿与二进制堆数据结构混淆) 。一次只能绑定一个资源描述符堆,而且 Vulkan 文档建议在应用程序的整个生命周期中使用相同的堆,因为绑定新的堆会产生高昂的成本并影响性能。单独的堆专门用于采样器,遵循相同的准则。访问着色器中的描述符可以使用多种方法来完成,详情请参阅以下章节。

作为这种灵活性的回报,应用程序负责内存分配和布局。下文简要介绍了 API,包括与描述符集的比较。有关更完整的详细信息,请参阅 Descriptor Heap 指南

如何使用描述符堆 

准备好开始编码了吗?要使用描述符堆和支持的工具,您需要以下版本的软件:

  • NVIDIA 驱动 610 或更高版本,适用于本文所述的所有内容。有时,Vulkan 开发者测试版驱动可能会抢先体验新的驱动级描述符堆更新。
  • Vulkan 头文件 1.4.340 或更高版本 (包含在 Vulkan SDK 中)
  • Nsight Graphics 2026.2

与任何 Vulkan 扩展程序一样,使用之前必须请求 VK_EXT_descriptor_heap 扩展程序。将 VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAMEVkPhysicalDeviceDescriptorHeapFeaturesEXT 添加到 VkDeviceCreateInfo 扩展程序和 pNext 链中。

要在不进行任何修改的情况下使用现有着色器代码,可以在创建着色器时使用 VkShaderDescriptorSetAndBindingMappingInfoEXT 将描述符映射到设置和绑定索引。有关详细信息,请参阅将堆映射到现有着色器。以下两个示例也在descriptor_heap 示例中演示:

  • 推送索引:将映射源设置为 VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_PUSH_INDEX_EXT 后,从推送常量数据读取的值会偏移描述符位置。这允许应用为同一着色器的每次绘制选择不同的描述符范围。
  • 常量偏移:更简单的是,VK_DESCRIPTOR_MAPPING_SOURCE_HEAP_WITH_CONSTANT_OFFSET_EXT 只是为集/ 绑定描述符位置添加了一个常量偏移量。着色器可以始终为该绑定访问相同的描述符,或者如果绑定是数组或具有 bindingCount,则着色器可以动态地从偏移量中选择自己的索引。
描述子集 描述符堆
vkCreateDescriptorPool vkAllocateDescriptorSets VkBuffer with VkDeviceMemory 绑定和 VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT
VkDescriptorSetLayoutBinding
vkCreateDescriptorSetLayout
VkShaderDescriptorSetAndBindingMappingInfoEXT
vkUpdateDescriptorSets
vkCmdPushDescriptorSet
vkWriteResourceDescriptorsEXT
vkCmdPushConstants vkCmdPushDataEXT
vkCmdBindDescriptorSets vkCmdBindSamplerHeapEXTvkCmdBindResourceHeapEXT
表 1. 描述符集 (左) 及其对应的描述符堆 (右)

在以下 GLSL 和语着色器示例中,整个堆已映射到具有零常数偏移量的绑定。然后,着色器可有效地对堆中的全局描述符进行索引。应用程序映射代码不仅指定了绑定集和位置,还指定了描述符的类型和步长细节,这些描述符必须与相应着色器代码中绑定的类型相匹配。

GLSL:

layout(set = 0, binding = 0)
uniform texture2D heapImages[];

layout(set = 0, binding = 1)
uniform sampler heapSamplers[];

layout(push_constant)
uniform PushConstants {
  uint textureIndex;
} push;

layout(location = 0) in vec2 uv;
layout(location = 0) out vec4 outColor;

void main
{
  outColor = texture(
    sampler2D(
      heapTextures[nonuniformEXT(push.textureIndex)],
      heapSamplers[0]),
    uv);
}

语着色语言:

[[vk::binding(0, 0)]]  // (binding, set)
Texture2D<float4> heapImages[];

[[vk::binding(1, 0)]]
SamplerState heapSamplers[];

[[vk::push_constant]]
ConstantBuffer<PushConstants> push;

[shader(“fragment”)]

float4 fragmentMain(float2 uv : TEXCOORD0) : SV_Target { return heapImages[NonUniformResourceIndex(push.textureIndex)].Sample( heapSamplers[0], uv); }

此组织可能看起来比较熟悉,特别是对于那些习惯使用 D3D12 描述符堆的人而言:

  • 着色器可以动态索引描述符
  • 索引由应用程序管理
  • 堆是全局绑定状态

但是,Vulkan 与 D3D12 的区别有很多,包括应用程序为堆选择确切的内存位置,以及不同类型的描述符大小不同。

直接描述符堆访问

着色器可以直接从绑定资源和采样器描述符堆中索引描述符数组,而无需将描述符映射到集索引和绑定索引。这种方法意味着指针锯齿,需要仔细编制索引以进行有效访问,并且必须启用 VK_KHR_shader_untyped_pointers 扩展程序。如需了解详情,请参阅 Vulkan 指南的无类型着色器模型部分

descriptor_heap 应用包含一个使用无类型指针的示例,并且还可与 Nsight Graphics 配合使用。此功能现已可供试用,但该功能是新功能,在着色器语言和工具链中仍处于成熟阶段。请持续关注更新。

检查和调试

在 Nsight Graphics 2026.2 中,描述符堆显示在开发者用于查看描述符集的同一着色器资源视图中。如果您使用 VkShaderDescriptorSetAndBindingMappingInfoEXT,则仅显示映射的描述符。 

例如,请参见图 2 中一个立方体的六个纹理。要查看映射值,请单击 API 检查器中链接的工作流或着色器对象,然后在 Object Browser undercreateInfo > pNext > pMappings 中查看 (图 3) 。

在调试工具中捕获和回放描述符堆需要实现支持设备功能 VkPhysicalDeviceDescriptorHeapFeaturesEXT::descriptorHeapCaptureReplay。NVIDIA 驱动从驱动版本 610 开始增加了对此功能的支持。

如果这是您第一次使用 Nsight Graphics,则需先创建一个 Capture,然后使用图形调试器和实时回放对其进行检查:

  1. 单击“Start Activity” (开始活动)
  2. 单击对话框窗口中的“Launch Graphics Capture” (启动图形截取)
  3. 按 F11 或单击应用程序窗口中的 Capture 按钮以创建 Capture 文件,其中包含用于渲染帧的所有 Vulkan API 调用
  4. 终止应用
  5. 返回 Nsight Graphics,您应该会看到捕获文件文档。在“document” (文档) 选项卡中,单击“Start Graphics Debugger” (启动图形调试器)
  6. 在窗口顶部的工具栏中,单击“Start Live Replay” (开始实时回放)
  7. 在 Graphics Debugger (图形调试器) 下探索窗口。在左侧列出的“Events” (事件) 的“Filter” (筛选) 文本框中输入“draw” (绘制) ,以隔离绘图调用。然后单击“FS” ( FS) 检查片段着色器 API 状态

开始使用描述符堆

描述符堆可与 NVIDIA 驱动和开发者工具配合使用。了解描述符堆的最佳方法是开始使用它们进行构建。

  • 运行并检查示例:最简单的方法是在“Help” (帮助) >“Samples” (示例) 下运行 Nsight Graphics 随附的示例
  • 本地构建和调试:有关构建 descriptor_heap 示例的说明可在其存储库中找到:NV_skp_1 的说明位于 https://github.com/nvpro-samples/vk_mini_samples/
  • 提供反馈: 对于 Nsight Graphics 问题,请使用“Help” (帮助) >“Send Feedback” (发送反馈) 。有关 vk_mini_samples 实现的问题,请打开 GitHub 问题。欢迎就实际使用和采用情况提供反馈,您可以在 NVIDIA 开发者论坛 上进行讨论,也可以在 Khronos Vulkan-Docs 资源库中发布反馈。

随着使用模式的成熟和着色语言支持的不断发展,请留意未来的更新。

标签