内容创建/渲染

高级 API 性能:描述符

 

通过使用描述符类型,您可以将资源绑定到着色器,并指定如何访问这些资源。这可在 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 上的性能排名:
    1. 根常量是最快的,没有间接,并且可以直接索引。
    2. 根 CBV/SRV/UAV 是第二快,具有单向和无边界检查。
    3. 描述符表速度最慢,需要检查两个间接和边界。
  • 例如 HLSL SM 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

  • 不要在单个描述符集中存在过于稀疏的绑定偏移。
    • 尽可能紧密地打包。
    • 未使用的绑定索引会浪费内存并降低缓存效率。

 

Tags