FidelityFX 帧插值交换链 1.1.3
目录
引言
FrameInterpolationSwapChain 实现 IDXGISwapChain4 和 VkSwapchainKHR 接口,提供了一种简单的方式来处理帧插值和帧率控制所需的工作负载的分派。
尽管此实现可能不适用于所有引擎或应用程序,但它的设计目的是提供一种简单的方式来集成 FSR3 帧生成,使其(几乎)对底层应用程序透明。
描述
从应用程序的角度来看,FrameInterpolationSwapChain 可以作为 DXGI 或 Vulkan 交换链的替代品,其行为应相似。
当禁用帧生成时,主要区别在于 Present 操作比直接使用交换链的成本稍高(需要额外的表面复制)。
在这种情况下,帧插值交换链仍然支持处理 UI 组成,因此应用程序在禁用帧插值时无需以不同方式处理其 UI。
FrameInterpolationSwapChain 内部将创建 2 个额外的 CPU 线程
- 第一个线程用于不让应用程序在等待 GPU 插值完成的同时发生停滞。之后,该线程将获取当前 CPU 时间并计算帧率控制信息。
- 第二个线程负责分派 UI 组成的 GPU 工作负载(如果需要,会调用回调函数)以及控制非插值帧的
Present操作。
集成
FrameInterpolationSwapChain 应按照 FidelityFX API 的描述,并在 组合 FSR 集成文档 中所述进行集成。
本节的其余部分将描述未被 FidelityFX API 支持的平台上的集成。
DirectX 12 的 FrameInterpolationSwapChain 实现 IDXGISwapChain4 接口,因此一旦创建,它的行为就像一个普通的交换链。
创建可以通过调用 ffxReplaceSwapchainForFrameinterpolationDX12 来替换现有交换链,或者通过调用 ffxCreateFrameinterpolationSwapchainDX12 或 ffxCreateFrameinterpolationSwapchainForHwndDX12 来创建,这类似于调用 CreateSwapChain 或 CreateSwapChainForHwnd。
Vulkan 的 FrameInterpolationSwapChain 提供了一组替换交换链功能的接口,这些接口可以像普通的 VkSwapchainKHR 一样使用。
创建可以通过调用 ffxReplaceSwapchainForFrameinterpolationVK 来完成,如果存在,它将替换现有交换链或创建新交换链。调用 ffxGetSwapchainReplacementFunctionsVK 将提供 Vulkan 交换链函数的替换。
录制和分派帧插值工作负载
FrameInterpolationSwapchain 被设计为独立于 FfxOpticalFlow 或 FfxFrameInterpolation 接口。为了实现这一点,它不直接与这些接口交互。可以通过以下两种方式将帧插值工作负载提供给 FrameInterpolationSwapchain:
- 在
FfxFrameGenerationConfig中提供一个回调函数(frameGenerationCallback)。在游戏线程调用::Present时,FrameInterpolationSwapChain将调用此函数,如果启用了帧插值,以记录包含帧插值工作负载的命令列表。 - 调用
ffxGetFrameinterpolationCommandlist{DX12,VK}(FfxSwapchain, FfxCommandList&)从FrameInterpolationSwapchain获取命令列表,并将帧插值工作负载记录到其中。在这种情况下,命令列表将在调用Present时执行。
命令列表可以在调用 Present 的同一命令队列上执行,也可以在异步计算队列上执行。
- 同步执行在应用程序调用 upscale 但随后决定不调用
Present某个帧的情况下,更具弹性。 - 异步执行可能会带来更高的性能,具体取决于硬件以及与帧插值工作负载并行运行的工作负载。
无论哪种方式,UI 组成和 Present 操作都将在第二个图形队列上执行,以避免 UI 组成被限制为计算,并允许驱动程序在准备下一帧时调度 Present 调用。
注意:为确保 Present 操作能够按照 FSR3 帧率控制逻辑的预期时间执行,避免微小卡顿,并确保显示器的 VRR 响应良好,建议确保帧由多个命令列表组成。
UI 组成
在使用帧插值时,强烈建议特别注意 UI,因为游戏运动矢量引起的失真在 3D 场景中可能很难察觉,但在 UI 中会显著影响文本的可读性,并导致非常明显的伪影,尤其是在 UI 的任何直线、硬边缘上。
为了对抗任何伪影并保持 UI 的美观和可读性,FSR3 在 FrameInterpolationSwapChain 中提供了 3 种处理 UI 组成的方式。
- 注册一个回调函数,该函数将在后备缓冲区之上渲染 UI。此函数将在每个显示的后备缓冲区(插值和真实)上被调用,因此它允许应用程序以显示速率渲染 UI 动画,或者为发送到显示器的每个帧以不同方式应用诸如胶片颗粒之类的效果。但是,这种方法显然会对性能产生一定影响,因为 UI 需要渲染两次,因此应注意仅在 UI 回调中记录小型工作负载。
- 将 UI 渲染到单独的表面,以便它可以与最终的后备缓冲区进行 alpha 混合。这样,UI 就可以应用到插值和真实的后备缓冲区,而不会出现任何失真。
- 除了最终的后备缓冲区外,还向
FrameInterpolationSwapChain提供一个包含无 HUD 场景的表面。在这种情况下,帧插值着色器将检测帧中的 UI 区域,并抑制这些区域的失真。
可等待对象
建议游戏使用 GetFrameLatencyWaitableObject 获取一个可等待对象,然后使用该对象防止 CPU 运行得比 GPU 超前太多。当 VSync 开启且显示器刷新率较低时,这一点尤其重要,因为 GPU 的渲染速率可能远低于 CPU 的提交速率。或者,应用程序可以使用一个设置为显示器刷新率一半的帧率限制器。
帧率控制和呈现
FrameInterpolationSwapchain 会自动处理帧率控制。由于 Windows 不是一个实时操作系统,并且可变刷新率显示器对定时不精确很敏感,FSR3 被设计成使用忙等循环,以实现最佳的定时行为。
启用帧生成后,帧的渲染时间可能差异很大。插值帧的工作负载可能比应用程序渲染的帧(“真实”帧)小得多。因此,正确地控制帧的呈现以确保流畅的体验非常重要。目标是让每一帧都显示相等的时间。
呈现和帧率控制是通过两个独立的 CPU 线程完成的,与主渲染循环分开。一个高优先级的帧率控制线程会跟踪平均帧时间(包括 UI 组成时间),并计算目标呈现时间差。它还会等待 GPU 工作完成,以避免在 CPU 端 Present 调用后发生长时间的 GPU 等待。
为了防止任何帧时间峰值对帧率控制产生过大的影响,使用多个帧的移动平均值来估算帧时间。
一个 Present 线程分派生成帧的帧组成工作,等待自上次呈现以来已过计算的 Present 时间差,然后呈现生成帧。它会对真实帧重复此操作。
应用程序应确保渲染帧率略低于所需输出帧率的一半。启用 VSync 时,渲染性能将自动限制为显示器最大刷新率的一半。
建议应用程序创建的 GPU 队列使用普通优先级,以便插值工作可以以更高的优先级进行调度。此外,开发人员应注意,与插值和组成并行运行的命令列表应简短(执行时间方面),以便能够在精确的时间调度呈现。
预期行为
为了进一步说明帧率控制方法及其背后的原理,以下部分将概述不同场景下的预期行为。我们将根据插值后的帧率以及显示器使用固定还是可变刷新率进行区分。
固定刷新率
已启用 VSync
在这种情况下,禁用撕裂,并且每一帧至少显示一个同步间隔。呈现与显示器的垂直消隐周期(“vsync”)同步。这可能会导致显示时序不均匀,并可能增加输入延迟(最多一个刷新周期)。
在图中,第一个真实帧在垂直消隐间隔之后一点点呈现,导致前一个插值帧显示了两个刷新间隔,输入延迟比即时显示有所增加。
已禁用 VSync
在这种情况下,可能会发生撕裂。呈现与显示器不同步。这样做的好处是输入延迟比低帧率时要低。
可变刷新率
本节适用于支持可变刷新率 (VRR) 技术(如 AMD FreeSync、NVIDIA G-SYNC® 和 VESA AdaptiveSync)的显示器和 GPU 组合。
显示刷新之间的时序由可变刷新率窗口决定。两次刷新之间的 Delta 时间可以在窗口内的任何时间。例如,如果 VRR 窗口为 64-120Hz,则 Delta 时间必须在 8.33 毫秒到 15.625 毫秒之间。如果 Delta 超出此窗口,则可能会发生撕裂。
如果在窗口内没有新的 Present 操作发生,则将再次显示上一帧。
VRR 窗口内的插值帧率
可变刷新率窗口通常不会超过显示器报告的本机刷新率,因此在这种情况下将禁用撕裂。
VRR 窗口外的插值帧率
如果帧率低于 VRR 窗口的下限,预期行为与固定刷新率显示器帧率低于刷新率的情况相同(如上文所述)。
如果帧率高于 VRR 窗口的上限,预期行为与固定刷新率显示器帧率高于刷新率的情况相同(如上文所述)。
附加信息
FrameInterpolationSwapChain 创建的资源列表
- 两个 CPU 工作线程。其中一个将在插值帧和真实帧的
Present操作之间进行部分忙等,以精确地控制Present的时间。 - 一个异步计算队列(仅当
FFX_FSR3_ENABLE_ASYNC_WORKLOAD_SUPPORT在 FSR3 上下文创建时设置为 true,并且allowAsyncWorkloads在FfxFrameGenerationConfig中为 true 时使用)。 - 一个异步
Present队列。此队列将用于执行 UI 组成工作负载和Present。 - 用于插值和 UI 组成工作负载的命令列表、分配器和栅栏集。
- 将后备缓冲区
blit到交换链并组成 UI 所需的 GPU 资源(如果未使用回调)。 - 附加到实际游戏窗口的交换链。
FrameInterpolationSwapchain 的设计旨在最大限度地减少运行时动态分配。
- 该类的系统内存使用量在交换链的生命周期内是恒定的,未使用 STL。
- DirectX 或 Vulkan 资源在首次使用时创建,并保持可用以供重用。