共用方式為


Direct3D 9Ex 改善

本主題描述 Windows 7 新增了對翻轉模式呈現的支援,以及 Direct3D 9Ex 和 Desktop Window Manager 中相關聯的目前統計數據。 目標應用程式包括視訊或幀速率型簡報應用程式。 使用 Direct3D 9Ex Flip Mode Present 的應用程式會在啟用 DWM 時減少系統資源負載。 與翻轉模式簡報相關聯的簡報統計數據增強功能可讓 Direct3D 9Ex 應用程式藉由提供即時意見反應和更正機制,以更妥善地控制呈現率。 包含範例資源的詳細說明和指標。

本主題包含下列各節。

改善適用於 Windows 7 的 Direct3D 9Ex

Direct3D 9Ex 的翻轉模式簡報是 Direct3D 9Ex 中呈現影像的改善模式,可有效率地將轉譯的影像交給 Windows 7 桌面視窗管理員 (DWM) 以進行組合。 從 Windows Vista 開始,DWM 會撰寫整個桌面。 啟用 DWM 時,視窗模式應用程式會使用名為 Blt Mode Present to DWM 的方法,在桌面上呈現其內容(或 Blt 模型)。 使用Blt模型時,DWM會維護Desktop組合的 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 簡報統計數據可讓應用程式實時查詢目前統計數據,而不是在畫面上顯示框架之後:當做全螢幕應用程式時,視窗模式的翻轉模型啟用應用程式可以使用相同的統計資料資訊:D3D9Ex API 中新增的旗標可讓翻轉模型應用程式在簡報時有效地捨棄晚期畫面。

Direct3D 9Ex Flip Model 應該由以 Windows 7 為目標的新視訊或幀速率型簡報應用程式使用。 由於 DWM 與 Direct3D 9Ex 執行時間之間的同步處理,使用翻轉模型的應用程式應該在 2 到 4 個後盾之間指定,以確保呈現順暢。 使用這些使用目前統計數據資訊的應用程式,將受益於使用已啟用翻轉模型的目前統計數據增強功能。

Direct3D 9EX 翻轉模式簡報

Direct3D 9Ex Flip Mode Present 的效能改善在系統上很重要,當 DWM 開啟時和應用程式處於視窗模式時,而不是全螢幕獨佔模式。 下表和圖例顯示記憶體頻寬使用量和系統讀取和寫入視窗化應用程式的簡化比較,這些應用程式選擇 [翻轉模型] 與預設使用量Blt模型。

呈現給 DWM 的 Blt 模式 D3D9Ex 翻轉模式呈現至 DWM
1.應用程式會更新其框架(寫入)
1.應用程式會更新其框架(寫入)
2. Direct3D 執行時間會將表面內容複製到 DWM 重新導向介面(讀取、寫入)
2.Direct3D 運行時間將應用程式介面傳遞至 DWM
3. 共用表面複製完成後,DWM 會將應用程式介面轉譯到畫面上(讀取、寫入)
3.DWM 會將應用程式介面轉譯到畫面上(讀取、寫入)

blt 模型和翻轉模型的比較圖例

翻轉模式呈現可藉由減少 DwM 視窗框架組合之 Direct3D 運行時間的讀取和寫入數目,以減少系統記憶體使用量。 這樣可減少系統耗電量和整體記憶體使用量。

無論應用程式處於視窗模式還是全螢幕獨佔模式,應用程式都可以利用 Direct3D 9Ex Flip Mode 在 DWM 開啟時呈現統計數據增強功能。

程序設計模型和 API

在 Windows 7 上使用 Direct3D 9Ex API 的新視訊或幀速率評量應用程式,可以利用 Windows 7 上執行時翻轉模式簡報所提供的記憶體和省電,以及改進的簡報。 (在舊版 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_PARAMETERS IDirect3D9Ex::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 設定為 TRUE,且 SwapEffect 設定為 D3DSWAPEFFECT_FLIPEX,運行時間會建立一個額外的後台緩衝區,並旋轉每一個句柄都屬於簡報時間成為前端緩衝區的緩衝區。

旗標

(僅限 Windows 7)

如果 SwapEffect 設定為新的D3DSWAPEFFECT_FLIPEX交換鏈結效果類型,則無法設定D3DPRESENTFLAG_LOCKABLE_BACKBUFFER旗標。

Direct3D 9Ex 翻轉模型應用程式的設計指導方針

使用下列各節中的指導方針來設計 Direct3D 9Ex Flip Model 應用程式。

使用從 Blt 模式呈現的個別 HWND 中的翻轉模式

應用程式應該使用 HWND 中的 Direct3D 9Ex 翻轉模式,但其他 API 並不以其他 API 為目標,包括 Blt Mode Present Direct3D 9Ex、其他 Direct3D 或 GDI 版本。 翻轉模式呈現可用來呈現子視窗;也就是說,當應用程式未與相同 HWND 中的 Blt 模型混合時,可以使用翻轉模型,如下圖所示。

direct3d 父視窗和 gdi 子視窗的圖例,每個視窗都有自己的 hwnd

gdi 父視窗和 direct3d 子視窗的圖例,每個視窗都有自己的 hwnd

因為Blt模型會維護表面的其他複本,因此 GDI 和其他 Direct3D 內容可以透過 Direct3D 和 GDI 的分次更新新增至相同的 HWND。 使用翻轉模型時,只會顯示傳遞至 DWM 之交換鏈結中 D3DSWAPEFFECT_FLIPEX Direct3D 9Ex 內容。 所有其他 Blt 模型 Direct3D 或 GDI 內容更新都會被忽略,如下圖所示。

如果使用翻轉模型,且 direct3d 和 gdi 內容位於相同的 hwnd 中,則可能無法顯示的 gdi 文字圖例

啟用 dwm 且應用程式處於視窗模式的 direct3d 和 gdi 內容的圖例

因此,應該針對交換鏈結緩衝區啟用翻轉模型,其中 Direct3D 9Ex Flip Model 單獨轉譯至整個 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::CreateDeviceEx 的 BehaviorFlags 參數包含裝置行為旗標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_INTERVAL_IMMEDIATE簡報旗標行為的D3DPRESENT_FORCEIMMEDIATE簡報旗標。 Direct3D 9Ex 應用程式會在 dwFlags 參數中指定這些簡報旗標,該應用程式會傳遞至 IDirect3DDevice9Ex::P resentEx,如下所示。

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_PARAMETERS SwapEffect 成員設定為D3DSWAPEFFECT_FLIPEX。

D3DPRESENT_FORCEIMMEDIATE

(僅限 Windows 7)

只有當應用程式在呼叫 CreateDeviceEx 時,才能指定此旗標,將 D3DPRESENT_PARAMETERS SwapEffect 成員設定為D3DSWAPEFFECT_FLIPEX。 應用程式可以使用此旗標,在 DWM Present 佇列稍後使用數個畫面格立即更新介面,基本上會略過中繼畫面格。

啟用視窗的 FlipEx 應用程式可以使用此旗標,立即使用 DWM Present 佇列中稍後的框架來更新介面,略過中繼畫面格。 這特別適用於想要捨棄最近偵測到的畫面格,並在組合時間呈現後續畫面的媒體應用程式。 IDirect3DDevice9Ex::P resentEx 如果未正確指定此旗標,則會傳回無效的參數錯誤。

若要取得目前的統計數據資訊,應用程式會藉由呼叫IDirect3DSwapChain9Ex::GetPresentStatistics API 來取得D3DPRESENTSTATS結構。

D3DPRESENTSTATS結構包含 IDirect3DDevice9Ex::P resentEx 呼叫的相關統計數據。 必須使用 IDirect3D9Ex::CreateDeviceEx 呼叫搭配 D3DCREATE_ENABLE_PRESENTSTATS 旗標來建立裝置。 否則,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 標識符。 這個 Present ID 是 D3DPRESENTSTATS 結構之 PresentCount 成員的值 針對已啟用 Blt-Model 的 Direct3D 9Ex 交換鏈結,而在視窗模式中,所有 D3DPRESENTSTATS 結構值都會是零。

為了方便起見,這裡會重複IDirect3DSwapChain9Ex::GetLastPresentCount語法。

HRESULT GetLastPresentCount(
  UINT * pLastPresentCount
);

當您修改適用於 Windows 7 的 Direct3D 9Ex 應用程式時,您應該考慮下列D3DPRESENTSTATS結構的相關信息

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 翻轉模型和簡報統計數據範例的逐步解說

若要選擇 Direct3D 9Ex 範例的 FlipEx 簡報

  1. 確定範例應用程式是在 Windows 7 或更新版本的作業系統版本上執行。
  2. D3DPRESENT_PARAMETERSSwapEffect 成員設定為在 CreateDeviceEx 呼叫中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;
    }

也加入 Direct3D 9Ex 範例的 FlipEx 相關聯簡報統計數據

    // 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 範例會新增隱含的反緩衝區,實際的 Present 佇列長度是反緩衝區計數 + 1。

  3. 建立 Helper Present 佇列結構,以儲存所有成功提交的 Present 標識碼 (PresentCount) 和相關聯的計算/預期的 PresentRefreshCount。

  4. 若要偵測故障發生次數:

    • 呼叫 GetPresentStatistics
    • 取得目前統計數據取得框架的目前標識碼 (PresentCount) 和 vsync 計數。
    • 擷取與目前標識符相關聯的預期 PresentRefreshCount (範例程序代碼中的 TargetRefresh)。
    • 如果實際的 PresentRefreshCount 晚於預期,就會發生問題。
  5. 若要從問題復原:

    • 計算要略過的畫面數(g_範例程序代碼中的 iImmediates 變數)。
    • 以間隔D3DPRESENT_FORCEIMMEDIATE呈現略過的畫面格。

故障偵測和復原的考慮

  1. 故障復原會採用 N (範例程式代碼中的g_iQueueDelay變數) 目前呼叫數目,其中 N (g_iQueueDelay) 等於 g_iImmediates加上 Present 佇列的長度,也就是:

    • 略過具有目前間隔的框架D3DPRESENT_FORCEIMMEDIATE,加上
    • 需要處理的佇列簡報
  2. 設定問題長度的限制(範例中GLITCH_RECOVERY_LIMIT)。 如果範例應用程式無法從太長的故障中復原(也就是說,60Hz 監視器上的 1 秒或 60 個 vsync),請跳過間歇性動畫並重設 Present Helper 佇列。

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 呼叫 ,下一個畫面會根據其目標同步間隔計數來顯示。

框架同步處理的程式設計建議摘要

  • 建立所有 LastPresentCount 識別符的備份清單(透過 GetLastPresentCount 取得),以及已提交之所有 Presents 的相關聯估計 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 上,在簡報期間顯示視訊或量測計幀速率的應用程式可以加入加入翻轉模型。 與翻轉模型 Direct3D 9Ex 相關聯的目前統計數據改進,可讓同步處理每個幀速率的簡報的應用程式受益,以及針對故障偵測和復原的即時意見反應。 採用 Direct3D 9Ex Flip 模型的開發人員應該將目標設為與 GDI 內容和幀速率同步處理的個別 HWND。 請參閱本主題中的詳細數據。 如需其他檔,請參閱 MSDN 上的 DirectX 開發人員中心。

建議採取的動作

當您建立嘗試同步處理簡報幀速率或從顯示器故障復原的應用程式時,建議您使用 Direct3D 9Ex Flip Model 及其 Windows 7 上的目前統計數據。

MSDN 上的 DirectX 開發人員中心