硬件翻转队列

本文介绍从 Windows 11 (WDDM 3.0) 开始支持的硬件翻转队列功能。 硬件翻转队列功能允许将多个未来帧提交到显示控制器队列。 当显示控制器正在处理多个排队帧时,CPU 和 部分 GPU 可以切换到较低的功耗状态,从而提高在有能力的硬件上视频播放场景的功耗效率。

WDDM 3.0 之前的翻转队列模型

许多新式显示控制器支持对按顺序显示的多个帧排队的能力。 从 WDDM 2.1 开始,OS 支持将在下一个 VSync 中显示的多个未完成的翻转覆盖请求。 显示微型端口驱动程序 (KMD) 通过 DXGK_DRIVERCAPS 中的 MaxQueuedMultiPlaneOverlayFlipVSync 值来表示这种支持。 此功能对于减少高帧速率游戏方案中的延迟非常有用,在这种方案中,多个帧以间隔 0 依次呈现,目的是只显示最近的帧。

在视频播放方案中,要按顺序显示的多个未来帧的内容是预先已知的,并且可以排队到 GPU。 这种提前排队允许 CPU 在处理排队帧时进入低功率状态,从而节省大量电源。 但是,在 WDDM 3.0 之前,OS 没有任何机制可以提交多个帧,这些帧需要在屏幕上停留至少一个 VSync 间隔,而无需进一步的 CPU 干预。 基本硬件翻转队列一节介绍了一种解决方案,该解决方案使 CPU 能够进入低功耗状态,并将排队的帧处理卸载到 GPU。

在 WDDM 3.0 之前的游戏方案中,GPU 完成将场景呈现到交换链后缓冲区后,会有一个到 CPU 的往返,以便将请求提交以将帧内容呈现到屏幕。 对于接近 VSync 完成的繁重 GPU 工作负载,这种往返可能会导致帧延迟并错过预期目标时间,从而导致可观察到的帧故障。 高级硬件翻转队列一节介绍了一种机制,用于避免这种 CPU 往返,并以非常低的延迟将完成的帧呈现给屏幕。 高级硬件翻转队列要求同时具有基本硬件翻转队列和 GPU 硬件计划阶段 2 功能。

基本硬件翻转队列

下图演示了呈现三个帧的情况,每个帧在屏幕上停留一个 VSync 间隔。

示意图显示了三个帧每个在屏幕上停留一个 VSync 间隔。

示意图中的填充图案显示了 Dxgkrnl 软件翻转队列处理和应用程序线程必须唤醒并执行 CPU 工作的时间。 在每个 VSync 上,显示控制器必须向 OS 发出完成翻转的 CPU 通知,并且 OS 必须提交下一个翻转请求。 应用程序还必须在每个 VSync 上唤醒,并查询当前统计信息,以最终了解三帧中的最后一帧何时显示。

从 WDDM 3.0 开始,提供了可以向显示控制器队列提交多个未来帧的硬件翻转队列 DDI。 如前所述,该机制允许 CPU 和部分 GPU 在显示控制器处理多个排队帧时转换到较低功耗状态,从而提高在有能力的硬件上视频播放场景的功耗效率。

下图说明了建议的体系结构。

演示基本硬件翻转队列机制的示意图。

借助硬件翻转队列方法,应用程序和 Dxgkrnl CPU 组件在 v2v4 时间之间的两个 VSync 间隔内处于完全空闲状态,从而使 CPU 能够进入低功耗状态。 只有当应用程序请求等待的帧 N+2 完成时,CPU 才会得到通知。

高级硬件翻转队列

在 WDDM 3.0 之前的游戏方案中,GPU 完成将场景呈现到交换链后缓冲区后,会有一个到 CPU 的往返,以便将请求提交以将帧内容呈现到屏幕。 下图显示了此方案。

描述需要 CPU 往返的帧完成情况的示意图。

如果呈现完成得离 VSync 太近,这种往返的代价可能会导致帧错过目标,如下图所示。

说明由于所需的 CPU 往返而丢失的帧的示意图。

一些显示器控制器本身支持等待条件,允许显示在 GPU 完成呈现帧时提交翻转请求,而无需 CPU 往返。 由于硬件翻转队列可以在没有 CPU 往返的情况下将刚刚完成的帧 N 提交到显示器,因此可以避免错过帧,如下图所示。

显示无需 CPU 往返即可完成帧的示意图。

本文的其余部分讨论基本硬件翻转队列功能。

DDI 支持

添加了以下 DDI 以支持硬件翻转队列功能。

检查功能可用性

硬件翻转队列需要 OS 启用/禁用协商。 支持硬件翻转队列的 KMD 必须在设备启动期间首先调用DXGKCB_QUERYFEATURESUPPORT,其中 FeatureIdDXGK_FEATURE_HWFLIPQUEUE,以确定 OS 是否允许启用硬件翻转队列。

仅当回调成功且启用设置为 TRUE 时,才能使用硬件翻转队列。

KMD 可以在硬件翻转队列启动和试验阶段使用以下示例代码。


DXGKARGCB_QUERYFEATURESUPPORT HwFlipQueueEnabledArgs = {};
HwFlipQueueEnabledArgs.DeviceHandle = DeviceHandle;
HwFlipQueueEnabledArgs.FeatureId = DXGK_FEATURE_HWFLIPQUEUE;
HwFlipQueueEnabledArgs.DriverSupportState = DXGK_FEATURE_SUPPORT_EXPERIMENTAL;

if (!NT_SUCCESS(pDxgkInterface->DxgkCbQueryFeatureSupport(&HwFlipQueueEnabledArgs)) ||
    !HwFlipQueueEnabledArgs.Enabled)
{
    // Disable hardware flip queue because the OS didn't allow it.           
}
else
{
    // Enable hardware flip queue because the OS allowed it.
}

在驱动程序启动期间,即使可以在不启用 GPU 硬件调度的情况下启用硬件翻转队列,这种组合也不被官方支持。 Windows 目前需要启用 GPU 硬件计划,以便在正式发布的驱动程序上启用基本硬件翻转队列。

指示硬件队列功能

MaxHwQueuedFlips 已添加到 DXGK_DRIVERCAPS,以指示硬件翻转队列支持。 如果 OS 如前所述允许硬件翻转队列支持,则支持硬件翻转队列的 KMD 应将 MaxHwQueuedFlips 设置为大于 1 的值。 当 MaxHwQueuedFlips 大于 1 时,KMD 指示显示硬件最多支持 MaxHwQueuedFlips 个未来帧,这些帧可以在 GPU 上为给定的 VidPnSource 排队显示。 OS 将遵循驱动程序提供的对可以提前排队的翻转类型的限制。

HwQueuedFlipCaps 也添加到 DXGK_DRIVERCAPS。 此成员当前保留供系统使用,不应由驱动程序使用。

翻转目标时间和目标时间戳格式

当 OS 向硬件翻转队列提交翻转请求时,它还会发送目标翻转时间。 在达到目标翻转时间后,用户可以看到翻转。

OS 使用从 KeQueryPerformanceCounter 获取的 CPU 时钟计数器单元传递目标帧时间并解释实际帧时间。

提交排队翻转

传递给 KMD 的 DxgkDdiSetVidPnSourceAddressWithMultiPlaneOverlay3 回调函数的 DXGKARG_SETVIDPNSOURCEADDRESSWITHMULTIPLANEOVERLAY3 结构被修改如下,以允许提交排队翻转:

  • 以下三个成员被添加到 OutputFlagsDXGK_SETVIDPNSOURCEADDRESS_OUTPUT_FLAGS 结构中。 有关这些成员的详细信息,请参阅 DxgkDdiSetVidPnSourceAddressWithMultiPlaneOverlay3 重试和失败案例

    • HwFlipQueueDrainNeeded
    • HwFlipQueueDrainAllPlanes
    • HwFlipQueueDrainAllSources
  • 添加了 TargetFlipTime 成员。 TargetFlipTime 描述以 QPC 为单位的目标翻转时间。 当时钟达到这个值时,帧可以被发送到显示器,同时遵循 VSync 和撕裂标志。 如果存在以前排队的挂起翻转,OS 保证对于翻转请求引用的每个 MPO 平面, TargetFlipTime 大于或等于此平面的任何挂起翻转目标时间。 换言之,可以有一系列时间戳相同或不断增加的翻转,但不可能有一个时间戳倒退的序列。

DxgkDdiSetVidPnSourceAddressWithMultiPlaneOverlay3 重试和失败案例

由于挂起的翻转,无法将请求排队到硬件

有几种特殊情况可能会阻止 KMD 在其他翻转请求挂起时对翻转请求进行排队。 在这种情况下,KMD 应从 DxgkDdiSetVidPnSourceAddressWithMultiPlaneOverlay3 返回 STATUS_RETRY,并将HwFlipQueueDrainNeeded 设置为 1。 在受此翻转影响的平面上的所有挂起翻转完成后,一旦达到目标时间,OS 将尝试再次提交翻转请求。

在某些情况下,显示硬件可能需要完成所有平面上的挂起翻转,而不仅仅是传入翻转请求所引用的那些平面。 在这种情况下,HwFlipQueueDrainNeededHwFlipQueueDrainAllPlanes 标志都应设置为 1,并且 KMD 应返回 STATUS_RETRY。

同样,为了重新分配内部资源,显示硬件可能需要完成所有 VidPn 源上的挂起翻转,在这种情况下,必须设置 HwFlipQueueDrainAllSourcesHwFlipQueueDrainNeeded 标志,并且 KMD 应返回 STATUS_RETRY。

此外,KMD 可以向 OS 指示是否应在设备 IRQL(PrePresentNeeded 设置为 0)上完成重新提交,或者 OS 是否应在 PASSIVE_LEVEL(PrePresentNeeded 设置为 1)执行此调用。 如果 KMD 仍然返回 STATUS_RETRY,即使该 VidPnSourceId 上没有更多挂起的翻转,此条件也将被视为无效的参数失败

重要的是 MaxHwQueuedFlips 的值仍反映可排队到 MPO 平面的简单仅地址更改翻转的最大数量。 STATUS_RETRY 机制应用于无法深度排队的更复杂的翻转请求,例如平面配置更改。

无效参数失败

在硬件翻转队列模型中,OS 对失败的翻转请求的处理被重新设计,以实现更好的可调试性。 当 KMD 无法处理翻转请求时,则会从 DxgkDdiSetVidPnSourceAddressWithMultiPlaneOverlay3 返回 STATUS_INVALID_PARAMETER。 根据 OS 设置,OS 将执行以下操作之一:

  • 内核调试器中断和错误检查:这种行为通常会在开发/预发布版本中启用,以便在出现故障时更好地进行调试。
  • 实时内核转储后跟 TDR:零售最终用户行为。

指定 VSync 中断行为

为了在排队翻转方案中实现节能,OS 通常会暂停常规 VSync 中断,以使 CPU 处于低功耗状态。 然而,一些翻转将被标记为需要引发中断,以便应用程序观察一批已完成的 Present 并排队进行进一步的工作。 还有一些情况下,无论是否有挂起的翻转请求,应用程序都会在每次 VSync 中断时请求唤醒。 相反,在完全空闲的系统上,VSync 中断将挂起,直到出现新的演示活动或 VSync 侦听器。

为了处理所有这些情况,引入了以下驱动程序回调和回调结构:

KMD 在 DRIVER_INITIALIZATION_DATA 中提供一个指向其 DxgkDdiSetInterruptTargetPresentId 函数的指针

OS 调用 DxgkDdiSetInterruptTargetPresentId 来指定目标 PresentId,该目标 PresentId 会在相应翻转完成后引发 VSync 中断。 它在设备中断级别 (DIRQL) 被调用,以与 DxgkDdiSetVidPnSourceAddress 和 VSync 中断同步。

与 DxgkDdiControlInterrupt 交互

当 VSync 中断通过 DxgkDdiControlInterrupt/DxgkDdiControlInterrupt2/DxgkDdiControlInterrupt3 完全禁用时,无论中断目标 PresentId 值如何,它们都将保持禁用状态。 KMD 需要存储最新的中断目标 PresentId,以便在再次启用 VSync 后可以遵循该 ID。

通过 DxgkDdiControlInterruptXxx 启用 VSync 中断时,中断目标 PresentId (pSetInterruptTargetPresentId) 提供精细的控制,如下所示:

  • 当目标 PresentId 设置为 UINT64_MAX 时,在再次更改目标 PresentId 之前,不需要任何 VSync 中断。 VSync 中断已禁用,但需要 KMD 来实现 DXGK_VSYNC_DISABLE_KEEP_PHASE 行为才能重新启用中断。

  • 当目标 PresentId 设置为 0 时,每个 VSync 都需要中断。

  • 对于任何其他 PresentId 值,如果当前扫描的 PresentId >= InterruptTargetPresentId,则会引发中断。

当多个 MPO 平面可用时,如果任何平面需要 VSync 中断,则应引发 VSync 中断。

使用 DxgkDdiSetInterruptTargetPresentId 禁用两阶段 VSync

如果 OS 对 DxgkDdiSetInterruptTargetPresentId 的调用在一个平面上设置一个 InterruptTargetPresentId,这将导致在此 VidPnSource 上完全禁用 VSync(即,此平面是最后一个保持启用 VSync 的平面,现在也在禁用 VSync),则 KMD 会禁用 VSync 中断,但保持硬件中的 VSync 阶段处于启用状态 (DXGK_VSYNC_DISABLE_KEEP_PHASE)。 在一定时间段(通常相当于两个 VSync 周期)之后,OS 将使用 DXGK_VSYNC_DISABLE_NO_PHASE 调用 DxgkDdiControlInterruptXxx。 此调用可确保 KMD 有机会禁用 VSync 阶段和 VSync 时钟,以节省最大功率并保持与非硬件翻转队列系统的性能奇偶校验。

排队翻转取消

在全屏状态转换或应用程序退出等情况下,可能需要取消未来排队翻转。 为了处理这些情况,引入了以下驱动程序回调和相关结构:

KMD 在 DRIVER_INITIALIZATION_DATA 中提供一个指向其 DxgkDdiCancelFlips 函数的指针。

OS 指定在调用 DxgkDdiCancelFlips 时要取消的排队翻转范围,KMD 向 OS 报告它能够同步取消的翻转范围。

以下示例说明了在单个平面上翻转取消的机制和同步案例。 (OS 在 Windows 11 版本 22H2 中不支持异步取消。)想象一下,以下翻转正在排队到硬件翻转队列:

  • PresentId N
  • time t0 PresentId N+1
  • time t1 PresentId N+2
  • time t2 PresentId N+3
  • time t3 PresentId N+4
  • time t4

然后,OS 决定取消翻转 N+2N+3N+4,因此它调用 DxgkDdiCancelFlips,并将 PresentIdCancelRequested 设置为 N+2

当 KMD 检查硬件翻转队列状态时,它确定翻转 N+2 已发送到显示硬件,在调用时无法取消,但 N+3N+4 可以同步从硬件翻转队列中删除,而不会产生副作用。 KMD 将 PresentIdCancelled 设置为 N+3,并照常完成 N+2

OS 将 N+3N+4 标记为已取消,并将 NN+1N+2 视为正在进行。 引发下一个 VSync 中断时,翻转队列日志将像往常一样指示 NN+1N+2 时间戳。

同步取消的翻转的范围必须是连续的,如果不是零,则假定包含提交到 KMD 的最后一个当前 ID。 换言之,在两个同步取消翻转范围内都不能有间隙。

取消多个平面上的互锁翻转

通过调用具有多个平面和 PresentId 的 DxgkDdiSetVidPnSourceAddress 来提交互锁翻转。 OS 和 KMD 之间的协定如下:

  • 这组平面必须在同一个 VSync 中可见。
  • 不允许显示硬件仅在一个 VSync 上显示这些平面的子集,而在下一个 VSync 上显示其余平面。

在硬件翻转队列模型中,通过在对 DxgkDdiCancelFlips 的调用中传递多个平面和 PresentId 来取消这种互锁翻转。 在这种情况下传递的平面集必须与挂起的互锁翻转请求相对应,并且 KMD 关于所有互锁 PresentId 的决定必须相同:

  • 请勿取消,或
  • 同步取消

DxgkDdiCancelFlips 在设备中断级别 (DIRQL) 调用,以便与 DxgkDdiSetVidPnSourceAddress 和 VSync 中断同步。

获取排队翻转的当前统计信息

由于硬件翻转队列方法是为了避免在每个 VSync 上唤醒 CPU,因此需要有一种机制来保留最后几个排队翻转的帧显示时间。

支持硬件翻转队列的图形驱动程序需要将每个活动 VidPnSource 的每个给定 MPO 平面上每个完成或取消的翻转的信息写入 OS 提供的翻转队列日志缓冲区。

OS 保证在为每个活动 VidPnSource 的给定 MPO 平面进行第一次 DxgkDdiSetVidPnSourceAddress 调用之前,提供翻转队列日志指针(在对 DxgkDdiSetFlipQueueLogBuffer 的调用中)。 当翻转队列没有任何未完成的请求时,允许 OS 销毁翻转队列日志缓冲区。 在这种情况下,它将在下一次 DxgkDdiSetVidPnSourceAddress 调用之前提供新的日志指针。 翻转队列日志是循环的。 写入 [NumberOfEntries-1] 条目后,下一个日志条目将为 [0]。

在一批排队的翻转完成后,KMD 必须保证已完成翻转的翻转队列日志在这两个时间点中的最早更新:

  • 一个用于需要引发中断的翻转的 VSync 中断处理程序。
  • 响应来自 OS 的显式 DxgkDdiUpdateFlipQueueLog 请求。

翻转队列日志 DDI

添加了以下与翻转队列日志相关的回调和相关结构:

KMD 提供了一个指向其在 DRIVER_INITIALIZATION_DATA 中的函数的指针。

VSync 中断结构更新

DXGKARGCB_NOTIFY_INTERRUPT_DATA 结构进行了以下更改,以实现硬件翻转队列模型的 VSync 中断:

  • DXGK_INTERRUPT_CRTC_VSYNC_WITH_MULTIPLANE_OVERLAY3 枚举值已添加为 InterruptType
  • CrtcVSyncWithMultiPlaneOverlay3 结构已添加到联合中。 CrtcVSyncWithMultiPlaneOverlay3 的语义类似于现有的 CrtcVSyncWithMultiPlaneOverlay2 结构,不同之处在于,除了每个平面的最后一个完整 PresentId,CrtcVSyncWithMultiPlaneOverlay3.pMultiPlaneOverlayVSyncInfo 指向翻转队列日志中以前未报告的 PresentId 的范围。
  • CrtcVSyncWithMultiPlaneOverlay3pMultiPlaneOverlayVSyncInfo 成员添加了 DXGK_MULTIPLANE_OVERLAY_VSYNC_INFO3 结构。

再次使用基本硬件翻转队列示例图:

演示基本硬件翻转队列机制的示意图。

假设在提交翻转 N 时,FirstFreeFlipQueueLogEntryIndex 设置为 40,然后完成 NN+1N+2 演示。

在单个平面配置分别在时间 v2、v3、v4 完成三个 PresentId NN+1N+2 之后,KMD 将在其翻转队列日志缓冲区中写入索引为 40、41 和 42 的三个新条目。 KMD 报告 CrtcVSyncWithMultiPlaneOverlay3 结构中的 FirstFreeFlipQueueLogEntryIndex 值为 43。 OS 将观察到 FirstFreeFlipQueueLogEntryIndex 已从 40 更改为 43,并将读取日志条目 40、41 和 42。 KMD 需要设置以下翻转队列日志缓冲区值,如下所示:

  • VidPnTargetId:与 CrtcVSyncWithMultiPlaneOverlay2 中的含义相同

  • PhysicalAdapterMask:与 CrtcVSyncWithMultiPlaneOverlay2 的含义相同

  • MultiPlaneOverlayVSyncInfoCount = 1

  • pMultiPlaneOverlayVSyncInfo[0].LayerIndex = 0

  • pMultiPlaneOverlayVSyncInfo[0].FirstFreeFlipQueueLogEntryIndex = 43

  • LogBufferAddressForPlane0[40].PresentId = N

  • LogBufferAddressForPlane0[40].PresentTimestamp = v2

  • LogBufferAddressForPlane0[41].PresentId = N+1

  • LogBufferAddressForPlane0[41].PresentTimestamp = v3

  • LogBufferAddressForPlane0[42].PresentId = N+2

  • LogBufferAddressForPlane0[42].PresentTimestamp = v4

显式翻转队列日志更新请求

在某些情况下,OS 需要获取有关最后一批完成的翻转的信息,而不必等待 VSync 中断。 在这种情况下,OS 显式调用 DxgkDdiUpdateFlipQueueLog,以请求 KMD 从其专有显示硬件数据结构读取,并将过去的翻转信息写入翻转队列日志。 日志的语义与前面所述的语义相同;唯一的变化是 FirstFreeFlipQueueLogEntryIndex 返回到 VSync 中断之外的 OS。

DxgkDdiUpdateFlipQueueLog 在设备中断级别 (DIRQL) 调用,它与 DxgkDdiSetVidPnSourceAddressWithMultiPlaneOverlay3 DDI 位于同一同步类中。

在硬件翻转队列中存在排队翻转时显示模式更改和电源转换

Dxgkrnl 将确保在启动模式更改或关闭监视器之前,硬件翻转队列中已排队的翻转已完成或取消。

将 Present 请求映射到硬件翻转队列时间戳

在特定适配器上启用硬件翻转队列时,所有翻转调用都将附带时间戳。 换句话说,KMD 不需要处理新旧 DxgkDdiSetVidPnSourceAddress 语义的混合。

OS 将自动将基于间隔的现有 Present API 请求转换为对 KMD 的基于时间戳的翻转调用。 以下各节讨论了各种情况,以及如何映射到 KMD 接收的标志、持续时间和时间戳的组合。

撕裂和非撕裂翻转语义

启用硬件翻转队列时,撕裂翻转的语义在概念上是相同的。 达到 TargetFlipTime 后,KMD 会提交翻转以显示,同时仍遵循 FlipImmediateFlipImmediateNoTearingFlipOnNextVSync 等标志。 换句话说,KMD 表现得好像 OS 在 TargetFlipTime 以相同的翻转标志和参数向它提交了翻转。

例如,如果将 FlipOnNextVSync 设置为 1,并且 TargetFlipTime 位于帧中间,则此翻转应仅显示在下一个 VSync 上。

FlipOverwrite 支持和硬件翻转队列

硬件翻转队列是翻转覆盖功能的严格超集,由 DXGK_DRIVERCAPS 中的 MaxQueuedMultiPlaneOverlayFlipVSync 值控制。

因此,如果驱动程序通过将 MaxHwQueuedFlips 设置为大于 1 的值选择进入硬件翻转队列,则 OS 将忽略 MaxQueuedMultiPlaneOverlayFlipVSync 值。

TargetFlipTime 过期的多次翻转

当给定 MPO 平面有多个排队翻转且 TargetFlipTime 已过期时,硬件显示队列必须选择最近排队的过期翻转并将其提交显示。 其余过期的翻转应被视为已取消,其相应的翻转队列日志项应包含 DXGK_HWFLIPQUEUE_TIMESTAMP_CANCELLED 作为 PresentTimestamp 值。

Duration 和 TargetFlipTime 之间的交互

当屏幕上显示此结构中指定的翻转时,DXGKARG_SETVIDPNSOURCEADDRESSWITHMULTIPLANEOVERLAY3 结构中的 Duration 参数应生效。 它为 VidPnSourceId 在所有平面上指定的输出指定新的所需显示刷新率行为。 在 WDDM 3.1 和 Windows 2022 版本中,为了简化不支持排队自定义 Duration 更改的硬件的驱动程序实现,OS 仅在之前的翻转请求完成后,才使用新的 Duration 参数提交翻转请求。

将 Present 间隔映射到 TargetFlipTime

刷新率固定时的映射间隔

若要保留现有 Present 间隔语义,OS 必须使用 Present 间隔和刷新率计算目标翻转时间。 但是,在实际的 VSync 定时稍微漂移的情况下,将目标翻转时间精确地设置为翻转应该到达屏幕的预期 VSync 时间将导致由于错过 VSync 而导致的频繁故障。 为了防止出现故障,OS 从计算的目标翻转时间中减去一半的 VSync 间隔。

以下是将 Present 间隔映射到目标翻转时间的简化公式:

TargetFlipTime = PreviousFlipStartVSyncTime + (PreviousFlipPresentInterval * FixedRefreshRate) - (FixedRefreshRate / 2)

存在虚拟刷新率 WDDM 2.9 功能时的映射间隔

虚拟刷新率功能可能会暂时将显示刷新率提升到当前刷新速率的整数倍数(即 24 Hz 可以提升到 144 Hz 或 192 Hz)。 对于能够支持此提升的设备,上一节中的公式被修改为使用当前刷新率的最快倍数:

TargetFlipTime = PreviousFlipStartVSyncTime + (PreviousFlipPresentInterval * FixedRefreshRate) - (FastestRefreshRate / 2)

将刷新率更改为非倍数时的映射间隔

当刷新率更改为当前刷新率的非倍数时(例如,从 24 Hz 更改为 60 Hz),OS 必须检查队列翻转,以查看在给定新刷新率的情况下,其计算的目标时间是否仍然有效。 如果需要更改目标翻转时间,OS 将取消排队翻转,并使用新计算的目标翻转时间重新排队。