Direct3D 9Ex 改进

本主题介绍 Windows 7 在 Direct3D 9Ex 和桌面窗口管理器中新增的对 Flip Mode Present 及其相关 Present 统计信息的支持。 目标应用包括视频或基于帧速率的呈现应用。 当启用 DWM 时,使用 Direct3D 9Ex Flip Mode Present 的应用程序会减少系统资源负载。 与 Flip Mode Present 相关的 Present 统计信息增强可让 Direct3D 9Ex 应用程序通过提供实时反馈和纠正机制来更好地控制呈现速度。 其中包括详细的解释和样本资源指针。

本主题包含以下各节:

适用于 Windows 7 的 Direct3D 9Ex 的功能改进

Direct3D 9Ex 的 Flip Mode Present 是 Direct3D 9Ex 中呈现图像的一种改进模式,可以有效地将呈现的图像交给 Windows 7 桌面窗口管理器 (DWM) 进行合成。 从 Windows Vista 开始,DWM 构成了整个桌面。 在启用 DWM 后,窗口模式应用程序将使用一种名为 Blt Mode Present 到 DWM(或 Blt 模型)的方法在桌面上呈现其内容。 通过使用 Blt 模型,DWM 维护了 Direct3D 9Ex 呈现图面的副本以供桌面合成。 当应用程序更新时,新的内容会通过 blt 复制到 DWM 图面。 对于包含 Direct3D 和 GDI 内容的应用程序,GDI 数据也会被复制到 DWM 图面上。

在 Windows 7 中,Flip Mode Present 到 DWM(或 Flip Model)是一种新的呈现方法,它本质上支持在窗口模式应用程序和 DWM 之间传递应用程序图面的句柄。 除了节省资源之外,Flip Model 还支持增强的 Present 统计信息。

Present 统计信息是帧时间信息,应用程序可以使用这些信息来同步视频和音频流并从视频播放故障中恢复。 Present 统计信息中的帧时间信息允许应用程序调整其视频帧的呈现速率,以实现更流畅的呈现。 在 Windows Vista 中,DWM 会为桌面组成维护相应的框架图面副本,而应用程序可以使用 DWM 提供的 Present 统计信息。 这种获取 Present 统计信息的方法在 Windows 7 中仍然适用于现有应用程序。

在 Windows 7 中,采用 Flip Model 的基于 Direct3D 9Ex 的应用程序应会使用 D3D9Ex API 来获取 Present 统计信息。 在启用 DWM 后,窗口模式和全屏独占模式的 Direct3D 9Ex 应用程序在使用 Flip Model 时可获得相同的 Present 统计信息。 Direct3D 9Ex Flip Model Present 统计信息使应用程序能够实时查询 Present 统计信息,而不是在帧显示在屏幕上之后;启用 Flip Model 的窗口模式应用程序与全屏应用程序可获得相同的 Present 统计信息;D3D9Ex API 中添加的标志允许 Flip Model 应用程序在呈现时有效地丢弃较晚的帧。

Direct3D 9Ex Flip Model 应被针对 Windows 7 的新视频或基于帧速率的呈现应用程序使用。 由于 DWM 和 Direct3D 9Ex 运行时是同步的,因此使用 Flip Model 的应用程序应指定 2 到 4 个后缓冲区,以确保流畅地呈现。 使用 Present 统计信息的应用程序将受益于启用 Flip Model 的 Present 统计信息增强功能。

Direct3D 9EX Flip Mode Present

Direct3D 9Ex Flip Mode Present 在开启 DWM 和应用程序处于窗口模式(而非全屏独占模式)时对系统性能的提升非常明显。 下表和插图显示了选择 Flip Model 和默认使用 Blt Model 的窗口应用程序内存带宽使用量和系统读写量的简化比较。

Blt Mode Present 到 DWM D3D9Ex Flip Mode Present 到 DWM
1. 应用程序更新其帧(写入)
1. 应用程序更新其帧(写入)
2. Direct3D 运行时将图面内容复制到 DWM 重定向图面(读取、写入)
2. Direct3D 运行时将应用程序图面传递给 DWM
3. 共享图面复制完成后,DWM 将应用程序图面呈现到屏幕上(读取、写入)
3. DWM 将应用程序图面呈现到屏幕上(读取、写入)

Blt Model 和 Flip Model 的比较图

Flip Mode Present 通过减少 Direct3D 运行时对 DWM 窗口帧合成的读写次数来减少系统内存使用量。 这降低了系统功耗和总体内存使用量。

当 DWM 开启时,应用程序可以利用 Direct3D 9Ex Flip Mode Present 统计信息增强功能,而无论应用程序处于窗口模式还是全屏独占模式。

编程模型和 API

在 Windows 7 上使用 Direct3D 9Ex API 的新视频或帧速率测量应用程序可以利用内存和电源节省以及在 Windows 7 上运行时由 Flip Mode Present 提供的改进呈现效果。 (在以前的 Windows 版本上运行时,Direct3D 运行时将应用程序默认为 Blt Mode Present。)

Flip Mode Present 意味着应用程序可以在 DWM 开启时利用实时 Present 统计信息反馈和更正机制。 但是,使用 Flip Mode Present 的应用程序在使用并发 GDI API 呈现时应注意限制。

可以修改现有的应用程序以利用 Flip Mode Present,并获得与新开发的应用程序相同的优点和注意事项。

如何选择加入 Direct3D 9Ex Flip Model

针对 Windows 7 的 Direct3D 9Ex 应用程序可以通过使用 D3DSWAPEFFECT_FLIPEX 枚举值创建交换链来选择加入 Flip Model。 要选择加入 Flip Model,应用程序需要指定 D3DPRESENT_PARAMETERS 结构,然后在调用 IDirect3D9Ex::CreateDeviceEx API 时传递指向该结构的指针。 本部分将介绍针对 Windows 7 的应用程序如何使用 IDirect3D9Ex::CreateDeviceEx 选择进入 Flip Model。 有关 IDirect3D9Ex::CreateDeviceEx API 的详细信息,请参阅 MSDN 上的 IDirect3D9Ex::CreateDeviceEx

为方便起见,这里重复了 D3DPRESENT_PARAMETERSIDirect3D9Ex::CreateDeviceEx 的语法。

HRESULT CreateDeviceEx(
  UINT Adapter,
  D3DDEVTYPE DeviceType,
  HWND hFocusWindow,
  DWORD BehaviorFlags,
  D3DPRESENT_PARAMETERS* pPresentationParameters,
  D3DDISPLAYMODEEX *pFullscreenDisplayMode,
  IDirect3DDevice9Ex **ppReturnedDeviceInterface
);
typedef struct D3DPRESENT_PARAMETERS {
    UINT BackBufferWidth, BackBufferHeight;
    D3DFORMAT BackBufferFormat;
    UINT BackBufferCount;
    D3DMULTISAMPLE_TYPE MultiSampleType;
    DWORD MultiSampleQuality;
    D3DSWAPEFFECT SwapEffect;
    HWND hDeviceWindow;
    BOOL Windowed;
    BOOL EnableAutoDepthStencil;
    D3DFORMAT AutoDepthStencilFormat;
    DWORD Flags;
    UINT FullScreen_RefreshRateInHz;
    UINT PresentationInterval;
} D3DPRESENT_PARAMETERS, *LPD3DPRESENT_PARAMETERS;

当修改适用于 Windows 7 的 Direct3D 9Ex 应用程序以选择使用 Flip Model 时,应考虑以下有关 D3DPRESENT_PARAMETERS 中指定成员的项目:

BackBufferCount

(仅限 Windows 7)

SwapEffect 设置为新的 D3DSWAPEFFECT_FLIPEX 交换链效果类型时,后置缓冲区计数应等于或大于 2,以防止因等待 DWM 释放前一个 Present 缓冲区而导致应用程序性能下降。

当应用程序也使用与 D3DSWAPEFFECT_FLIPEX 相关的 Present 统计信息时,建议将后缓冲区计数设置为 2 至 4。

在 Windows Vista 或以前版本的操作系统上使用 D3DSWAPEFFECT_FLIPEX,CreateDeviceEx 将返回失败。

SwapEffect

(仅限 Windows 7)

新的 D3DSWAPEFFECT_FLIPEX 交换链效果类型指定应用程序何时在 DWM 中采用 Flip Mode Present。 它可以让应用程序更高效的利用内存和电源,同时还能让应用程序在窗口模式下利用全屏显示统计信息。 全屏应用程序行为不受影响。 如果 Windowed 设置为 TRUESwapEffect 设置为 D3DSWAPEFFECT_FLIPEX,则运行时会创建一个额外的后缓冲区,并旋转属于在呈现时成为前台缓冲区的缓冲区的句柄。

标记

(仅限 Windows 7)

如果 SwapEffect 设置为新的 D3DSWAPEFFECT_FLIPEX 交换链效果类型,则无法设置 D3DPRESENTFLAG_LOCKABLE_BACKBUFFER 标志。

Direct3D 9Ex Flip Model 应用程序的设计指南

使用以下部分中的指南来设计 Direct3D 9Ex Flip Model 应用程序。

在与 Blt Mode Present 不同的 HWND 中使用 Flip Mode Present

应用程序应在未被其他 API(包括 Blt Mode Present Direct3D 9Ex、其他版本的 Direct3D 或 GDI)定位的 HWND 中使用 Direct3D 9Ex Flip Mode Present。 Flip Mode Present 可用于呈现给子窗口;也就是说,当 Flip Model 未与 Blt Model 混合在同一个 HWND 中时,应用程序就可以使用 Flip Model,如下图所示。

Direct3D 父窗口和 GDI 子窗口的图示,每个窗口都有自己的 hwnd

gdi 父窗口和 direct3d 子窗口的图示,每个窗口都有自己的 hwnd

由于 Blt 模型维护了图面的额外副本,因此可以通过 Direct3D 和 GDI 的逐块更新将 GDI 和其他 Direct3D 内容添加到同一个 HWND。 在使用 Flip Model 时,只有传递给 DWM 的 D3DSWAPEFFECT_FLIPEX 交换链中的 Direct3D 9Ex 内容才会显示。 所有其他 Blt 模型 Direct3D 或 GDI 内容更新都将被忽略,如下图所示。

如果使用 Flip Model 并且 direct3d 和 gdi 内容位于同一个 hwnd 中,则可能无法显示 gdi 文本的插图

启用了 dwm 且应用程序处于窗口模式的 direct3d 和 gdi 内容的图示

因此,应该为交换链缓冲区图面启用 Flip Model,其中 Direct3D 9Ex Flip Model 将单独呈现到整个 HWND。

不要将 Flip Model 与 GDI 的 ScrollWindow 或 ScrollWindowEx 一起使用

一些 Direct3D 9Ex 应用程序使用 GDI 的 ScrollWindow 或 ScrollWindowEx 函数在触发用户滚动事件时更新窗口内容。 当窗口滚动时,ScrollWindow 和 ScrollWindowEx 会在屏幕上执行窗口内容的 blt。 这些函数还需要针对 GDI 和 Direct3D 9Ex 内容进行 Blt 模型更新。 当应用程序处于窗口模式且启用 DWM 时,使用任一函数的应用程序不一定显示在屏幕上滚动的可见窗口内容。 建议不要在应用程序中使用 GDI 的 ScrollWindow 和 ScrollWindowEx API,而是在滚动时重新绘制其内容。

每个 HWND 使用一个 D3DSWAPEFFECT_FLIPEX 交换链

使用 Flip Model 的应用程序不应使用针对同一 HWND 的多个 Flip Model 交换链。

Direct3D 9Ex Flip Model 应用程序的帧同步

Present 统计信息是帧时间信息,媒体应用程序可以使用这些信息来同步视频和音频流并从视频播放故障中恢复。 为了启用 Present 统计信息可用性,Direct3D 9Ex 应用程序必须确保应用程序传递给 IDirect3D9Ex::CreateDeviceExBehaviorFlags 参数包含设备行为标志 D3DCREATE_ENABLE_PRESENTSTATS

为了方便起见,这里重复了 IDirect3D9Ex::CreateDeviceEx 的语法。

HRESULT CreateDeviceEx(
  UINT Adapter,
  D3DDEVTYPE DeviceType,
  HWND hFocusWindow,
  DWORD BehaviorFlags,
  D3DPRESENT_PARAMETERS* pPresentationParameters,
  D3DDISPLAYMODEEX *pFullscreenDisplayMode,
  IDirect3DDevice9Ex **ppReturnedDeviceInterface
);

Direct3D 9Ex Flip Model 添加了 D3DPRESENT_FORCEIMMEDIATE 呈现标志,以强制执行 D3DPRESENT_INTERVAL_IMMEDIATE 呈现标志行为。 Direct3D 9Ex 应用程序在传递给 IDirect3DDevice9Ex::PresentExdwFlags 参数中指定这些表示标志,如下所示。

HRESULT PresentEx(
  CONST RECT *pSourceRect,
  CONST RECT *pDestRect,
  HWND hDestWindowOverride,
  CONST RGNDATA *pDirtyRegion,
  DWORD dwFlags
);

在修改适用于 Windows 7 的 Direct3D 9Ex 应用程序时,应该考虑有关指定的 D3DPRESENT 呈现标志的以下信息:

D3DPRESENT_DONOTFLIP

此标志仅在全屏模式下可用,或

(仅限 Windows 7)

当应用程序在调用 CreateDeviceEx 时将 D3DPRESENT_PARAMETERSSwapEffect 成员设置为 D3DSWAPEFFECT_FLIPEX

D3DPRESENT_FORCEIMMEDIATE

(仅限 Windows 7)

仅当应用程序在调用 CreateDeviceEx 时将 D3DPRESENT_PARAMETERSSwapEffect 成员设置为 D3DSWAPEFFECT_FLIPEX 时,才可以指定此标志。 应用程序可以使用此标志立即更新 DWM Present 队列中稍后几帧的图面,从而基本上跳过中间帧。

支持窗口式 FlipEx 的应用程序可以使用此标志立即使用 DWM Present 队列中较靠后的帧来更新图面,从而跳过中间帧。 这对于想要丢弃被检测为迟到的帧并在合成时显示后续帧的媒体应用程序尤其有用。 如果此标志指定不正确,IDirect3DDevice9Ex::PresentEx 将返回无效参数错误。

为了获取 Present 统计信息,应用程序通过调用 IDirect3DSwapChain9Ex::GetPresentStatistics API 来获取 D3DPRESENTSTATS 结构。

D3DPRESENTSTATS 结构包含有关 IDirect3DDevice9Ex::PresentEx 调用的统计信息。 必须使用带有 D3DCREATE_ENABLE_PRESENTSTATS 标志的 IDirect3D9Ex::CreateDeviceEx 调用来创建设备。 否则,GetPresentStatistics 返回的数据未定义。 启用了 Flip Model 的 Direct3D 9Ex 交换链在窗口模式和全屏模式下均提供 Present 统计信息。

对于窗口模式下启用 Blt-模型的 Direct3D 9Ex 交换链,所有 D3DPRESENTSTATS 结构值都将为零。

对于 FlipEx Present 统计信息,GetPresentStatistics 在以下情况下返回 D3DERR_PRESENT_STATISTICS_DISJOINT:

  • 首次调用 GetPresentStatistics,表示序列的开始
  • DWM 从开启到关闭的转换
  • 模式更改:从窗口模式到全屏或从全屏到全屏的转换

为了方便起见,这里重复了 GetPresentStatistics 的语法。

HRESULT GetPresentStatistics(
  D3DPRESENTSTATS * pPresentationStatistics
);

IDirect3DSwapChain9Ex::GetLastPresentCount 方法返回最后一个 PresentCount,即与交换链关联的显示设备最后一次成功调用 Present 的 Present ID。 此 Present ID 是 D3DPRESENTSTATS 结构的 PresentCount 成员的值。 对于启用 Blt 模型的 Direct3D 9Ex 交换链,在窗口模式下,所有 D3DPRESENTSTATS 结构值都将为零。

为了方便起见,这里重复了 IDirect3DSwapChain9Ex::GetLastPresentCount 的语法。

HRESULT GetLastPresentCount(
  UINT * pLastPresentCount
);

在修改适用于 Windows 7 的 Direct3D 9Ex 应用程序时,应考虑有关 D3DPRESENTSTATS 结构的以下信息:

  • PresentEx 调用(其中 dwFlags 参数指定了 D3DPRESENT_DONOTWAIT)返回失败时,GetLastPresentCount 返回的 PresentCount 值不会更新。
  • 当使用 D3DPRESENT_DONOTFLIP 调用 PresentEx 时,当应用程序处于窗口模式时,GetPresentStatistics 调用会成功,但不会返回更新的 D3DPRESENTSTATS 结构。
  • D3DPRESENTSTATSPresentRefreshCount 比较 SyncRefreshCount
    • 当应用程序在每个 vsync 上出现时,PresentRefreshCount 等于 SyncRefreshCount
    • SyncRefreshCount 是在提交礼物时的 vsync 间隔上获得的,SyncQPCTime 大约是与 vsync 间隔相关的时间。
typedef struct _D3DPRESENTSTATS {
    UINT PresentCount;
    UINT PresentRefreshCount;
    UINT SyncRefreshCount;
    LARGE_INTEGER SyncQPCTime;
    LARGE_INTEGER SyncGPUTime;
} D3DPRESENTSTATS;

DWM 关闭时窗口化应用程序的帧同步

当 DWM 关闭时,窗口应用程序将直接显示到显示器屏幕上,而无需经过翻转链。 在 Windows Vista 中,当 DWM 关闭时,不支持获取窗口应用程序的帧统计信息。 为了维护应用程序不需要 DWM 感知的 API,Windows 7 将在 DWM 关闭时返回窗口应用程序的帧统计信息。 DWM 关闭时返回的帧统计信息仅为估计值。

Direct3D 9Ex Flip Model 和 Present 统计信息示例的演练

选择加入 Direct3D 9Ex 示例的 FlipEx 呈现

  1. 确保示例应用程序在 Windows 7 或更高版本的操作系统上运行。
  2. 在对 CreateDeviceEx 的调用中,将 D3DPRESENT_PARAMETERSSwapEffect 成员设置为 D3DSWAPEFFECT_FLIPEX
    OSVERSIONINFO version;
    ZeroMemory(&version, sizeof(version));
    version.dwOSVersionInfoSize = sizeof(version);
    GetVersionEx(&version);
    
    // Sample would run only on Win7 or higher
    // Flip Model present and its associated present statistics behavior are only available on Windows 7 or higher operating system
    bool bIsWin7 = (version.dwMajorVersion > 6) || 
        ((version.dwMajorVersion == 6) && (version.dwMinorVersion >= 1));

    if (!bIsWin7)
    {
        MessageBox(NULL, L"This sample requires Windows 7 or higher", NULL, MB_OK);
        return 0;
    }

还选择加入 FlipEx 相关的 Direct3D 9Ex 示例的 Present 统计信息

    // Set up the structure used to create the D3DDevice
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(d3dpp));

    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_FLIPEX;        // Opts into Flip Model present for D3D9Ex swapchain
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = 256;                
    d3dpp.BackBufferHeight = 256;
    d3dpp.BackBufferCount = QUEUE_SIZE;
    d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;

    g_iWidth = d3dpp.BackBufferWidth;
    g_iHeight = d3dpp.BackBufferHeight;

    // Create the D3DDevice with present statistics enabled - set D3DCREATE_ENABLE_PRESENTSTATS for behaviorFlags parameter
    if(FAILED(g_pD3D->CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                      D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_ENABLE_PRESENTSTATS,
                                      &d3dpp, NULL, &g_pd3dDevice)))
    {
        return E_FAIL;
    }

要加以避免,请检测故障并从中恢复

  1. 队列 Present 调用:建议的后缓冲区数量为 2 到 4。

  2. Direct3D 9Ex 示例添加了一个隐式后缓冲区,实际的 Present 队列长度是后缓冲区计数 + 1。

  3. 创建帮助程序 Present 队列结构来存储所有成功提交的 Present 的 Present ID (PresentCount) 以及相关的、计算的/预期的 PresentRefreshCount。

  4. 检测故障的发生:

    • 调用 GetPresentStatistics
    • 获取已获得 Present 统计信息的帧的 Present ID (PresentCount) 和该帧显示的 vsync 计数 (PresentRefreshCount)。
    • 检索与 Present ID 关联的预期 PresentRefreshCount(示例代码中为 TargetRefresh)。
    • 如果实际的 PresentRefreshCount 晚于预期,则表明出现了故障。
  5. 要从故障中恢复:

    • 计算要跳过的帧数(示例代码中的 g_ iImmediates 变量)。
    • 以间隔 D3DPRESENT_FORCEIMMEDIATE 呈现跳过的帧。

故障检测和恢复的注意事项

  1. 故障恢复需要 N(示例代码中的 g_iQueueDelay 变量)次 Present 调用,其中 N (g_iQueueDelay) 等于 g_iImmediates 加上 Present 队列的长度,即:

    • 跳过具有 Present 间隔 D3DPRESENT_FORCEIMMEDIATE 的帧,加上
    • 需要处理的已排队 Present
  2. 设置故障长度的限制(示例中的 GLITCH_RECOVERY_LIMIT)。 如果示例应用程序无法从过长的故障中恢复(即 1 秒或 60Hz 显示器上的 60 次垂直同步),请跳过间歇性动画并重置 Present 帮助程序队列。

VOID Render()
{
    g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    g_pd3dDevice->BeginScene();

    // Compute new animation parameters for time and frame based animations

    // Time-based is a difference between base and current SyncRefreshCount
    g_aTimeBasedHistory[g_iBlurHistoryCounter] = g_iStartFrame + g_LastSyncRefreshCount - g_SyncRefreshCount;
    // Frame-based is incrementing frame value
    g_aFrameBasedHistory[g_iBlurHistoryCounter] = g_iStartFrame + g_iFrameNumber;

    RenderBlurredMesh(TRUE);    // Time-based
    RenderBlurredMesh(FALSE);   // Frame-based

    g_iBlurHistoryCounter = (g_iBlurHistoryCounter + 1) % BLUR_FRAMES;

    DrawText();

    g_pd3dDevice->EndScene();

    // Performs glitch recovery if glitch was detected
    if (g_bGlitchRecovery && (g_iImmediates > 0))
    {
        // If we have present immediates queued as a result of glitch detected, issue forceimmediate Presents for glitch recovery 
        g_pd3dDevice->PresentEx(NULL, NULL, NULL, NULL, D3DPRESENT_FORCEIMMEDIATE);
        g_iImmediates--;
        g_iShowingGlitchRecovery = MESSAGE_SHOW;
    }
    // Otherwise, Present normally
    else
    {
        g_pd3dDevice->PresentEx(NULL, NULL, NULL, NULL, 0);
    }

    // Add to helper Present queue: PresentID + expected present refresh count of last submitted Present
    UINT PresentCount;
    g_pd3dSwapChain->GetLastPresentCount(&PresentCount);
    g_Queue.QueueFrame(PresentCount, g_TargetRefreshCount);
    
    // QueueDelay specifies # Present calls to be processed before another glitch recovery attempt
    if (g_iQueueDelay > 0)
    {
        g_iQueueDelay--;
    }

    if (g_bGlitchRecovery)
    {
        // Additional DONOTFLIP presents for frame conversions, which basically follows the same logic, but without rendering
        for (DWORD i = 0; i < g_iDoNotFlipNum; i++)
        {
            if (g_TargetRefreshCount != -1)
            {
                g_TargetRefreshCount++;
                g_iFrameNumber++;
                g_aTimeBasedHistory[g_iBlurHistoryCounter] = g_iStartFrame + g_LastSyncRefreshCount - g_SyncRefreshCount;
                g_aFrameBasedHistory[g_iBlurHistoryCounter] = g_iStartFrame + g_iFrameNumber;
                g_iBlurHistoryCounter = (g_iBlurHistoryCounter + 1) % BLUR_FRAMES;
            }
            
            if (g_iImmediates > 0)
            {
                g_pd3dDevice->PresentEx(NULL, NULL, NULL, NULL, D3DPRESENT_FORCEIMMEDIATE | D3DPRESENT_DONOTFLIP);
                g_iImmediates--;
            }
            else
            {
                g_pd3dDevice->PresentEx(NULL, NULL, NULL, NULL, D3DPRESENT_DONOTFLIP);
            }
            UINT PresentCount;
            g_pd3dSwapChain->GetLastPresentCount(&PresentCount);
            g_Queue.QueueFrame(PresentCount, g_TargetRefreshCount);

            if (g_iQueueDelay > 0)
            {
                g_iQueueDelay--;
            }
        }
    }

    // Check Present Stats info for glitch detection 
    D3DPRESENTSTATS PresentStats;

    // Obtain present statistics information for successfully displayed presents
    HRESULT hr = g_pd3dSwapChain->GetPresentStats(&PresentStats);

    if (SUCCEEDED(hr))
    {
        // Time-based update
        g_LastSyncRefreshCount = PresentStats.SyncRefreshCount;
        if ((g_SyncRefreshCount == -1) && (PresentStats.PresentCount != 0))
        {
            // First time SyncRefreshCount is reported, use it as base
            g_SyncRefreshCount = PresentStats.SyncRefreshCount;
        }

        // Fetch frame from the queue...
        UINT TargetRefresh = g_Queue.DequeueFrame(PresentStats.PresentCount);

        // If PresentStats returned a really old frame that we no longer have in the queue, just don't do any glitch detection
        if (TargetRefresh == FRAME_NOT_FOUND)
            return;

        if (g_TargetRefreshCount == -1)
        {
            // This is first time issued frame is confirmed by present stats, so fill target refresh count for all frames in the queue
            g_TargetRefreshCount = g_Queue.FillRefreshCounts(PresentStats.PresentCount, g_SyncRefreshCount);
        } 
        else
        {
            g_TargetRefreshCount++;
            g_iFrameNumber++;

            // To determine whether we're glitching, see if our estimated refresh count is confirmed
            // if the frame is displayed later than the expected vsync count
            if (TargetRefresh < PresentStats.PresentRefreshCount)
            {
                // then, glitch is detected!

                // If glitch is too big, don't bother recovering from it, just jump animation
                if ((PresentStats.PresentRefreshCount - TargetRefresh) > GLITCH_RECOVERY_LIMIT)
                {
                    g_iStartFrame += PresentStats.SyncRefreshCount - g_SyncRefreshCount;
                    ResetAnimation();
                    if (g_bGlitchRecovery)
                        g_iGlitchesInaRow++;    
                } 
                // Otherwise, compute number of immediate presents to recover from it -- if we?re not still trying to recover from another glitch
                else if (g_iQueueDelay == 0)
                {
                      // skip frames to catch up to expected refresh count
                    g_iImmediates = PresentStats.PresentRefreshCount - TargetRefresh;
                    // QueueDelay specifies # Present calls before another glitch recovery 
                    g_iQueueDelay = g_iImmediates + QUEUE_SIZE;
                    if (g_bGlitchRecovery)
                        g_iGlitchesInaRow++;
                }
            }
            else
            {
                // No glitch, reset glitch count
                g_iGlitchesInaRow = 0;
            }
        }
    }
    else if (hr == D3DERR_PRESENT_STATISTICS_DISJOINT)
    {
        // D3DERR_PRESENT_STATISTICS_DISJOINT means measurements should be started from the scratch (could be caused by mode change or DWM on/off transition)
        ResetAnimation();
    }

    // If we got too many glitches in a row, reduce framerate conversion factor (that is, render less frames)
    if (g_iGlitchesInaRow == FRAMECONVERSION_GLITCH_LIMIT)
    {
        if (g_iDoNotFlipNum < FRAMECONVERSION_LIMIT)
        {
            g_iDoNotFlipNum++;
        }
        g_iGlitchesInaRow = 0;
        g_iShowingDoNotFlipBump = MESSAGE_SHOW;
    }
}

示例方案

  • 下图显示了一个后缓冲区计数为 4 的应用程序。 因此,实际 Present 队列长度为 5。

    应用程序呈现帧和 Present 队列的图示

    帧 A 预计在同步间隔数为 1 时显示在屏幕上,但检测到它在同步间隔数为 4 时显示。 因此出现了一个故障。 后续 3 帧将以 D3DPRESENT_INTERVAL_FORCEIMMEDIATE 呈现。 故障在恢复之前总共需要 8 次 Present 调用 - 下一帧将按照其目标同步间隔计数显示。

帧同步编程建议摘要

  • 创建所有 LastPresentCount ID(通过 GetLastPresentCount 获取)的备份列表以及所有已提交的 Presents 的相关估计 PresentRefreshCount。

    注意

    当应用程序使用 D3DPRESENT_DONOTFLIP 调用 PresentEx 时,GetPresentStatistics 调用会成功,但在应用程序处于窗口模式时不会返回更新的 D3DPRESENTSTATS 结构。

  • 调用 GetPresentStatistics 获取与所显示的每个帧的 Present ID 相关的实际 PresentRefreshCount,以确保应用程序处理调用中的失败返回。

  • 如果实际的 PresentRefreshCount 晚于估计的 PresentRefreshCount,则会检测到故障。 通过提交包含 D3DPRESENT_FORCEIMMEDIATE 的滞后帧的 Present 来进行补偿。

  • 当一个帧在 Present 队列中延迟呈现时,所有后续排队的帧都将延迟呈现。 D3DPRESENT_FORCEIMMEDIATE 将仅纠正所有排队帧之后要显示的下一帧。 因此,当前队列或后缓冲区计数不应太长 - 这样需要赶上的帧故障就会更少。 最佳后缓冲区数量为 2 到 4 个。

  • 如果估计的 PresentRefreshCount 晚于实际的 PresentRefreshCount,则可能发生了 DWM 限制。 可以采用以下解决方案:

    • 减少当前队列长度
    • 除了减少当前队列长度之外,还可以使用任何其他方式减少 GPU 内存需求(即降低质量、删除效果等)
    • 指定 DwmEnableMMCSS 以便从总体上防止 DWM 节流
  • 在以下方案中验证应用程序显示功能和帧统计信息性能:

    • 开启和关闭 DWM
    • 全屏独占和窗口模式
    • 性能较低的硬件
  • 当应用程序无法通过 D3DPRESENT_FORCEIMMEDIATE Present 从大量故障帧中恢复时,它们可能会执行以下操作:

    • 通过减少工作负荷进行呈现,从而降低 CPU 和 GPU 使用率。
    • 在视频解码的情况下,通过降低质量来加快解码速度,从而降低 CPU 和 GPU 的使用率。

关于 Direct3D 9Ex 改进的结论

在 Windows 7 上,显示视频或在呈现过程中测量帧速率的应用程序可以选择 Flip Model。 与 Flip Model Direct3D 9Ex 相关的 Present 统计数据改进可以使同步每帧速率演示的应用程序受益,并为故障检测和恢复提供实时反馈。 采用 Direct3D 9Ex Flip Model 的开发人员应该考虑将 HWND 与 GDI 内容和帧速率同步分开。 请参阅本主题中的详细信息。 有关更多文档,请参阅 MSDN 上的 DirectX 开发人员中心

行动号召

在创建尝试同步演示帧速率或从显示故障中恢复的应用程序时,鼓励在 Windows 7 上使用 Direct3D 9Ex Flip Model 及其 Present 统计信息。

MSDN 上的 DirectX 开发人员中心