65.9K
CodeProject 正在变化。 阅读更多。
Home

Microsoft DirectX 12 中资源绑定的性能考量

starIconstarIconemptyStarIconemptyStarIconemptyStarIcon

2.00/5 (1投票)

2015年10月23日

CPOL

13分钟阅读

viewsIcon

28056

本文介绍如何选择不同的资源绑定机制,以便在特定的英特尔 GPU 上高效运行应用程序。

随着 Windows* 10 于 7 月 29 日的发布以及第六代英特尔® 酷睿™ 处理器系列(代号 Skylake)的推出,我们现在可以更深入地研究专门针对英特尔® 平台的资源绑定。

上一篇文章“Microsoft DirectX* 12 中的资源绑定简介”介绍了 DirectX 12 中的新资源绑定方法,并得出结论:面对所有这些选择,挑战在于为目标 GPU、资源类型及其更新频率选择最理想的绑定机制。

本文介绍如何选择不同的资源绑定机制,以便在特定的英特尔 GPU 上高效运行应用程序。

工具

要开发 DirectX 12 游戏,您需要以下工具

  • Windows 10
  • Visual Studio* 2013 或更高版本
  • DirectX 12 SDK 随 Visual Studio 一起提供
  • 支持 DirectX 12 的 GPU 和驱动程序

概述

描述符是描述 GPU 对象的数据块,格式由 GPU 特定定义。DirectX 12 提供了以下描述符,这些描述符在 DirectX 11 中以前称为“资源视图”。

  • 常量缓冲器视图 (CBV)
  • 着色器资源视图 (SRV)
  • 无序访问视图 (UAV)
  • 采样器视图 (SV)
  • 渲染目标视图 (RTV)
  • 深度模具视图 (DSV)
  • 以及其他

这些描述符或资源视图可视为 GPU 前端使用的结构(也称为块)。描述符的大小约为 32-64 字节,包含纹理尺寸、格式和布局等信息。

描述符存储在描述符堆中,描述符堆表示内存中一系列结构。

描述符表包含指向描述符堆中位置的偏移量。通过根签名使其可用,从而将连续的描述符范围映射到着色器槽。此根签名还可以包含根常量、根描述符和静态采样器。

图 1. 描述符、描述符堆、描述符表、根签名。

图 1 展示了描述符、描述符堆、描述符表和根签名之间的关系。

图 1 描述的代码如下所示:

// the init function sets the shader registers
// parameters: type of descriptor, num of descriptors, base shader register
// the first descriptor table entry in the root signature in
// image 1 sets shader registers t1, b1, t4, t5
// performance: order from most frequent to least frequent used
D3D12_DESCRIPTOR_RANGE Param0Ranges[3]; 
Param0Ranges[0].Init(D3D12_DESCRIPTOR_RANGE_SRV, 1, 1); // t1 Param0Ranges[1].Init(D3D12_DESCRIPTOR_RANGE_CBV, 1, 1); // b1 Param0Ranges[2].Init(D3D12_DESCRIPTOR_RANGE_SRV, 2, 4); // t4-t5  

// the second descriptor table entry in the root signature
// in image 1 sets shader registers u0 and b2
D3D12_DESCRIPTOR_RANGE Param1Ranges[2]; Param1Ranges[0].Init(D3D12_DESCRIPTOR_RANGE_UAV, 1, 0); // u0 Param1Ranges[1].Init(D3D12_DESCRIPTOR_RANGE_CBV, 1, 2); // b2  

// set the descriptor tables in the root signature
// parameters: number of descriptor ranges, descriptor ranges, visibility
// visibility to all stages allows sharing binding tables
// with all types of shaders
D3D12_ROOT_PARAMETER Param[4]; 
Param[0].InitAsDescriptorTable(3, Param0Ranges, D3D12_SHADER_VISIBILITY_ALL); 
Param[1].InitAsDescriptorTable(2, Param1Ranges, D3D12_SHADER_VISIBILITY_ALL); // root descriptor
Param[2].InitAsShaderResourceView(1, 0); // t0
// root constants
Param[3].InitAsConstants(4, 0); // b0 (4x32-bit constants)

// writing into the command list
cmdList->SetGraphicsRootDescriptorTable(0, [srvGPUHandle]); 
cmdList->SetGraphicsRootDescriptorTable(1, [uavGPUHandle]);
cmdList->SetGraphicsRootConstantBufferView(2, [srvCPUHandle]);
cmdList->SetGraphicsRoot32BitConstants(3, {1,3,3,7}, 0, 4);

上面的源代码设置了一个包含两个描述符表、一个根描述符和一个根常量的根签名。代码还表明,根常量没有间接寻址,而是直接通过 `SetGraphicsRoot32bitConstants` 调用提供。它们直接路由到着色器寄存器;没有实际的常量缓冲器、常量缓冲器描述符或绑定发生。根描述符只有一个级别的间接寻址,因为它们存储指向内存的指针(descriptor->memory),而描述符表有两个级别的间接寻址(descriptor table -> descriptor-> memory)。

描述符根据其类型(例如 SV 和 CBV/SRV/UAV)存储在不同的堆中。这是因为不同硬件平台上描述符类型的尺寸差异很大。对于每种类型的描述符堆,应只分配一个堆,因为更改堆可能成本很高。

总的来说,DirectX 12 允许一次性分配超过一百万个描述符,足以满足整个游戏关卡的需求。虽然之前的 DirectX 版本自行处理驱动程序中的分配,但使用 DirectX 12 可以避免运行时进行任何分配。这意味着描述符的任何初始分配都可以从性能“方程”中移除。

注意:对于第三代英特尔® 酷睿™ 处理器(代号 Ivy Bridge)/第四代英特尔® 酷睿™ 处理器系列(代号 Haswell)以及 DirectX 11 和 Windows 显示驱动程序模型 (WDDM) 1.x 版本,资源会根据命令缓冲器中引用的资源动态映射到内存中,并通过页表映射操作完成。这样可以避免数据复制。动态映射很重要,因为这些架构仅为 GPU 提供 2 GB 内存(英特尔® 至强® E3-1200 v4 产品系列处理器(代号 Broadwell)提供更多内存)。
对于 DirectX 12 和 WDDM 2.x 版本,由于资源在创建时必须分配静态虚拟地址,因此不再可能根据需要将资源重新映射到 GPU 虚拟地址空间,并且在创建后资源的虚拟地址无法更改。即使资源已从 GPU 内存中“逐出”,它仍会保留其虚拟地址,以便在以后再次变为常驻时使用。
因此,Ivy Bridge/Haswell 中 2 GB 的总可用内存可能成为一个限制因素。

如上一篇文章所述,应用程序一个非常合理的结果可能是所有绑定类型的组合:根常量、根描述符、用于在发出绘制调用时即时收集的描述符的描述符表,以及大型描述符表的动态索引。

不同的硬件架构在使用根常量和根描述符的集合与使用描述符表之间会表现出不同的性能权衡。因此,根据目标硬件平台调整根参数和描述符表之间的比例可能是必要的。

预期的变化模式

要理解哪些类型变化会产生额外成本,我们必须首先分析游戏引擎通常如何更改数据、描述符、描述符表和根签名。

让我们从所谓的常量数据开始。大多数游戏引擎通常将所有常量数据存储在“系统内存”中。游戏引擎会在 CPU 可访问的内存中更改数据,然后在帧的稍后,将整个常量数据块复制/映射到 GPU 内存中,然后通过常量缓冲器视图或根描述符由 GPU 读取。

如果常量数据通过 `SetGraphicsRoot32BitConstants()` 作为根常量提供,则根描述符中的条目不会改变,但数据可能会改变。如果它通过 CBV(即描述符)然后通过描述符表提供,则描述符不会改变,但数据可能会改变。

如果我们为双缓冲或三缓冲渲染等场景需要多个常量缓冲器视图,则根签名中的 CBV 或描述符可能会每帧发生更改。

对于纹理数据,预期纹理在启动时就已在 GPU 内存中分配。然后将创建一个 SV(即描述符),存储在描述符表或静态采样器中,然后引用到根描述符中。此后,数据、描述符或静态采样器均不更改。

对于动态数据,例如更改纹理或缓冲器数据(例如,包含渲染的本地化文本的纹理、动画顶点缓冲器或程序生成的网格),我们会分配渲染目标或缓冲器,提供 RTV 或 UAV(它们是描述符),然后这些描述符可能从此不再更改。渲染目标或缓冲器中的数据可能会更改。

如果我们为双缓冲或三缓冲渲染等场景需要多个渲染目标或缓冲器,则根签名中的描述符可能会每帧发生更改。

对于以下讨论,如果绑定资源的变化执行了以下操作,则认为该变化很重要:

  • 更改/替换描述符表中的描述符,例如上面描述的 CBV、RTV 或 UAV。
  • 更改根签名中的任何条目。

Haswell/Broadwell 上的描述符表中的描述符

在基于 Haswell/Broadwell 的平台上,更改根签名中的一个描述符表的成本相当于更改所有描述符表。更改一个参数意味着硬件必须复制(版本化)当前所有参数。根签名中的根参数数量是硬件在任何子集更改时必须版本化的数据量。

注意:DirectX 12 中的所有其他类型内存,如描述符堆、缓冲器资源等,都不会被硬件版本化。

换句话说,更改所有参数的成本与仅更改一个参数的成本大致相同(参见 [Lauritzen] 和 [MSDN])。不更改任何内容仍然是最便宜的,但不太有用。

注意:其他硬件,例如在快速/慢速(溢出)根参数存储之间进行划分的硬件,只需要版本化参数发生更改的内存区域——无论是快速区域还是溢出区域。

在 Haswell/Broadwell 上,更改描述符表会产生额外成本,这是由于硬件中绑定表的大小有限。

在这些硬件平台上,描述符表使用“绑定表”硬件。每个绑定表条目是单个 DWORD,可以视为指向描述符堆的偏移量。64 KB 的环形缓冲区可以存储 16,384 个绑定表条目。

换句话说,每个绘制调用的内存消耗量取决于在描述符表中索引并在根签名中引用的描述符总数。

如果绑定表条目的内存不足 64 KB,驱动程序将分配另一个 64 KB 的绑定表。这些表之间的切换会导致管道停顿,如图 2 所示。

图 2. 管道停顿(图片由 Andrew Lauritzen 提供)。

例如,一个根签名在一个描述符表中引用 64 个描述符。停顿将每 16,384 / 64 = 256 个绘制调用发生一次。

由于更改根签名被认为是廉价的,因此拥有多个具有少量描述符的描述符表的根签名比拥有具有大量描述符的描述符表的根签名更有利。

因此,在 Haswell/Broadwell 上,最好将描述符表中引用的描述符数量保持在尽可能低的水平。

这对渲染器设计意味着什么?使用更多具有更少描述符的描述符表,因此更多的根签名,应该会增加管道状态对象 (PSO) 的数量,因为随着根签名数量的增加,由于它们之间的一对一关系,PSO 的数量也需要增加。

拥有更多的管道状态对象可能会导致更多的着色器,在这种情况下,这些着色器可能更具针对性,而不是提供更广泛功能的更长着色器,这通常是推荐的做法。

Haswell/Broadwell 上的根常量/根描述符

与更改一个描述符表成本与更改所有描述符表成本相同一样,更改一个根常量或根描述符等同于更改所有这些(参见 [Lauritzen])。

根常量是通过“推送常量”实现的,它是一个硬件用于预填充执行单元 (EU) 寄存器的缓冲器。由于 EU 线程启动时值立即可用,因此将常量数据存储为根常量可能是一种性能优势,而不是将其与描述符表一起存储。

根描述符也通过“推送常量”实现。它们只是作为常量传递给着色器的指针,通过通用内存路径读取数据。

Haswell/Broadwell 上的描述符表与根常量/根描述符的比较

既然我们已经了解了描述符表、根常量和根描述符的实现方式,我们就可以回答本文的主要问题:哪种方法更优?由于绑定表硬件的尺寸有限且可能导致跨越此限制而产生停顿,因此在 Haswell/Broadwell 硬件上,更改根常量和根描述符的成本预计会更低,因为它们不使用绑定表硬件。对于根描述符和根常量,如果数据在每次绘制调用时都会更改,则尤其推荐这种方法。

Haswell/Broadwell 上的静态采样器

如上一篇文章所述,可以在根签名中或直接在着色器中使用 HLSL 根签名语言定义采样器。这些称为静态采样器。

在 Haswell/Broadwell 硬件上,驱动程序会将静态采样器放置在常规采样器堆中。这等同于手动将它们放入描述符中。其他硬件在着色器寄存器中实现采样器,因此静态采样器可以直接编译到着色器中。

总的来说,静态采样器在许多平台上都会带来优势,因此使用它们没有坏处。在 Haswell/Broadwell 硬件上,如果通过增加描述符表中描述符的数量,我们可能会更频繁地遇到管道停顿,因为描述符表硬件只有 16,384 个插槽。

这是 HLSL 中静态采样器的语法

StaticSampler( sReg,
               [ filter = FILTER_ANISOTROPIC, 
               addressU = TEXTURE_ADDRESS_WRAP,
               addressV = TEXTURE_ADDRESS_WRAP,
               addressW = TEXTURE_ADDRESS_WRAP,
               mipLODBias = 0.f,     maxAnisotropy = 16,
               comparisonFunc = COMPARISON_LESS_EQUAL,
               borderColor = STATIC_BORDER_COLOR_OPAQUE_WHITE,
               minLOD = 0.f, maxLOD = 3.402823466e+38f,
               space = 0, visibility = SHADER_VISIBILITY_ALL ])

大多数参数不言自明,因为它们与 C++ 级别的用法相似。主要区别在于边框颜色:在 C++ 级别,它提供全彩色范围,而在 HLSL 级别,则限于纯白色/黑色和透明黑色。静态着色器的示例为

StaticSampler(s4, filter=FILTER_MIN_MAG_MIP_LINEAR)

Skylake

Skylake 允许在一个描述符表中动态索引整个描述符堆(约 100 万个资源)。这意味着一个描述符表可能足以索引所有可用的描述符堆内存。

与以前的架构相比,不再需要频繁更改根签名中的描述符表条目。这意味着可以减少根签名的数量。显然,不同的材质需要不同的着色器,因此需要不同的 PSO。但是这些 PSO 可以引用相同的根签名。

现代渲染引擎使用的着色器比 DirectX 9 和 11 时代的要少,以便避免更改着色器和附加状态的成本,减少根签名和 PSO 的数量是有利的,应该能在任何硬件平台上提高性能。

结论

针对 Haswell/Broadwell 和 Skylake,开发高性能 DirectX 12 应用程序的建议取决于底层平台。对于 Haswell/Broadwell,描述符表中的描述符数量应保持较低,而对于 Skylake,建议保持此数量较高并减少描述符表的数量。

为了实现最佳性能,应用程序程序员可以在启动时检查硬件类型,然后选择最高效的资源绑定模式。(有一个 GPU 检测示例,展示了如何在 https://software.intel.com/en-us/articles/gpu-detect-sample/ 检测不同的英特尔® 硬件架构。)资源绑定模式的选择将影响系统着色器的编写方式。

关于作者

Wolfgang 是 Confetti 的首席执行官。Confetti 是一个专注于前沿实时图形研究的智囊团,也是视频游戏和电影行业的服务提供商。在与他人共同创立 Confetti 之前,Wolfgang 在 Rockstar 的核心技术组 RAGE 担任了四年多的首席图形程序员。他是 *ShaderX* 和 *GPU Pro* 图书系列的创始人兼编辑,是微软 MVP,撰写了多本关于实时渲染的书籍和文章,并定期为全球网站和会议贡献内容。他编辑的一本书 *ShaderX4* 于 2006 年荣获 Game Developer Front Line 奖。Wolfgang 活跃于行业内的多个咨询委员会,其中包括微软的 DirectX 12 图形咨询委员会。他是推动游戏行业的若干未来标准的重要贡献者。您可以在 Twitter 上找到他 @wolfgangengel。Confetti 的网站是 www.conffx.com

致谢

感谢本文审稿人

  • Andrew Lauritzen
  • Robin Green
  • Michal Valient
  • Dean Calver
  • Juul Joosten
  • Michal Drobot

参考资料和相关链接

** 性能测试中使用的软件和工作负载可能已针对仅在英特尔微处理器上的性能进行了优化。诸如 SYSmark* 和 MobileMark* 等性能测试均使用特定的计算机系统、组件、软件、操作和功能进行测量。任何这些因素的更改都可能导致结果有所不同。您应咨询其他信息和性能测试,以帮助您全面评估您打算购买的产品,包括该产品与其他产品结合使用时的性能。

© . All rights reserved.