Direct3D 9Ex 改进

本主题介绍 Windows 7 在 Direct3D 9Ex 和桌面窗口管理器中添加了对翻转模式演示的支持及其关联的当前统计信息。 目标应用程序包括基于视频或帧速率的演示应用程序。 启用 DWM 后,使用 Direct3D 9Ex Flip Mode Present 的应用程序可减少系统资源负载。 显示与翻转模式演示关联的统计信息增强功能使 Direct3D 9Ex 应用程序可以通过提供实时反馈和更正机制更好地控制演示速率。 包含指向示例资源的详细说明和指针。

本主题包含以下各节:

Direct3D 9Ex for Windows 7 的改进内容

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

在 Windows 7 中,翻转模式呈现到 DWM (或翻转模型) 是一种新的表示方法,它基本上允许在窗口模式应用程序和 DWM 之间传递应用程序图面的句柄。 除了节省资源外,翻转模型还支持增强的当前统计信息。

当前统计信息是帧计时信息,应用程序可以使用这些信息来同步视频和音频流,并从视频播放故障中恢复。 通过当前统计信息中的帧计时信息,应用程序可以调整其视频帧的呈现速率,以便更流畅地呈现。 在 Windows Vista 中,DWM 为桌面组合维护相应的框架图面副本,应用程序可以使用 DWM 提供的当前统计信息。 对于现有应用程序,此获取当前统计信息的方法在 Windows 7 中仍可用。

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

面向 Windows 7 的新视频或基于帧速率的演示应用程序应使用 Direct3D 9Ex Flip 模型。 由于 DWM 与 Direct3D 9Ex 运行时之间存在同步,因此使用翻转模型的应用程序应指定 2 到 4 个后退器,以确保顺利呈现。 使用显示统计信息的应用程序将受益于使用启用了翻转模型的呈现统计信息增强功能。

Direct3D 9EX 翻转模式演示文稿

当 DWM 处于打开和应用程序处于窗口模式时(而不是全屏独占模式)时,Direct3D 9Ex 翻转模式呈现的性能改进在系统上是显著的。 下表和插图显示了内存带宽使用情况和系统读取和写入窗口应用程序(选择翻转模型与默认使用情况 Blt 模型)的简化比较。

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

blt 模型与翻转模型比较的插图

翻转模式呈现通过减少 Direct3D 运行时对 DWM 窗口框架组合的读取和写入次数,减少了系统内存使用量。 这可降低系统功耗和总体内存使用量。

当 DWM 处于打开状态时,无论应用程序处于窗口模式还是全屏独占模式,应用程序都可以利用 Direct3D 9Ex 翻转模式提供统计信息增强功能。

编程模型和 API

在 Windows 7 上运行时,使用 Direct3D 9Ex API 的新视频或帧速率测量应用程序可以利用内存和省电以及翻转模式演示提供的改进演示文稿。 (在以前的 Windows 版本中运行时,Direct3D 运行时会将应用程序默认为 Blt Mode Present.)

打开 DWM 时,翻转模式演示要求应用程序可以利用实时呈现统计信息反馈和更正机制。 但是,使用翻转模式呈现的应用程序在使用并发 GDI API 呈现时应注意限制。

可以修改现有应用程序以利用翻转模式呈现,其优点和注意事项与新开发的应用程序相同。

如何选择加入 Direct3D 9Ex 翻转模型

面向 Windows 7 的 Direct3D 9Ex 应用程序可以通过创建具有 D3DSWAPEFFECT_FLIPEX 枚举值的交换链来选择加入翻转模型。 若要选择加入翻转模型,应用程序需要指定 D3DPRESENT_PARAMETERS 结构,然后在调用 IDirect3D9Ex::CreateDeviceEx API 时传递指向此结构的指针。 本部分介绍面向 Windows 7 的应用程序如何使用 IDirect3D9Ex::CreateDeviceEx 选择加入翻转模型。 有关 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 应用程序以选择加入翻转模型时,应考虑以下有关 D3DPRESENT_PARAMETERS指定成员的项目:

BackBufferCount

(Windows 7 仅)

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

当应用程序还使用与D3DSWAPEFFECT_FLIPEX关联的当前统计信息时,建议将回退缓冲区计数从 2 设置为 4。

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

SwapEffect

(Windows 7 仅)

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

标志

(Windows 7 仅)

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

Direct3D 9Ex 翻转模型应用程序设计指南

使用以下部分中的指南设计 Direct3D 9Ex 翻转模型应用程序。

在单独的 HWND 中使用翻转模式呈现与 Blt 模式呈现

应用程序应在 HWND 中使用 Direct3D 9Ex 翻转模式呈现,该模式不是其他 API 的目标,包括 Blt Mode Present Direct3D 9Ex、其他版本的 Direct3D 或 GDI。 翻转模式演示可用于向子窗口呈现;也就是说,当它不与同一 HWND 中的 Blt 模型混合时,应用程序可以使用翻转模型,如下图所示。

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

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

由于 Blt 模型维护图面的其他副本,因此可以通过 Direct3D 和 GDI 的逐段更新将 GDI 和其他 Direct3D 内容添加到同一 HWND。 使用翻转模型,将仅显示传递给 DWM 的D3DSWAPEFFECT_FLIPEX 交换链中的 Direct3D 9Ex 内容。 将忽略所有其他 Blt 模型 Direct3D 或 GDI 内容更新,如下图所示。

使用翻转模型且 direct3d 和 gdi 内容位于同一 hwnd 中的 gdi 文本的插图

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

因此,应为交换链缓冲区表面启用翻转模型,其中 Direct3D 9Ex 翻转模型单独呈现到整个 HWND。

不要将翻转模型与 GDI 的 ScrollWindow 或 ScrollWindowEx 配合使用

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

每个 HWND 使用一个D3DSWAPEFFECT_FLIPEX交换链

使用翻转模型的应用程序不应使用针对同一 HWND 的多个翻转模型交换链。

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

当前统计信息是媒体应用程序用于同步视频和音频流以及从视频播放故障中恢复的帧计时信息。 若要启用当前统计信息可用性,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 翻转模型添加 D3DPRESENT_FORCEIMMEDIATE 表示标志,该标志强制执行 D3DPRESENT_INTERVAL_IMMEDIATE 演示文稿标志行为。 Direct3D 9Ex 应用程序在应用程序传递给 IDirect3DDevice9Ex::P resentExdwFlags 参数中指定这些表示标志,如下所示。

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

修改适用于 Windows 7 的 Direct3D 9Ex 应用程序时,应考虑有关指定的 D3DPRESENT 演示文稿标志的以下信息:

D3DPRESENT_DONOTFLIP

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

(Windows 7 仅)

应用程序将 D3DPRESENT_PARAMETERSSwapEffect 成员设置为在调用 CreateDeviceExD3DSWAPEFFECT_FLIPEX

D3DPRESENT_FORCEIMMEDIATE

(Windows 7 仅)

仅当应用程序在调用 CreateDeviceEx 时将 D3DPRESENT_PARAMETERSSwapEffect 成员设置为D3DSWAPEFFECT_FLIPEX时,才能指定此标志。 应用程序可以使用此标志在 DWM Present 队列的后面立即更新具有多个帧的图面,实质上跳过中间帧。

启用窗口的 FlipEx 的应用程序可以使用此标志立即使用 DWM Present 队列中稍后的帧更新图面,从而跳过中间帧。 这对于希望放弃已检测为延迟的帧并在合成时呈现后续帧的媒体应用程序尤其有用。 如果未正确指定此标志,IDirect3DDevice9Ex::P resentEx 将返回无效的参数错误。

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

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

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

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

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

为方便起见,此处将重复 GetPresentStatistics 的 语法。

HRESULT GetPresentStatistics(
  D3DPRESENTSTATS * pPresentationStatistics
);

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

为方便起见,此处将重复 IDirect3DSwapChain9Ex::GetLastPresentCount 的 语法。

HRESULT GetLastPresentCount(
  UINT * pLastPresentCount
);

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

  • 当使用 dwFlags 参数中指定的D3DPRESENT_DONOTWAIT的 PresentEx 调用返回失败时,GetLastPresentCount 返回的 PresentCount 值不会更新。
  • 使用 D3DPRESENT_DONOTFLIP 调用 PresentEx 时, GetPresentStatistics 调用成功,但当应用程序处于窗口模式时,不会返回更新的 D3DPRESENTSTATS 结构。
  • D3DPRESENTSTATS 中的 PresentRefreshCountSyncRefreshCount
    • 当应用程序在每个 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,当 DWM 关闭时,Windows 7 将返回窗口化应用程序的帧统计信息。 DWM 关闭时返回的帧统计信息只是估计值。

Direct3D 9Ex 翻转模型和呈现统计信息示例的Walk-Through

选择使用 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 的“演示统计信息”示例

    // 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. 队列当前调用:建议的回退器计数为 2 到 4。

  2. Direct3D 9Ex 示例添加了一个隐式反缓冲区,实际“当前”队列长度为 backbuffer 计数 + 1。

  3. 创建帮助程序“演示”队列结构,以存储所有成功提交的 Present ID (PresentCount) 和关联的、计算的/预期的 PresentRefreshCount。

  4. 若要检测故障发生::

    • 调用 GetPresentStatistics
    • 获取 Present ID (PresentCount) 和 vsync count,其中帧显示在 (PresentRefreshCount) ,该帧已获取其当前统计信息。
    • 在与 Present ID 关联的示例代码) 中检索预期的 PresentRefreshCount (TargetRefresh。
    • 如果实际的 PresentRefreshCount 晚于预期,则发生故障。
  5. 若要从故障中恢复::

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

故障检测和恢复注意事项

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

    • 跳过帧的当前间隔D3DPRESENT_FORCEIMMEDIATE,加上
    • 需要处理的排队演示
  2. 为示例) 中的故障长度 (GLITCH_RECOVERY_LIMIT设置限制。 如果示例应用程序无法从 (太长的故障(例如 1 秒或 60Hz 监视器) 上的 60 个 vsync)中恢复,请跳过间歇动画并重置“当前帮助程序”队列。

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 的应用程序。 因此,实际“当前”队列长度为 5。

    应用程序呈现的帧和当前队列的插图

    帧 A 的目标是在同步间隔计数为 1 时在屏幕上显示,但检测到它显示在同步间隔计数为 4 时。 因此发生了故障。 随后的 3 帧会显示D3DPRESENT_INTERVAL_FORCEIMMEDIATE。 故障在恢复之前应总共需要 8 次 Present 调用 - 下一帧将根据其目标同步间隔计数显示。

帧同步编程建议摘要

  • 创建通过 GetLastPresentCount) 获取的所有 LastPresentCount ID (的备份列表,并关联所有提交的 PresentRefreshCount 估计的 PresentRefreshCount。

    备注

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

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

  • 如果实际的 PresentRefreshCount 晚于估计的 PresentRefreshCount,则检测到故障。 通过提交滞后帧的“演示”D3DPRESENT_FORCEIMMEDIATE进行补偿。

  • 当一个帧在“当前”队列中后期呈现时,所有后续排队帧都将延迟呈现。 D3DPRESENT_FORCEIMMEDIATE将仅更正所有排队帧之后要显示的下一帧。 因此,“当前队列”或“后备器”计数不应过长,因此要跟上的帧故障更少。 最佳反缓冲区计数为 2 到 4。

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

    • 减少当前队列长度
    • 除了减少当前队列长度 ((即降低质量、删除效果等)外,还通过任何其他方法降低 GPU 内存需求)
    • 指定 DwmEnableMMCSS 以防止 DWM 限制一般
  • 在以下方案中验证应用程序显示功能和帧统计信息性能:

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

    • 通过减少工作负载的渲染来减少 CPU 和 GPU 使用率。
    • 在视频解码的情况下,通过降低质量(因此降低 CPU 和 GPU 使用率)来更快地解码。

有关 Direct3D 9Ex 改进的结论

在 Windows 7 上,在演示期间显示视频或测量帧速率的应用程序可以选择使用翻转模型。 与 Flip Model Direct3D 9Ex 关联的现有统计信息改进可让同步每帧速率呈现的应用程序受益,同时提供故障检测和恢复的实时反馈。 采用 Direct3D 9Ex 翻转模型的开发人员应将目标与 GDI 内容和帧速率同步分开考虑。 请参阅本主题和 MSDN 文档中的详细信息。 有关其他文档,请参阅 MSDN 上的 DirectX 开发人员中心

行动号召

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

MSDN 上的 DirectX 开发人员中心