相比于 D3D11,在 D3D12 中使用交换链会增加复杂性。 可能仅翻转模型 [1] 交换链适用于 D3D12。 它会涉及到许多必选参数,比如缓冲器数量、动态帧数量、显示 SyncInterval,以及是否使用 WaitableObject。 我们内部开发了这款应用,以帮助了解不同参数之间的交互,发现最实用的参数组合。
该应用包含渲染帧通过显示列队从 CPU 到 GPU 再到显示屏这一过程的交互可视化。 所有参数都可以实时调整。 通过屏幕统计数据显示器可以观察调整后对帧速率和延迟产生的影响。
图 1:含有注释的示例应用截屏
交换链参数
这些参数用于调查 D3D12 交换链。
参数 | 描述 |
Fullscreen | 窗口覆盖屏幕时为真值(例如,无边框窗口模式)。 注: 不同于面向互斥模式的 SetFullscreenState。 |
Vsync | 控制 Present() 函数的 SyncInterval 参数。 |
Use Waitable Object | 交换链是否通过 DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT 创建而成 |
Maximum Frame Latency | 该值传递至 SetMaximumFrameLatency。 如果未启用 “Use Waitable Object”,则忽略。 如果没有可等待对象,Maximum Frame Latency 的有效值为 3。 |
BufferCount | 该值在 DXGI_SWAP_CHAIN_DESC1::BufferCount 中加以规定。 |
FrameCount | 等待最早的一个游戏帧完成之前在 CPU 上生成的 “game frames” 的最大值。 game frame(游戏帧)是一种用户数据结构,D3D12 栅栏负责追踪它在 GPU 上的完成情况。 多个游戏帧可以指向同一个交换链缓冲区。 |
其他参数
这些参数也纳入至了交换链调查。 不过它们的值是固定的。 下面我们通过列表来解释这些值为何固定不变。
参数 | 描述 |
Exclusive mode | 由于显示统计数据机制不在互斥模式下运行,因此示例从不调用 SetFullscreenState。 |
SwapEffect | 该值在 DXGI_SWAP_CHAIN_DESC1::SwapEffect 中加以规定。 通常设为 DXGI_SWAP_EFFECT_FLIP_DISCARD。 DISCARD 是最不常规定的行为,它为操作系统提供最大的灵活性来优化演示。 另外唯一的一个选项 DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL 仅用于涉及重新使用之前显示的映像区的操作(例如,滚动矩形)。 |
了解 BufferCount 和 FrameCount
BufferCount 指交换链中缓冲区的数量。 借助翻转模型交换链,操作系统可能针对显示时的整个同步间隔锁定 1 个缓冲区,因此,供应用写入的缓冲区数量实际上是 BufferCount-1。 如果 BufferCount = 2,表示操作系统在下一个同步期间发布第 2 个缓冲区之前,只有 1 个供写入的缓冲区。 这样会导致一种后果,即帧速率无法超越刷新率。
当 BufferCount >= 3,表示至少有 2 个供应用写入的缓冲区,相互之间可以循环(假设 SyncInterval=0),这样帧速率将是无限的。
FrameCount 指动态 “渲染帧” 的最大数量,其中渲染帧指 GPU 必须执行渲染的资源和缓冲区的集。 如果 FrameCount = 1,表示 CPU 不会构建下一个渲染帧,直到前一个彻底处理完。 这意味着,FrameCount 必须至少是 2,这样 CPU 和 GPU 才能并行运行。
最长帧延迟,以及 “Waitable Object” 如何降低延迟
延迟指帧从生成到显示于屏幕期间所消耗的时间。 因此,为了将间隔固定(同步)的显示系统中的延迟降到最低,帧生成后必须尽快显示。
排队显示操作的最大数称为“最长帧延迟”。 如果应用在达到该限值后尝试排列其他显示,Present() 将阻止这种排列,直到前一个帧完成显示。
渲染线程用于阻止 Present 函数的时间均发生在帧生成和帧显示期间,因此,这样会直接延长帧显示的延迟。 使用“可等待对象”可以消除这种延迟。
从概念上来说,“可等待对象”可视作初始化为“最长帧延迟”的旗语 (semaphore)。如果显示从“显示队列”中删除,该旗语会收到信号。 如果应用在渲染之前等待旗语收到信号,显示队列就不会填满(Present 也将不会阻止),这样延迟就会消除。
最佳参数预设
调查结果根据您的具体要求,提供了 3 个不同的 “最佳” 值。 我们认为这些参数组合最适合于游戏。
游戏模式
- 同步启用
- 3 个缓冲区,2 帧
- 可等待对象,最长帧延迟 2
游戏模式很好地平衡了延迟和吞吐量。
经典游戏模式
- 同步启用
- 3 个缓冲区,3 帧
- 未使用可等待对象
在 D3D11 带有 3 次缓冲的情况下,这种模式会隐式地发生,故称为 “经典” 模式。 经典游戏模式侧重于吞吐量。 额外帧排列能够更好地利用峰值,但要以延迟为代价。
最低延迟
- 同步启用
- 2 个缓冲区,1 帧
- 可等待对象,最长帧延迟 1
在不使用 VR 风格的同步竞态技巧的情况下,延迟绝对是最低的。 如果应用错过同步,帧速率会立即下降至刷新率的 1/2。 CPU 和 GPU 将以串行,而非并行的方式运行。
应用版本
源代码包含一个项目文件,以将示例构建为 Windows 10 通用应用。 Direct3D 代码的唯一区别是调用 CreateSwapChainForCoreWindow,而非 CreateSwapChainForHWND。
如果想在无需编译的情况下试用应用版本,请点击此处的链接进入 Windows 应用商店页面:https://www.microsoft.com/store/apps/9NBLGGH6F7TT
参考资料
1 - “DXGI 翻转模型。”https://msdn.microsoft.com/zh-cn/library/windows/desktop/hh706346%28v=vs.85%29.aspx
2 - “借助 DXGI 1.3 交换链降低延迟。”https://msdn.microsoft.com/zh-cn/library/windows/apps/dn448914.aspx
3 - DirectX 12:Windows 10 中的演示模式。https://www.youtube.com/watch?v=E3wTajGZOsA
4 - DirectX 12:非节流帧速率。https://www.youtube.com/watch?v=wn02zCXa9IU