资源绑定概述

了解 DirectX 12 中资源绑定的关键是描述符、描述符表、描述符堆和根签名的概念。

资源和图形管道

着色器资源(例如纹理、常量表、图像、缓冲区等)不会直接绑定到着色器管道,而是通过描述符进行引用。 描述符是包含有关一个资源的信息的小型对象。

描述符组合在一起构成了描述符表。 每个描述符表存储有关一个资源类型范围的信息。 有多种不同类型的资源。 最常见的资源为:

  • 常量缓冲区视图 (CBV)
  • 无序访问视图 (UAV)
  • 着色器资源视图 (SRV)
  • 采样器

SRV、UAV 和 CBV 描述符可以合并到同一个描述符表中。

图形和计算管道通过按索引引用描述符表来获取对资源的访问权限。

描述符表存储在描述符堆中。 在理想情况下,描述符堆包含要渲染的一个或多个帧的所有描述符(在描述符表中)。 所有资源将存储在用户模式堆中。

另一个概念是根签名。 根签名是应用程序定义的一种绑定约定,着色器使用它来定位所要访问的资源。 根签名可以存储:

  • 描述符表在描述符堆(其中已预定义描述符表的布局)中的索引。
  • 常量,因此应用程序可将用户定义的常量(称为“根常量”)直接绑定到着色器,而无需使用描述符和描述符表。
  • 极少量的描述符(例如,每次绘制都会发生更改的常量缓冲区视图 (CBV))直接存储在根签名中,因此,应用程序无需将这些描述符放入描述符堆。

换言之,根签名可为每次绘制时都会发生更改的少量数据提供性能优化。

Direct3D 12 的绑定设计将此任务与内存管理、对象生存期管理、状态跟踪和内存同步等其他任务相隔离(请参阅与 Direct3D 11 中的绑定模型的区别)。 Direct3D 12 绑定在设计上是低开销的,已针对最常发出的 API 调用进行优化。 它还可以从低端硬件到高端硬件进行缩放,且可以从图形引擎编程的旧方法(更具线性的 Direct3D 11 管道)到新方法(更高的并行度)进行缩放。

资源类型和视图

资源类型与 Direct3D 11 相同,即:

  • Texture1D 和 Texture1DArray
  • Texture2D、Texture2DArray、Texture2DMS、Texture2DMSArray
  • Texture3D
  • 缓冲区(类型化、结构化和原始)

资源视图与 Direct3D 11 类似,只是略有不同,已添加顶点和索引缓冲区视图。

  • 常量缓冲区视图 (CBV)
  • 无序访问视图 (UAV)
  • 着色器资源视图 (SRV)
  • 采样器
  • 渲染器目标视图 (RTV)
  • 深度模板视图 (DSV)
  • 索引缓冲区视图 (IBV)
  • 顶点缓冲区视图 (VBV)
  • 流输出视图 (SOV)

着色器实际上只能看到其中的前四个视图,具体请参阅对着色器可见的描述符堆对着色器不可见的描述符堆

资源绑定控制流

如果只注重于根签名、根描述符、根常量、描述符表和描述符堆,则应用的渲染逻辑流应类似于:

  • 创建一个或多个根签名对象 – 为应用程序所需的每个不同绑定配置各创建一个。
  • 使用着色器和管道所用的根签名对象创建其状态。
  • 创建一个(必要时可创建多个)描述符堆,用于包含每个渲染帧的所有 SRV、UAV 和 CBV 描述符。
  • 在可能的情况下,针对要在多个帧中重复使用的描述符集,使用描述符初始化描述符堆。
  • 对于在渲染的每个帧:
    • 对于每个命令列表:
      • 设置要使用的当前根签名(并在渲染期间根据需要进行更改 – 极少需要更改)。
      • 对于新的视图(例如世界/视图投影),更新某些根签名的常量和/或根签名描述符。
      • 对于要绘制的每个项:
        • 根据需要在描述符堆中定义任何新的描述符,以按对象进行渲染。 对于着色器可见的描述符堆,应用必须确保使用尚未由进行中的渲染使用的描述符堆空间 – 例如,在渲染期间通过描述符堆线性分配空间。
        • 使用指向描述符堆所需区域的指针更新根签名。 例如,一个描述符表可能指向以前初始化的某些静态(不会更改)的描述符,而另一个描述符表可能指向为当前渲染配置的某些动态描述符。
        • 对于按项进行的渲染,更新某些根签名的常量和/或根签名描述符。
        • 为要绘制的项设置与当前绑定的根签名兼容的管道状态(仅当需要更改时)。
        • 绘制
      • 重复(下一项)
    • 重复(下一命令列表)
    • 严格地说,当 GPU 用完不再使用的任何内存时,可将其释放。 如果尚未提交使用这些描述符的其他渲染,则不需要删除描述符对它的引用。 因此,后续的渲染可以指向描述符堆中的其他区域,或者,可以使用有效的描述符覆盖过时的描述符,以重复使用描述符堆空间。
  • 重复(下一帧)

请注意,其他描述符类型、呈现目标视图 (RTV) 、深度模具视图 (DSV) 、索引缓冲区视图 (IBV) 、顶点缓冲区视图 (VBV) (SOV) 流输出视图的管理方式不同。 在记录命令列表期间,驱动程序会处理每次绘制绑定的描述符集的版本控制(类似于硬件/驱动程序对根签名绑定进行版本控制)。 这不同于着色器可见的描述符堆的内容,对于后者,应用程序必须通过堆进行手动分配,因为每次绘制它都引用不同的描述符。 由应用程序对着色器可见的堆内容进行版本控制,因为它允许应用程序执行此类操作:重复使用不会更改的描述符、使用大型的静态描述符集、使用着色器索引(例如按材料 ID)从描述符堆中选择要使用的描述符,或者对不同的描述符集使用各种技术的组合。 对于其他描述符类型(RTV、DSV、IBV、VBV、SOV),硬件无法处理这种灵活性。

子分配

在 Direct3D 12 中,应用可对内存管理进行低级别的控制。 在早期版本的 Direct3D(包括 Direct3D 11)中,每个资源有一个分配。 在 Direct3D 12 中,应用可以使用 API 来分配大于任一对象所需量的大型内存块。 此操作完成后,应用可以创建描述符,以指向该大型内存块的区段。 决定要将哪个对象放在哪个位置(将较小的块放在大块中)的这种过程称为“子分配”。 具有此功能的应用的优势是能够有效利用计算资源和内存。 例如,资源重命名看上去已过时。 如果提供此技术,应用可以使用围栏来确定何时使用和不使用特定的资源,方法是在命令列表需要使用该特定资源时,对命令列表的执行应用围栏隔离。

释放资源

在释放绑定到管道的任何内存之前,GPU 必须已用完该内存。

等待帧渲染也许是确定 GPU 已完成的最粗糙方法。 在更精细的粒度上,可以再次使用围栏 — 将某个命令记录到要跟踪其完成进度的命令列表时,紧接在该命令的后面插入一个围栏。 然后,可以针对该围栏执行各种同步操作。 提交等到指定的隔离已在 GPU 上通过的新工作(命令列表),指示它前面的所有工作已完成,或者,可以在围栏已通过时请求引发 CPU 事件(应用可以在休眠的线程上等待)。 在 Direct3D 11 中,这是 EnqueueSetEvent()。