Direct3D 9Ex 개선 사항

이 항목에서는 Flip Mode Present에 대한 Windows 7의 추가 지원과 Direct3D 9Ex 및 데스크톱 창 관리자의 관련 현재 통계에 대해 설명합니다. 대상 애플리케이션에는 비디오 또는 프레임 속도 기반 프레젠테이션 애플리케이션이 포함됩니다. Direct3D 9Ex Flip Mode Present를 사용하는 애플리케이션은 DWM을 사용할 때 시스템 리소스 부하를 줄입니다. Flip Mode Present와 관련된 통계 개선 사항을 제시하면 Direct3D 9Ex 애플리케이션이 실시간 피드백 및 수정 메커니즘을 제공하여 프레젠테이션 속도를 더 잘 제어할 수 있습니다. 샘플 리소스에 대한 자세한 설명 및 포인터가 포함되어 있습니다.

이 항목에는 다음과 같은 섹션이 포함되어 있습니다.

Windows 7용 Direct3D 9Ex의 향상된 기능

Direct3D 9Ex의 대칭 이동 모드 프레젠테이션은 렌더링된 이미지를 구성을 위해 Windows 7 DWM(데스크톱 창 관리자)에 효율적으로 전달하는 Direct3D 9Ex의 이미지를 표시하는 향상된 모드입니다. Windows Vista부터 DWM은 전체 데스크톱을 구성합니다. DWM을 사용하도록 설정하면 창 모드 애플리케이션은 Blt Mode Present to DWM(또는 Blt Model)이라는 메서드를 사용하여 데스크톱에 콘텐츠를 표시합니다. Blt 모델에서 DWM은 데스크톱 컴퍼지션을 위해 Direct3D 9Ex 렌더링된 표면의 복사본을 유지 관리합니다. 애플리케이션이 업데이트되면 새 콘텐츠가 Blt를 통해 DWM 화면에 복사됩니다. Direct3D 및 GDI 콘텐츠를 포함하는 애플리케이션의 경우 GDI 데이터도 DWM 화면에 복사됩니다.

Windows 7에서 사용할 수 있는 Flip Mode Present to DWM(또는 Flip Model)은 기본적으로 창 모드 애플리케이션과 DWM 간에 애플리케이션 표면의 핸들을 전달할 수 있도록 하는 새로운 프레젠테이션 방법입니다. Flip Model은 리소스 저장 외에도 향상된 현재 통계를 지원합니다.

현재 통계는 애플리케이션이 비디오 및 오디오 스트림을 동기화하고 비디오 재생 결함에서 복구하는 데 사용할 수 있는 프레임 타이밍 정보입니다. 현재 통계의 프레임 타이밍 정보를 사용하면 애플리케이션이 더 원활한 프레젠테이션을 위해 비디오 프레임의 프레젠테이션 속도를 조정할 수 있습니다. DWM이 데스크톱 컴퍼지션을 위해 프레임 표면의 해당 복사본을 유지 관리하는 Windows Vista에서 애플리케이션은 DWM에서 제공하는 현재 통계를 사용할 수 있습니다. 현재 통계를 가져오는 이 방법은 기존 애플리케이션에 대해 Windows 7에서 계속 사용할 수 있습니다.

Windows 7에서 Flip 모델을 채택하는 Direct3D 9Ex 기반 애플리케이션은 D3D9Ex API를 사용하여 현재 통계를 가져와야 합니다. DWM을 사용하도록 설정하면 창 모드 및 전체 화면 전용 모드 Direct3D 9Ex 애플리케이션은 Flip 모델을 사용할 때 동일한 현재 통계 정보를 기대할 수 있습니다. Direct3D 9Ex Flip Model 프레젠테이션 통계를 사용하면 애플리케이션에서 프레임이 화면에 표시된 후가 아니라 실시간으로 현재 통계를 쿼리할 수 있습니다. 창 모드 Flip-Model 사용하도록 설정된 애플리케이션에 대해 전체 화면 애플리케이션과 동일한 현재 통계 정보를 사용할 수 있습니다. D3D9Ex API에 추가된 플래그를 사용하면 Flip Model 애플리케이션이 프레젠테이션 시간에 늦은 프레임을 효과적으로 삭제할 수 있습니다.

Direct3D 9Ex Flip Model은 Windows 7을 대상으로 하는 새로운 비디오 또는 프레임 속도 기반 프레젠테이션 애플리케이션에서 사용해야 합니다. DWM과 Direct3D 9Ex 런타임 간의 동기화로 인해 Flip 모델을 사용하는 애플리케이션은 원활한 프레젠테이션을 위해 2~4개의 백 버퍼를 지정해야 합니다. 현재 통계 정보를 사용하는 애플리케이션은 Flip 모델을 사용하도록 설정된 현재 통계 향상 기능을 사용하면 이점을 얻을 수 있습니다.

Direct3D 9EX 대칭 이동 모드 프레젠테이션

Direct3D 9Ex Flip Mode Present의 성능 향상은 DWM이 켜져 있고 애플리케이션이 전체 화면 전용 모드가 아닌 창 모드에 있을 때 시스템에서 중요합니다. 다음 표와 그림에서는 대칭 이동 모델과 기본 사용량 Blt 모델을 선택하는 창이 있는 애플리케이션의 메모리 대역폭 사용량 및 시스템 읽기 및 쓰기를 간소화한 비교를 보여 줍니다.

DWM에 있는 Blt 모드 DWM에 있는 D3D9Ex 대칭 이동 모드
1. 애플리케이션이 프레임을 업데이트합니다(쓰기).
1. 애플리케이션이 프레임을 업데이트합니다(쓰기).
2. Direct3D 런타임은 표면 콘텐츠를 DWM 리디렉션 화면에 복사합니다(읽기, 쓰기).
2. Direct3D 런타임이 애플리케이션 표면을 DWM에 전달합니다.
3. 공유 표면 복사가 완료되면 DWM은 애플리케이션 표면을 화면에 렌더링합니다(읽기, 쓰기).
3. DWM은 애플리케이션 화면을 화면에 렌더링합니다(읽기, 쓰기).

Blt 모델 및 대칭 이동 모델 비교 그림

대칭 이동 모드 프레젠테이션은 DWM의 창 프레임 컴퍼지션에 대한 Direct3D 런타임의 읽기 및 쓰기 수를 줄여 시스템 메모리 사용량을 줄입니다. 이렇게 하면 시스템 전원 사용량과 전체 메모리 사용량이 줄어듭니다.

애플리케이션은 애플리케이션이 창 모드인지 전체 화면 전용 모드인지에 관계없이 DWM이 켜져 있을 때 Direct3D 9Ex Flip 모드의 통계 향상 기능을 활용할 수 있습니다.

프로그래밍 모델 및 API

Windows 7에서 Direct3D 9Ex API를 사용하는 새로운 비디오 또는 프레임 속도 측정 애플리케이션은 Windows 7에서 실행할 때 Flip 모드 Present에서 제공하는 향상된 프레젠테이션과 메모리 및 전원 절감을 활용할 수 있습니다. (이전 Windows 버전에서 실행하는 경우 Direct3D 런타임은 기본적으로 애플리케이션을 Blt 모드 Present로 설정합니다.)

Flip Mode Present는 애플리케이션이 DWM이 켜질 때 실시간 현재 통계 피드백 및 수정 메커니즘을 활용할 수 있음을 수반합니다. 그러나 Flip Mode Present를 사용하는 애플리케이션은 동시 GDI API 렌더링을 사용할 때 제한 사항을 알고 있어야 합니다.

기존 애플리케이션을 수정하여 새로 개발된 애플리케이션과 동일한 이점 및 주의 사항과 함께 Flip Mode Present를 활용할 수 있습니다.

Direct3D 9Ex Flip 모델을 옵트인하는 방법

Windows 7을 대상으로 하는 Direct3D 9Ex 애플리케이션은 D3DSWAPEFFECT_FLIPEX 열거형 값으로 스왑 체인을 만들어 Flip 모델을 옵트인할 수 있습니다. Flip 모델을 옵트인하기 위해 애플리케이션은 D3DPRESENT_PARAMETERS 구조를 지정한 다음 IDirect3D9Ex::CreateDeviceEx API를 호출할 때 이 구조체에 대한 포인터를 전달합니다. 이 섹션에서는 Windows 7을 대상으로 하는 애플리케이션이 IDirect3D9Ex::CreateDeviceEx 를 사용하여 Flip 모델을 옵트인하는 방법을 설명합니다. 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 스왑 체인 효과 유형으로 설정된 경우 이전 Present 버퍼가 DWM에서 해제될 때까지 대기한 결과로 애플리케이션 성능 저하를 방지하려면 백 버퍼 수가 2보다 크거나 같아야 합니다.

애플리케이션에서 D3DSWAPEFFECT_FLIPEX 연결된 현재 통계도 사용하는 경우 백 버퍼 수를 2에서 4로 설정하는 것이 좋습니다.

Windows Vista 또는 이전 운영 체제 버전에서 D3DSWAPEFFECT_FLIPEX 사용하면 CreateDeviceEx에서 실패가 반환됩니다.

SwapEffect

(Windows 7에만 해당)

새 D3DSWAPEFFECT_FLIPEX 스왑 체인 효과 유형은 애플리케이션이 DWM에 Flip Mode Present를 채택하는 시기를 지정합니다. 이를 통해 애플리케이션에서 메모리 및 전원을 보다 효율적으로 사용할 수 있으며, 애플리케이션이 창 모드에서 전체 화면 표시 통계를 활용할 수 있습니다. 전체 화면 애플리케이션 동작은 영향을 받지 않습니다. Windowed가 TRUE 로 설정되고 SwapEffect 가 D3DSWAPEFFECT_FLIPEX 설정되면 런타임은 하나의 추가 백 버퍼를 만들고 프레젠테이션 시 전면 버퍼가 되는 버퍼에 속하는 핸들을 회전합니다.

플래그

(Windows 7에만 해당)

SwapEffect가 새 D3DSWAPEFFECT_FLIPEX 스왑 체인 효과 유형으로 설정된 경우 D3DPRESENTFLAG_LOCKABLE_BACKBUFFER 플래그를 설정할 수 없습니다.

Direct3D 9Ex Flip Model 애플리케이션에 대한 디자인 지침

다음 섹션의 지침을 사용하여 Direct3D 9Ex Flip Model 애플리케이션을 디자인합니다.

Blt 모드에서 별도의 HWND에 있는 대칭 이동 모드 사용

애플리케이션은 Blt 모드 Present Direct3D 9Ex, 다른 버전의 Direct3D 또는 GDI를 비롯한 다른 API의 대상이 아닌 HWND에 있는 Direct3D 9Ex 대칭 이동 모드를 사용해야 합니다. Flip 모드 Present를 사용하여 자식 창에 프레젠테이션할 수 있습니다. 즉, 애플리케이션은 다음 그림과 같이 동일한 HWND에서 Blt 모델과 혼합되지 않은 경우 Flip 모델을 사용할 수 있습니다.

각각 고유한 hwnd가 있는 direct3d 부모 창 및 gdi 자식 창 그림

각각 고유한 hwnd가 있는 gdi 부모 창 및 direct3d 자식 창의 그림

Blt Model은 표면의 추가 복사본을 유지 관리하므로 Direct3D 및 GDI의 증분 업데이트를 통해 GDI 및 기타 Direct3D 콘텐츠를 동일한 HWND에 추가할 수 있습니다. 대칭 이동 모델을 사용하면 DWM에 전달되는 D3DSWAPEFFECT_FLIPEX 스왑 체인의 Direct3D 9Ex 콘텐츠만 표시됩니다. 다음 그림과 같이 다른 모든 Blt Model Direct3D 또는 GDI 콘텐츠 업데이트는 무시됩니다.

대칭 이동 모델을 사용하고 direct3d 및 gdi 콘텐츠가 동일한 hwnd에 있는 경우 표시되지 않을 수 있는 gdi 텍스트 그림

dwm이 활성화되고 애플리케이션이 창 모드에 있는 direct3d 및 gdi 콘텐츠의 그림

따라서 Direct3D 9Ex Flip 모델만으로 전체 HWND로 렌더링되는 스왑 체인 버퍼 표면에 대칭 이동 모델을 사용하도록 설정해야 합니다.

GDI의 ScrollWindow 또는 ScrollWindowEx에서 대칭 이동 모델을 사용하지 마세요.

일부 Direct3D 9Ex 애플리케이션은 GDI의 ScrollWindow 또는 ScrollWindowEx 함수를 사용하여 사용자 스크롤 이벤트가 트리거될 때 창 콘텐츠를 업데이트합니다. ScrollWindow 및 ScrollWindowEx는 창이 스크롤될 때 화면에서 창 내용의 Blt을 수행합니다. 이러한 함수에는 GDI 및 Direct3D 9Ex 콘텐츠에 대한 Blt 모델 업데이트도 필요합니다. 두 함수 중 하나를 사용하는 애플리케이션은 애플리케이션이 창 모드이고 DWM이 사용하도록 설정된 경우 화면에 표시되는 창 내용이 반드시 표시되지는 않습니다. 애플리케이션에서 GDI의 ScrollWindow 및 ScrollWindowEx API를 사용하지 않고 스크롤에 대한 응답으로 화면에서 해당 콘텐츠를 다시 그리는 것이 좋습니다.

HWND당 하나의 D3DSWAPEFFECT_FLIPEX 스왑 체인 사용

Flip 모델을 사용하는 애플리케이션은 동일한 HWND를 대상으로 하는 여러 Flip Model 스왑 체인을 사용하면 안 됩니다.

Direct3D 9Ex Flip Model 애플리케이션의 프레임 동기화

현재 통계는 미디어 애플리케이션이 비디오 및 오디오 스트림을 동기화하고 비디오 재생 결함에서 복구하는 데 사용하는 프레임 타이밍 정보입니다. 현재 통계 가용성을 사용하도록 설정하려면 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 대칭 이동 모델은 D3DPRESENT_INTERVAL_IMMEDIATE 프레젠테이션 플래그 동작을 적용하는 D3DPRESENT_FORCEIMMEDIATE 프레젠테이션 플래그를 추가합니다. Direct3D 9Ex 애플리케이션은 다음과 같이 애플리케이션이 IDirect3DDevice9Ex::P resentEx에 전달하는 dwFlags 매개 변수에 이러한 프레젠테이션 플래그를 지정합니다.

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 호출에서 D3DSWAPEFFECT_FLIPEXD3DPRESENT_PARAMETERSSwapEffect 멤버를 설정하는 경우.

D3DPRESENT_FORCEIMMEDIATE

(Windows 7에만 해당)

이 플래그는 애플리케이션이 CreateDeviceEx호출에서D3DSWAPEFFECT_FLIPEX D3DPRESENT_PARAMETERSSwapEffect 멤버를 설정하는 경우에만 지정할 수 있습니다. 애플리케이션은 이 플래그를 사용하여 DWM Present 큐의 뒷부분에 있는 여러 프레임으로 표면을 즉시 업데이트하여 기본적으로 중간 프레임을 건너뛸 수 있습니다.

창이 있는 FlipEx 지원 애플리케이션은 이 플래그를 사용하여 중간 프레임을 건너뛰고 나중에 DWM Present 큐에 있는 프레임으로 표면을 즉시 업데이트할 수 있습니다. 이는 늦게 감지된 프레임을 삭제하고 컴퍼지션 시 후속 프레임을 표시하려는 미디어 애플리케이션에 특히 유용합니다. 이 플래그를 잘못 지정하면 IDirect3DDevice9Ex::P resentEx가 잘못된 매개 변수 오류를 반환합니다.

현재 통계 정보를 얻기 위해 애플리케이션은 IDirect3DSwapChain9Ex::GetPresentStatistics API를 호출하여 D3DPRESENTSTATS 구조를 가져옵니다.

D3DPRESENTSTATS 구조체에는 IDirect3DDevice9Ex::P resentEx 호출에 대한 통계가 포함되어 있습니다. D3DCREATE_ENABLE_PRESENTSTATS 플래그와 함께 IDirect3D9Ex::CreateDeviceEx 호출을 사용하여 디바이스를 만들어야 합니다. 그렇지 않으면 GetPresentStatistics 에서 반환된 데이터가 정의되지 않습니다. Flip-Model 지원 Direct3D 9Ex 스왑 체인은 창 모드와 전체 화면 모드 모두에서 현재 통계 정보를 제공합니다.

창 모드의 Blt-Model 지원 Direct3D 9Ex 스왑 체인의 경우 모든 D3DPRESENTSTATS 구조 값은 0이 됩니다.

FlipEx 현재 통계의 경우 GetPresentStatistics는 다음과 같은 상황에서 D3DERR_PRESENT_STATISTICS_DISJOINT 반환합니다.

  • 시퀀스의 시작을 나타내는 GetPresentStatistics 에 대한 첫 번째 호출
  • DWM이 켜기에서 끄기로 전환
  • 모드 변경: 창 모드에서 전체 화면 또는 전체 화면으로 전환

편의를 위해 GetPresentStatistics 구문은 여기에서 반복됩니다.

HRESULT GetPresentStatistics(
  D3DPRESENTSTATS * pPresentationStatistics
);

IDirect3DSwapChain9Ex::GetLastPresentCount 메서드는 스왑 체인과 연결된 디스플레이 디바이스에서 마지막으로 성공한 Present 호출의 Present ID인 마지막 PresentCount를 반환합니다. 이 Present ID는 D3DPRESENTSTATS 구조체의 PresentCount 멤버 값입니다. Blt-Model 지원 Direct3D 9Ex 스왑 체인의 경우 창 모드에서는 모든 D3DPRESENTSTATS 구조 값이 0이 됩니다.

편의를 위해 여기서는 IDirect3DSwapChain9Ex::GetLastPresentCount 구문이 반복됩니다.

HRESULT GetLastPresentCount(
  UINT * pLastPresentCount
);

Windows 7용 Direct3D 9Ex 애플리케이션을 수정할 때 D3DPRESENTSTATS 구조체에 대한 다음 정보를 고려해야 합니다.

  • GetLastPresentCount가 반환하는 PresentCount 값은 dwFlags 매개 변수에 지정된 D3DPRESENT_DONOTWAIT PresentEx 호출이 실패를 반환할 때 업데이트되지 않습니다.
  • D3DPRESENT_DONOTFLIP PresentEx 를 호출하면 GetPresentStatistics 호출이 성공하지만 애플리케이션이 창 모드에 있을 때 업데이트된 D3DPRESENTSTATS 구조를 반환하지 않습니다.
  • PresentRefreshCountD3DPRESENTSTATSSyncRefreshCount:
    • PresentRefreshCount 는 애플리케이션이 모든 vsync에 표시되는 경우 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 Statistics 샘플의 Walk-Through

Direct3D 9Ex 샘플에 FlipEx 프레젠테이션을 옵트인하려면

  1. 샘플 애플리케이션이 Windows 7 이상 운영 체제 버전에서 실행되고 있는지 확인합니다.
  2. CreateDeviceEx 호출에서 D3DSWAPEFFECT_FLIPEX D3DPRESENT_PARAMETERS SwapEffect 멤버를 설정합니다.
    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 관련 Present Statistics를 옵트인하려면

    // 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. 성공적으로 제출된 Present의 Present ID(PresentCount) 및 연결된 계산/예상 PresentRefreshCount를 모두 저장할 도우미 Present 큐 구조를 만듭니다.

  4. 결함 발생을 검색하려면 다음을 수행합니다.

    • GetPresentStatistics를 호출합니다.
    • 현재 통계를 얻은 프레임의 프레임이 표시되는 현재 ID(PresentCount) 및 vsync 수를 가져옵니다.
    • Present ID와 연결된 예상 PresentRefreshCount(샘플 코드의 TargetRefresh)를 검색합니다.
    • 실제 PresentRefreshCount가 예상보다 최신인 경우 결함이 발생했습니다.
  5. 결함에서 복구하려면 다음을 수행합니다.

    • 건너뛸 프레임 수를 계산합니다(샘플 코드에서 iImmediates 변수 g_).
    • 건너뛴 프레임에 간격 D3DPRESENT_FORCEIMMEDIATE 표시합니다.

결함 검색 및 복구에 대한 고려 사항

  1. 글리치 복구는 N(샘플 코드의 g_iQueueDelay 변수) Present 호출 수를 사용합니다. 여기서 N(g_iQueueDelay)은 Present 큐의 길이를 더한 g_iImmediates 같습니다.

    • 프레젠테이션 간격 D3DPRESENT_FORCEIMMEDIATE 있는 프레임 건너뛰기 및 추가
    • 처리해야 하는 큐에 대기 중인 선물
  2. 한도를 결함 길이(샘플의 GLITCH_RECOVERY_LIMIT)로 설정합니다. 샘플 애플리케이션이 너무 긴 결함(즉, 60Hz 모니터의 경우 1초 또는 60개 vsyncs)에서 복구할 수 없는 경우 간헐적인 애니메이션으로 이동하여 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입니다.

    렌더링된 프레임 및 현재 큐에 해당하는 그림

    프레임 A는 동기화 간격 수 1에서 화면으로 이동하는 것을 대상으로 하지만 동기화 간격 수가 4인 것으로 감지되었습니다. 따라서 결함이 발생했습니다. 이후 3개의 프레임에는 D3DPRESENT_INTERVAL_FORCEIMMEDIATE 표시됩니다. 결함은 복구되기 전에 총 8개의 Present 호출을 수행해야 합니다. 다음 프레임은 대상 동기화 간격 수에 따라 표시됩니다.

프레임 동기화에 대한 프로그래밍 권장 사항 요약

  • 제출된 모든 Presents의 모든 LastPresentCount ID( GetLastPresentCount를 통해 얻은) 및 연결된 예상 PresentRefreshCount의 백업 목록을 만듭니다.

    참고

    애플리케이션이 D3DPRESENT_DONOTFLIP 함께 PresentEx 를 호출하면 GetPresentStatistics 호출이 성공하지만 애플리케이션이 창 모드에 있을 때 업데이트된 D3DPRESENTSTATS 구조를 반환하지 않습니다.

  • GetPresentStatistics를 호출하여 표시된 프레임의 각 Present ID와 연결된 실제 PresentRefreshCount를 가져와서 애플리케이션이 호출에서 실패 반환을 처리하는지 확인합니다.

  • 실제 PresentRefreshCount가 예상 PresentRefreshCount보다 이후인 경우 결함이 검색됩니다. 지연 프레임의 Present를 D3DPRESENT_FORCEIMMEDIATE 제출하여 보정합니다.

  • 한 프레임이 Present 큐에 늦게 표시되면 모든 후속 대기 중인 프레임이 늦게 표시됩니다. D3DPRESENT_FORCEIMMEDIATE 큐에 대기된 모든 프레임 후에 표시할 다음 프레임만 수정합니다. 따라서 Present 큐 또는 백버퍼 수가 너무 길어서는 안 되므로 따라 잡을 프레임 결함이 적습니다. 최적의 백 버퍼 수는 2~4개입니다.

  • 예상 PresentRefreshCount가 실제 PresentRefreshCount보다 나중인 경우 DWM 제한이 발생했을 수 있습니다. 가능한 솔루션은 다음과 같습니다.

    • 큐 길이 표시 감소
    • 현재 큐 길이를 줄이는 것 외에 다른 수단을 사용하여 GPU 메모리 요구 사항 감소(즉, 품질 감소, 효과 제거 등)
    • 일반적으로 DWM 제한을 방지하기 위해 DwmEnableMMCSS 지정
  • 다음 시나리오에서 애플리케이션 표시 기능 및 프레임 통계 성능을 확인합니다.

    • DWM 켜기 및 끄기
    • 전체 화면 전용 및 창 모드
    • 낮은 기능 하드웨어
  • 애플리케이션이 D3DPRESENT_FORCEIMMEDIATE Present를 사용하여 많은 수의 결함이 있는 프레임에서 복구할 수 없는 경우 다음 작업을 수행할 수 있습니다.

    • 더 적은 워크로드로 렌더링하여 CPU 및 GPU 사용량을 줄입니다.
    • 비디오 디코딩의 경우 품질을 줄이고 CPU 및 GPU 사용량을 줄여 더 빠르게 디코딩합니다.

Direct3D 9Ex 개선 사항에 대한 결론

Windows 7에서는 프레젠테이션 중에 비디오 또는 계기 프레임 속도를 표시하는 애플리케이션이 Flip Model을 옵트인할 수 있습니다. Flip Model Direct3D 9Ex와 관련된 현재 통계 개선 사항은 결함 감지 및 복구에 대한 실시간 피드백과 함께 프레임당 프레젠테이션 속도를 동기화하는 애플리케이션에 도움이 될 수 있습니다. Direct3D 9Ex Flip 모델을 채택하는 개발자는 GDI 콘텐츠 및 프레임 속도 동기화에서 별도의 HWND를 대상으로 지정해야 합니다. 이 항목의 세부 정보 및 MSDN 설명서를 참조하세요. 추가 설명서는 MSDN의 DirectX 개발자 센터를 참조하세요.

활용 방안

프레젠테이션 프레임 속도를 동기화하거나 디스플레이 결함에서 복구하려는 애플리케이션을 만들 때 Windows 7에서 Direct3D 9Ex Flip Model 및 현재 통계를 사용하는 것이 좋습니다.

MSDN의 DirectX 개발자 센터