这篇文章最初发表在 NVIDIA 技术博客上。
通过使用描述符类型,您可以将资源绑定到着色器,并指定如何访问这些资源。这可在 CPU 和 GPU 之间实现高效通信,并使着色器能够在渲染期间访问必要的数据。
推荐
- 首选"无绑定"设计。
- 使用无界数组描述符,指向包含帧所需的所有已知纹理、缓冲区和加速结构的大型描述符表或集合。
- 预先上传尽可能多的数据(纹理、每绘制常量和每帧常量),并通过这些描述符数组进行访问。
- 这种设计还可以更轻松地实现光线追踪,即允许访问每个着色器中的每个纹理和缓冲区。
- 将描述符缓存在 GPU 可见的描述符堆 (DirectX 12) 或具有已知偏移量的集合 (Vulkan) 上。这降低了 CPU 开销,几乎消除了复制描述符的需求。
- 使用堆的多个副本以优雅方式处理描述符更改,例如流式传输纹理和缓冲区。但不要超过 1M 和 2K 的限制。有关更多信息,请参阅本博文后面的 不推荐 小节。
- 使用根 (DirectX 12) 或推送 (Vulkan) 常量。它们是每次绘制传输不同常量的最快方法。
- 在 Pascal 上:对于常量数据,首选 CBV 而非 SRV.
- 一般来说,SRV 缓冲区的速度要比 <=Pascal 上的 CBV 缓冲区慢。
- Volta 及更高版本的性能相当。
- 更好的方法是尝试使用根常量。
- 即使对于不经常更改的数据(例如,材质数据、通道数据和每帧数据),它们也可以更快。
DirectX 12
- 您可以随意更大限度地使用完整的 64
DWORD
根签名中可用的数据类型。 - GPU 和 CPU 上的性能排名:
- 根常量是最快的,没有间接,并且可以直接索引。
- 根 CBV/SRV/UAV 是第二快,具有单向和无边界检查。
- 描述符表速度最慢,需要检查两个间接和边界。
- 例如 HLSL SM 6.6,使用动态资源绑定。
- 这样,您可以从根签名中省略一些描述符表,以获得更多的根常量和其他数据空间。有关更多信息,请参阅 正在开发中:HLSL 着色器模型 6.6。
- 切换根签名是一项快速操作。
- 使用多个根签名来提高绑定效率可能是一种有效的策略。
- 这对于非无绑定设计尤其如此。
- 在不必要地重新绑定大量数据时,它可能效率低下。切换根签名会导致现有绑定丢失。
- 在某些情况下,使用 Root Signature 1.1 可以获得略高的性能。
- 特别是,使用
DATA_STATIC_SET_AT_EXECUTE
尽可能让驱动程序提前内联一些数据。 - 这不是高优先级;仅在方便时使用。
- 特别是,使用
Vulkan
- 尽量减少管道布局中描述符集的数量。
- 使用动态统一缓冲区和存储缓冲区进行每次绘制调用更改。
- 首选使用组合图像和采样器描述符。
- Vulkan 1.2 支持将存储缓冲区的设备地址作为 64 位值传递给着色器。这可实现 DirectX 或 HLSL 中无法使用的类似指针的工作流(例如投射)。GLSL 通过
GL_EXT_buffer_reference (2)
并使用SPV_EXT_physical_storage_buffer
。尝试优化buffer_reference_align
因为硬件可以相应地利用更广泛的内存加载操作。
不推荐
- 整个应用程序(GPU 可见)的活动描述符和采样器总数不得超过 100 万个。
- 否则,在切换描述符堆 (DirectX 12) 时,整个 GPU 的工作流可能会停滞。
- 每当超过限制时,它都会降低命令列表的异步执行效率。
- 在 Vulkan 上,驱动程序会自动执行描述符的重复数据删除。前面提到的限制仅计入独特的变体。
- 通常情况下,请尽量低于
VkPhysicalDeviceLimits
.
- 通常情况下,请尽量低于
- 尽可能避免使用类型化的 UAV 负载或存储。
DirectX 12
- 防止在帧期间过度创建或复制描述符。
- 永久性地保留描述符,而不是在每一帧中重新分配或复制它们。
- 在描述符表中使用根 CBV 而非 CBV.
- 无需调用
CreateConstantBufferView
具有根 CBV.
- 无需调用
- 仔细选择较小的描述符表也可以改善这种情况。
- 尽可能将重复的描述符减少为相同的资源。
- 示例:不应在描述符 0、10、20、30、40、50 等中引用纹理 0.
- 相反,请尝试更改描述符表的布局,以便能够多次重用相同的描述符。
Vulkan
- 不要在单个描述符集中存在过于稀疏的绑定偏移。
- 尽可能紧密地打包。
- 未使用的绑定索引会浪费内存并降低缓存效率。