コンポジション スワップチェーンのコード例
以下の例では、Windows Implementation Libraries (WIL) を使用します。 WIL をインストールするための便利な方法は、Visual Studio で、[プロジェクト]>[NuGet パッケージの管理...]>[参照] とクリックし、検索ボックスに「Microsoft.Windows.ImplementationLibrary」と入力するか貼り付けます。検索結果の項目を選択し、[インストール] をクリックしてそのプロジェクトにパッケージをインストールします。
例 1 - コンポジション スワップチェーン API をサポートするシステムでプレゼンテーション マネージャーを作成する
メンションしたように、コンポジション スワップチェーン API では、サポートされているドライバーが機能する必要があります。 次の例は、システムが API をサポートしている場合に、アプリケーションでプレゼンテーション マネージャーを作成する方法を示しています。 これは、API がサポートされている場合にのみプレゼンテーション マネージャーを返す TryCreate
-style 関数として 示されています。 この関数では、プレゼンテーション マネージャーをバックする Direct3D デバイスを適切に作成する方法も示します。
C++ の例
bool TryCreatePresentationManager(
_In_ bool requestDirectPresentation,
_Out_ ID3D11Device** ppD3DDevice,
_Outptr_opt_result_maybenull_ IPresentationManager **ppPresentationManager)
{
// Null the presentation manager and Direct3D device initially
*ppD3DDevice = nullptr;
*ppPresentationManager = nullptr;
// Direct3D device creation flags. The composition swapchain API requires that applications disable internal
// driver threading optimizations, as these optimizations are incompatible with the
// composition swapchain API. If this flag is not present, then the API will fail the call to create the
// presentation factory.
UINT deviceCreationFlags =
D3D11_CREATE_DEVICE_BGRA_SUPPORT |
D3D11_CREATE_DEVICE_SINGLETHREADED |
D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS;
// Create the Direct3D device.
com_ptr_failfast<ID3D11DeviceContext> d3dDeviceContext;
FAIL_FAST_IF_FAILED(D3D11CreateDevice(
nullptr, // No adapter
D3D_DRIVER_TYPE_HARDWARE, // Hardware device
nullptr, // No module
deviceCreationFlags, // Device creation flags
nullptr, 0, // Highest available feature level
D3D11_SDK_VERSION, // API version
ppD3DDevice, // Resulting interface pointer
nullptr, // Actual feature level
&d3dDeviceContext)); // Device context
// Call the composition swapchain API export to create the presentation factory.
com_ptr_failfast<IPresentationFactory> presentationFactory;
FAIL_FAST_IF_FAILED(CreatePresentationFactory(
(*ppD3DDevice),
IID_PPV_ARGS(&presentationFactory)));
// Determine whether the system is capable of supporting the composition swapchain API based
// on the capability that's reported by the presentation factory. If your application
// wants direct presentation (that is, presentation without the need for DWM to
// compose, using MPO or iflip), then we query for direct presentation support.
bool isSupportedOnSystem;
if (requestDirectPresentation)
{
isSupportedOnSystem = presentationFactory->IsPresentationSupportedWithIndependentFlip();
}
else
{
isSupportedOnSystem = presentationFactory->IsPresentationSupported();
}
// Create the presentation manager if it is supported on the current system.
if (isSupportedOnSystem)
{
FAIL_FAST_IF_FAILED(presentationFactory->CreatePresentationManager(ppPresentationManager));
}
return isSupportedOnSystem;
}
例 2 - プレゼンテーション マネージャーとプレゼンテーション 画面を作成する
次の例は、アプリケーションでプレゼンテーション マネージャーとプレゼンテーション サーフェイスを作成して、ビジュアル ツリー内のビジュアルにバインドする方法を示しています。 以降の例では、プレゼンテーション サーフェイスを DirectComposition ビジュアル ツリーと Windows.UI.Composition ビジュアル ツリーの両方にバインドする方法を示します。
C++ の例 (DCompositionGetTargetStatistics)
bool MakePresentationManagerAndPresentationSurface(
_Out_ ID3D11Device** ppD3dDevice,
_Out_ IPresentationManager** ppPresentationManager,
_Out_ IPresentationSurface** ppPresentationSurface,
_Out_ unique_handle& compositionSurfaceHandle)
{
// Null the output pointers initially.
*ppD3dDevice = nullptr;
*ppPresentationManager = nullptr;
*ppPresentationSurface = nullptr;
compositionSurfaceHandle.reset();
com_ptr_failfast<IPresentationManager> presentationManager;
com_ptr_failfast<IPresentationSurface> presentationSurface;
com_ptr_failfast<ID3D11Device> d3d11Device;
// Call the function we defined previously to create a Direct3D device and presentation manager, if
// the system supports it.
if (TryCreatePresentationManager(
true, // Request presentation with independent flip.
&d3d11Device,
&presentationManager) == false)
{
// Return 'false' out of the call if the composition swapchain API is unsupported. Assume the caller
// will handle this somehow, such as by falling back to DXGI.
return false;
}
// Use DirectComposition to create a composition surface handle.
FAIL_FAST_IF_FAILED(DCompositionCreateSurfaceHandle(
COMPOSITIONOBJECT_ALL_ACCESS,
nullptr,
compositionSurfaceHandle.addressof()));
// Create presentation surface bound to the composition surface handle.
FAIL_FAST_IF_FAILED(presentationManager->CreatePresentationSurface(
compositionSurfaceHandle.get(),
presentationSurface.addressof()));
// Return the Direct3D device, presentation manager, and presentation surface to the caller for future
// use.
*ppD3dDevice = d3d11Device.detach();
*ppPresentationManager = presentationManager.detach();
*ppPresentationSurface = presentationSurface.detach();
// Return 'true' out of the call if the composition swapchain API is supported and we were able to
// create a presentation manager.
return true;
}
例 3 - プレゼンテーション サーフェイスを Windows.UI.Composition サーフェス ブラシにバインドする
次の例は、上の例 で作成したように、アプリケーションがプレゼンテーション サーフェイスにバインドされたコンポジション サーフェス ハンドルを Windows.UI.Composition (WinComp) サーフェス ブラシにバインドする方法を示しています。このブラシは、アプリケーションのビジュアル ツリー内のスプライト ビジュアルにバインドできます。
C++ の例
void BindPresentationSurfaceHandleToWinCompTree(
_In_ ICompositor * pCompositor,
_In_ ISpriteVisual * pVisualToBindTo, // The sprite visual we want to bind to.
_In_ unique_handle& compositionSurfaceHandle)
{
// QI an interop compositor from the passed compositor.
com_ptr_failfast<ICompositorInterop> compositorInterop;
FAIL_FAST_IF_FAILED(pCompositor->QueryInterface(IID_PPV_ARGS(&compositorInterop)));
// Create a composition surface for the presentation surface's composition surface handle.
com_ptr_failfast<ICompositionSurface> compositionSurface;
FAIL_FAST_IF_FAILED(compositorInterop->CreateCompositionSurfaceForHandle(
compositionSurfaceHandle.get(),
&compositionSurface));
// Create a composition surface brush, and bind the surface to it.
com_ptr_failfast<ICompositionSurfaceBrush> surfaceBrush;
FAIL_FAST_IF_FAILED(pCompositor->CreateSurfaceBrush(&surfaceBrush));
FAIL_FAST_IF_FAILED(surfaceBrush->put_Surface(compositionSurface.get()));
// Bind the brush to the visual.
auto brush = surfaceBrush.query<ICompositionBrush>();
FAIL_FAST_IF_FAILED(pVisualToBindTo->put_Brush(brush.get()));
}
例 4 - プレゼンテーション サーフェイスを DirectComposition ビジュアルにバインドする
次の例は、アプリケーションがビジュアル ツリー内の DirectComposition (DComp) ビジュアルにプレゼンテーション サーフェイスをバインドする方法を示しています。
C++ の例
void BindPresentationSurfaceHandleToDCompTree(
_In_ IDCompositionDevice* pDCompDevice,
_In_ IDCompositionVisual* pVisualToBindTo,
_In_ unique_handle& compositionSurfaceHandle) // The composition surface handle that was
// passed to CreatePresentationSurface.
{
// Create a DComp surface to wrap the presentation surface.
com_ptr_failfast<IUnknown> dcompSurface;
FAIL_FAST_IF_FAILED(pDCompDevice->CreateSurfaceFromHandle(
compositionSurfaceHandle.get(),
&dcompSurface));
// Bind the presentation surface to the visual.
FAIL_FAST_IF_FAILED(pVisualToBindTo->SetContent(dcompSurface.get()));
}
例 5 - プレゼンテーション サーフェイスにアルファ モードと色空間を設定する
次の例は、アプリケーションでプレゼンテーション サーフェイスにアルファ モードと色空間を設定する方法を示しています。 アルファ モードでは、テクスチャ内のアルファ チャネルを解釈する必要があるかどうか、およびその方法が記述されます。 色空間は、テクスチャ ピクセルが参照する色空間を表します。
このようなプロパティの更新はすべて、アプリケーションの次の存在の一部として有効になり、その存在の一部であるバッファー更新と共にアトミックに有効になります。 また、アプリケーションが必要に応じて、バッファーをまったく更新せず、代わりにプロパティの更新のみで構成される場合もあります。 特定の存在でバッファーが更新されないプレゼンテーション サーフェイスはメインそのプレゼンテーションの前にバインドされていたバッファーにバインドされます。
C++ の例
void SetAlphaModeAndColorSpace(
_In_ IPresentationSurface* pPresentationSurface)
{
// Set alpha mode.
FAIL_FAST_IF_FAILED(pPresentationSurface->SetAlphaMode(DXGI_ALPHA_MODE_IGNORE));
// Set color space to full RGB.
FAIL_FAST_IF_FAILED(pPresentationSurface->SetColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709));
}
例 6 - プレゼンテーション 画面にレターボックスの余白を設定する
次の例は、アプリケーションでプレゼンテーション画面にレターボックスの余白を指定する方法を示しています。 レターボックス化は、コンテンツと表示されているディスプレイ デバイス間の縦横比の違いを考慮して、サーフェス コンテンツ自体の外部で指定された領域を塗りつぶすために使用されます。 この良い例は、スクリーンシアター用にフォーマットされた映画をPCで見ているときに、コンテンツの上下に表示されることが多い黒い バー です。 コンポジション スワップチェーン API を使用すると、そのような場合にレターボックス化をレンダリングする余白を指定できます。
現在、レターボックス領域は常に不透明な黒で塗りつぶされています。
ビジュアル ツリー コンテンツとして、プレゼンテーション サーフェイスはホスト ビジュアルの座標空間に存在します。 レターボックス化が存在する場合、ビジュアルの座標空間の原点はレターボックス化の左上に対応します。 つまり、バッファー自体はビジュアルの座標空間へのオフセットが存在します。 境界は、バッファーのサイズとレターボックス化のサイズとして計算されます。
次に、ビジュアル座標空間にバッファーとレターボックスが存在する場所と、境界を計算する方法を示します。
最後に、プレゼンテーション サーフェイスに適用された変換にオフセットが含まれている場合、バッファーまたはレターボックス化でカバーされていない領域はコンテンツの境界外と見なされ、他のビジュアル ツリー コンテンツがコンテンツ境界外でどのように処理されるかと同様に、透明として扱われます。
レターボックス化と変換の相互作用
アプリケーションがバッファーに適用するスケールに関係なく、一貫性のあるレターボックス余白サイズを提供するために、DWM は、スケールに関係なく、プレゼンテーション画面に適用された変換の結果を考慮して、一貫したサイズで余白をレンダリングしようとします。
言い換えて言えば、レターボックス化の余白は、プレゼンテーション サーフェイスの変換が適用される前に技術的に適用されますが、その変換の一部である可能性のあるスケールを補正します。 つまり、プレゼンテーション サーフェイスの変換は、スケールする変換の一部と変換の再メインダーという 2 つのコンポーネントに分解されます。
たとえば、100px のレターボックス余白があり、プレゼンテーション サーフェイスに変換が適用されていない場合、結果のバッファーはスケールなしでレンダリングされ、レターボックスの余白は幅 100px になります。
もう 1 つの例として、100px の文字ボックス化余白と、プレゼンテーション サーフェイスに 2 倍のスケール変換が適用された場合、結果のバッファーは 2 倍のスケールでレンダリングされ、画面に表示されるレターボックスの余白はすべてのサイズで 100px のままです。
もう 1 つの例として、45 度回転変換を使用すると、結果として得られるレターボックスの余白は 100px で、レターボックス化の余白はバッファーと共に回転されます。
もう 1 つの例として、スケール 2x と回転 45 度変換では、イメージが回転およびスケーリングされ、レターボックスの余白も幅 100 ピクセルになり、バッファーと共に回転されます。
結果の X または Y スケールが 0 の場合など、アプリケーションがプレゼンテーション サーフェイスに適用する変換からスケール変換を明確に抽出できない場合、変換全体が適用されたレターボックス余白がレンダリングされ、スケールの補正は試みられません。
C++ の例
void SetContentLayoutAndFill(
_In_ IPresentationSurface* pPresentationSurface)
{
// Set layout properties. Each layout property is described below.
// The source RECT describes the area from the bound buffer that will be sampled from. The
// RECT is in source texture coordinates. Below we indicate that we'll sample from a
// 100x100 area on the source texture.
RECT sourceRect;
sourceRect.left = 0;
sourceRect.top = 0;
sourceRect.right = 100;
sourceRect.bottom = 100;
FAIL_FAST_IF_FAILED(pPresentationSurface->SetSourceRect(&sourceRect));
// The presentation transform defines how the source rect will be transformed when
// rendering the buffer. In this case, we indicate we want to scale it 2x in both
// width and height, and we also want to offset it 50 pixels to the right.
PresentationTransform transform = { 0 };
transform.M11 = 2.0f; // X scale 2x
transform.M22 = 2.0f; // Y scale 2x
transform.M31 = 50.0f; // X offset 50px
FAIL_FAST_IF_FAILED(pPresentationSurface->SetTransform(&transform));
// The letterboxing parameters describe how to letterbox the content. Letterboxing
// is commonly used to fill area not covered by video/surface content when scaling to
// an aspect ratio that doesn't match the aspect ratio of the surface itself. For
// example, when viewing content formatted for the theater on a 1080p home screen, one
// can typically see black "bars" on the top and bottom of the video, covering the space
// on screen that wasn't covered by the video due to the differing aspect ratios of the
// content and the display device. The composition swapchain API allows the user to specify
// letterboxing margins, which describe the number of pixels to surround the surface
// content with on screen. In this case, surround the top and bottom of our content with
// 100 pixel tall letterboxing.
FAIL_FAST_IF_FAILED(pPresentationSurface->SetLetterboxingMargins(
0.0f,
100.0f,
0.0f,
100.0f));
}
例 7 - プレゼンテーション 画面にコンテンツ制限を設定する
次の例は、保護されたコンテンツの目的で、他のアプリケーションが (PrintScreen、DDA、Capture API などのテクノロジから) プレゼンテーション サーフェイスの内容を読み戻さないようにする方法を示しています。 また、プレゼンテーション サーフェイスの表示を 1 つの DXGI 出力のみに制限する方法も示します。
コンテンツの読み取り戻しが無効になっている場合、アプリケーションが読み取り戻そうとすると、キャプチャされたイメージには、プレゼンテーション 画面の代わりに不透明な黒が含まれます。 プレゼンテーション サーフェイスが IDXGIOutput に表示制限されている場合は、他のすべての出力で不透明な黒として表示されます。
C++ の例
void SetContentRestrictions(
_In_ IPresentationSurface* pPresentationSurface,
_In_ IDXGIOutput* pOutputToRestrictTo)
{
// Disable readback of the surface via printscreen, bitblt, etc.
FAIL_FAST_IF_FAILED(pPresentationSurface->SetDisableReadback(true));
// Restrict display of surface to only the passed output.
FAIL_FAST_IF_FAILED(pPresentationSurface->RestrictToOutput(pOutputToRestrictTo));
}
例 8 - プレゼンテーション マネージャーへのプレゼンテーション バッファーの追加
次の例は、アプリケーションで Direct3D テクスチャのセットを割り当て、プレゼンテーション バッファーとして使用するためにプレゼンテーション マネージャーに追加して、プレゼンテーション サーフェイスに表示する方法を示しています。
メンション、テクスチャを登録できるプレゼンテーション マネージャーの数に関する制限はありません。 ただし、ほとんどの通常のユース ケースでは、テクスチャは 1 つのプレゼンテーション マネージャーにのみ登録されます。
また、API は、プレゼンテーション マネージャーでテクスチャをプレゼンテーション バッファーとして登録するときに共有バッファー ハンドルを通貨として受け入れるので、DXGI は 1 つの texture2D に対して複数の共有バッファーを作成できるため、アプリケーションで 1 つのテクスチャに対して複数の共有バッファー ハンドルを作成し、両方をプレゼンテーション マネージャーに登録することが技術的に可能であることに注意してください。 基本的には、同じバッファーを複数回追加する効果を持ちます。 これは、一意に追跡される 2 つのプレゼンテーション バッファーが実際には同じテクスチャに対応するため、プレゼンテーション マネージャーが提供する同期メカニズムを中断するため、お勧めできません。 これは高度な API であり、実際にはこのケースが発生したときにこのケースを検出するのは実装が非常に困難であるため、API はこのシナリオの検証を試みません。
C++ の例
void AddBuffersToPresentationManager(
_In_ ID3D11Device* pD3D11Device, // The backing Direct3D device
_In_ IPresentationManager* pPresentationManager, // Previously-made presentation manager
_In_ UINT bufferWidth, // The width of the buffers to add
_In_ UINT bufferHeight, // The height of the buffers to add
_In_ UINT numberOfBuffersToAdd, // The number of buffers to add to the presentation manager
_Out_ vector<com_ptr_failfast<ID3D11Texture2D>>& textures, // Array of textures returned
_Out_ vector<com_ptr_failfast<IPresentationBuffer>>& presentationBuffers) // Array of presentation buffers returned
{
// Clear the returned vectors initially.
textures.clear();
presentationBuffers.clear();
// Add the desired buffers to the presentation manager.
for (UINT i = 0; i < numberOfBuffersToAdd; i++)
{
com_ptr_failfast<ID3D11Texture2D> texture;
com_ptr_failfast<IPresentationBuffer> presentationBuffer;
// Call our helper to make a new buffer of the desired type.
AddNewPresentationBuffer(
pD3D11Device,
pPresentationManager,
bufferWidth,
bufferHeight,
&texture,
&presentationBuffer);
// Track our buffers in our own set of vectors.
textures.push_back(texture);
presentationBuffers.push_back(presentationBuffer);
}
}
void AddNewPresentationBuffer(
_In_ ID3D11Device* pD3D11Device,
_In_ IPresentationManager* pPresentationManager,
_In_ UINT bufferWidth,
_In_ UINT bufferHeight,
_Out_ ID3D11Texture2D** ppTexture2D,
_Out_ IPresentationBuffer** ppPresentationBuffer)
{
com_ptr_failfast<ID3D11Texture2D> texture2D;
unique_handle sharedResourceHandle;
// Create a shared Direct3D texture and handle with the passed attributes.
MakeD3D11Texture(
pD3D11Device,
bufferWidth,
bufferHeight,
&texture2D,
out_param(sharedResourceHandle));
// Add the texture2D to the presentation manager, and get back a presentation buffer.
com_ptr_failfast<IPresentationBuffer> presentationBuffer;
FAIL_FAST_IF_FAILED(pPresentationManager->AddBufferFromSharedHandle(
sharedResourceHandle.get(),
&presentationBuffer));
// Return back the texture and buffer presentation buffer.
*ppTexture2D = texture2D.detach();
*ppPresentationBuffer = presentationBuffer.detach();
}
void MakeD3D11Texture(
_In_ ID3D11Device* pD3D11Device,
_In_ UINT textureWidth,
_In_ UINT textureHeight,
_Out_ ID3D11Texture2D** ppTexture2D,
_Out_ HANDLE* sharedResourceHandle)
{
D3D11_TEXTURE2D_DESC textureDesc = {};
// Width and height can be anything within max texture size of the adapter backing the Direct3D
// device.
textureDesc.Width = textureWidth;
textureDesc.Height = textureHeight;
// MipLevels and ArraySize must be 1.
textureDesc.MipLevels = 1;
textureDesc.ArraySize = 1;
// Format can be one of the following:
// DXGI_FORMAT_B8G8R8A8_UNORM
// DXGI_FORMAT_R8G8B8A8_UNORM
// DXGI_FORMAT_R16G16B16A16_FLOAT
// DXGI_FORMAT_R10G10B10A2_UNORM
// DXGI_FORMAT_NV12
// DXGI_FORMAT_YUY2
// DXGI_FORMAT_420_OPAQUE
// For this
textureDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
// SampleDesc count and quality must be 1 and 0 respectively.
textureDesc.SampleDesc.Count = 1;
textureDesc.SampleDesc.Quality = 0;
// Usage must be D3D11_USAGE_DEFAULT.
textureDesc.Usage = D3D11_USAGE_DEFAULT;
// BindFlags must include D3D11_BIND_SHADER_RESOURCE for RGB textures, and D3D11_BIND_DECODER
// for YUV textures. For RGB textures, it is likely your application will want to specify
// D3D11_BIND_RENDER_TARGET in order to render to it.
textureDesc.BindFlags =
D3D11_BIND_SHADER_RESOURCE |
D3D11_BIND_RENDER_TARGET;
// MiscFlags should include D3D11_RESOURCE_MISC_SHARED and D3D11_RESOURCE_MISC_SHARED_NTHANDLE,
// and might also include D3D11_RESOURCE_MISC_SHARED_DISPLAYABLE if your application wishes to
// qualify for MPO and iflip. If D3D11_RESOURCE_MISC_SHARED_DISPLAYABLE is not provided, then the
// content will not qualify for MPO or iflip, but can still be composed by DWM
textureDesc.MiscFlags =
D3D11_RESOURCE_MISC_SHARED |
D3D11_RESOURCE_MISC_SHARED_NTHANDLE |
D3D11_RESOURCE_MISC_SHARED_DISPLAYABLE;
// CPUAccessFlags must be 0.
textureDesc.CPUAccessFlags = 0;
// Use Direct3D to create a texture 2D matching the desired attributes.
com_ptr_failfast<ID3D11Texture2D> texture2D;
FAIL_FAST_IF_FAILED(pD3D11Device->CreateTexture2D(&textureDesc, nullptr, &texture2D));
// Create a shared handle for the texture2D.
unique_handle sharedBufferHandle;
auto dxgiResource = texture2D.query<IDXGIResource1>();
FAIL_FAST_IF_FAILED(dxgiResource->CreateSharedHandle(
nullptr,
GENERIC_ALL,
nullptr,
&sharedBufferHandle));
// Return the handle to the caller.
*ppTexture2D = texture2D.detach();
*sharedResourceHandle = sharedBufferHandle.release();
}
例 9 - プレゼンテーション マネージャーからのプレゼンテーション バッファーの削除とオブジェクトの有効期間
プレゼンテーション マネージャーからプレゼンテーション バッファーを削除するのは、IPresentationBuffer オブジェクトを 0 refcount に解放するのと同じくらい簡単です。 ただし、この場合、必ずしもプレゼンテーション バッファーを解放できるとは限りません。 プレゼンテーション バッファーが画面上に表示されている場合や、それを参照する未処理のプレゼンテーションがある場合など、バッファーがまだ使用されている場合があります。
つまり、プレゼンテーション マネージャーは、すべてのバッファーがアプリケーションによって直接、間接的にどのように使用されているかを追跡します。 表示されるバッファー、未処理のバッファー、参照バッファーを追跡し、バッファーが本当に使用されなくなるまで、バッファーが本当に解放または登録解除されないように、この状態をすべて内部的に追跡します。
バッファーの有効期間について考える良い方法は、アプリケーションが IPresentationBuffer を解放した場合、今後の呼び出しでそのバッファーを使用しなくなったことを API に伝えることです。 コンポジション スワップチェーン API は、バッファーが使用されている他の方法と同様に、これを追跡し、完全に安全な場合にのみバッファーを完全に解放します。
要するに、アプリケーションでは、 IPresentationBuffer が完了したら解放し、コンポジション スワップチェーン API が他のすべての使用も完了したときにバッファーを完全に解放することを認識する必要があります。
上記の概念の図を次に示します。
この例では、2 つのプレゼンテーション バッファーを持つプレゼンテーション マネージャーがあります。 バッファー 1 には、それを維持する 3 つの参照があります。
- アプリケーションは、それを参照する IPresentationBuffer を保持します。
- API の内部では、それがプレゼンテーション サーフェイスの現在のバインド バッファーとして格納されます。これは、アプリケーションがバインドした最後のバッファーであり、その後表示されたバッファーでした。
- プレゼンテーション画面にバッファー 1 を表示する予定の現在のキューにも未処理の存在があります。
バッファー 1 は、対応する Direct3D テクスチャ割り当ての参照をさらに保持します。 アプリケーションには、 その割り当てを参照する ID3D11Texture2D もあります。
バッファー 2 には、アプリケーション側からの未処理の参照はなく、参照するテクスチャの割り当てもありませんが、バッファー 2 は現在画面のプレゼンテーション画面に表示されているため、維持されています。 Present 1 が処理され、代わりにバッファー 1 が画面に表示されると、バッファー 2 はそれ以上参照を持たず、解放され、Direct3D texture2D の割り当てで参照が解放されます。
C++ の例
void ReleasePresentationManagerBuffersExample(
_In_ ID3D11Device* pD3D11Device,
_In_ IPresentationManager* pPresentationManager)
{
com_ptr_failfast<ID3D11Texture2D> texture2D;
com_ptr_failfast<IPresentationBuffer> newBuffer;
// Create a new texture/presentation buffer.
AddNewPresentationBuffer(
pD3D11Device,
pPresentationManager,
200, // Buffer width
200, // Buffer height
&texture2D,
&newBuffer);
// Release the IPresentationBuffer. This indicates that we have no more intention to use it
// from the application side. However, if we have any presents outstanding that reference
// the buffer, it will be kept alive internally and released when all outstanding presents
// have been retired.
newBuffer.reset();
// When the presentation buffer is truly released internally, it will in turn release its
// reference on the texture2D which it corresponds to. Your application must also release
// its own references to the texture2D for it to be released.
texture2D.reset();
}
例 10 - プレゼンテーション マネージャーでのバッファーのサイズ変更
次の例は、アプリケーションでプレゼンテーション バッファーの サイズ変更 を実行する方法を示しています。 たとえば、ムービー ストリーミング サービスが 480p をストリーミングしているのに、ネットワーク帯域幅が高いためにフォーマットを 1080p に切り替える場合は、バッファーを 480p から 1080p に再割り当てして、1080p コンテンツの表示を開始できるようにする必要があります。
DXGI では、これは サイズ変更 操作と呼ばれます。 DXGI は、すべてのバッファーをアトミックに再割り当てします。これはコストがかかり、画面上でグリッチが発生する可能性があります。 この例では、コンポジション スワップチェーン API で DXGI と同等のアトミック サイズ変更を実現する方法について説明します。 後の例では、サイズ変更を 千鳥状 に行い、複数のプレゼンテーション間で再割り当てを間隔を置き、アトミックスワップチェーンのサイズ変更よりも優れたパフォーマンスを実現する方法を示します。
C++ の例
void ResizePresentationManagerBuffersExample(
_In_ ID3D11Device* pD3D11Device,
_In_ IPresentationManager* pPresentationManager) // Previously-made presentation manager.
{
// Vectors representing the IPresentationBuffer and ID3D11Texture2D collections.
vector<com_ptr_failfast<ID3D11Texture2D>> textures;
vector<com_ptr_failfast<IPresentationBuffer>> presentationBuffers;
// Add 6 50x50 buffers to the presentation manager. See previous example for definition of
// this function.
AddBuffersToPresentationManager(
pD3D11Device,
pPresentationManager,
50, // Buffer width
50, // Buffer height
6, // Number of buffers
textures,
presentationBuffers);
// Release all the buffers we just added. The presentation buffers internally will be released
// when any other references are also removed (outstanding presents, display references, etc.).
textures.clear();
presentationBuffers.clear();
// Add 6 new 100x100 buffers to the presentation manager to replace the
// old ones we just removed.
AddBuffersToPresentationManager(
pD3D11Device,
pPresentationManager,
100, // Buffer width
100, // Buffer height
6, // Number of buffers
textures,
presentationBuffers);
}
例 11 - バッファー使用可能なイベントを使用してプレゼンテーションを同期し、プレゼンテーション マネージャーの失われたイベントを処理する
以前のプレゼンテーションで使用されたプレゼンテーション バッファーを含むプレゼンテーション バッファーを含むプレゼンテーションをアプリケーションで発行する場合は、プレゼンテーション バッファーへのレンダリングとプレゼンテーションの再表示のために、以前のプレゼンテーションが完了していることを確認する必要があります。 コンポジション スワップチェーン API には、この同期を容易にするさまざまなメカニズムがいくつか用意されています。 最も簡単なのは、プレゼンテーション バッファーの 使用可能なイベントです。これは、バッファーが使用できるようになったときに通知される NT イベント オブジェクトです (つまり、以前のすべてのプレゼンテーションが リタイア またはインベントリから 削除 された状態に入った) し、それ以外の場合は符号なしになります。 この例では、バッファーの使用可能なイベントを使用して、表示する使用可能なバッファーを選択する方法を示します。 また、DXGI デバイスの損失と道徳的に同等の デバイス損失 エラーをリッスンし、プレゼンテーション マネージャーの再作成を必要とする方法についても説明します。
プレゼンテーション マネージャーがエラーを失った
プレゼンテーション マネージャーは、回復できないエラーが内部的に発生すると、失われる可能性があります。 アプリケーションでは、失われたプレゼンテーション マネージャーを破棄し、代わりに使用する新しいプレゼンテーション マネージャーを作成する必要があります。 プレゼンテーション マネージャーが失われると、それ以上のプレゼンテーションの受け入れは停止します。 紛失したプレゼンテーション マネージャーで Present を呼び出そうとすると、 PRESENTATION_ERROR_LOST が返されます。 ただし、他のすべてのメソッドは正常に機能します。 これは、アプリケーションが現在の呼び出しで PRESENTATION_ERROR_LOST にチェックするだけで済み、すべての API 呼び出しで失われたエラーを予測/処理する必要がないことを保証するためです。 アプリケーションが現在の呼び出しの外部でエラーをチェックまたは通知する場合は、 IPresentationManager::GetLostEvent によって返される失われたイベントを使用できます。
キューとタイミングを表示する
プレゼンテーション マネージャーによって発行されたプレゼンテーションでは、対象となる特定の時刻を指定できます。 この目標時間は、システムが現在を表示しようとする理想的な時間です。 画面は有限の周期で更新されるため、現在は指定された時刻に正確に表示される可能性はほとんどありませんが、可能な限り時間に近い状態で表示されます。
このターゲット時間は、システム相対時間、またはシステムがオンになってから実行されている時間 (数百ナノ秒単位) です。 現在の時刻を計算する簡単な方法は、現在 の QueryPerformanceCounter (QPC) 値を照会し、システムの QPC 頻度で除算し、10,000,000 で乗算することです。 丸めと精度の制限はさておき、これにより、適用可能な単位で現在の時刻が計算されます。 また、 MfGetSystemTime または QueryInterruptTimePrecise を呼び出すことによって、システムは現在の時刻を取得することもできます。
現在の時刻がわかれば、通常、アプリケーションは現在の時刻からのオフセットを増やして表示を出力します。
ターゲット時間に関係なく、プレゼンテーションは常にキューの順序で処理されます。 前の現在よりも前の時刻を対象とするプレゼンテーションであっても、その前のプレゼンテーションが処理されるまで処理されません。 これは基本的に、前の発表よりも後の時刻をターゲットにしていない存在が、その前の存在をオーバーライドすることを意味します。 また、アプリケーションが できるだけ早く プレゼンテーションを発行する場合は、単にターゲット時間 (ターゲット時間が再び設定されるメイン前回の時刻) を設定したり、ターゲット時刻を 0 に設定したりすることはできません。 どちらも同じ効果を持ちます。 アプリケーションが、新しいプレゼントが行われるまで前の発表が完了するのを待ちたくない場合は、以前のプレゼンテーションをキャンセルする必要があります。 今後の例では、これを行う方法について説明します。
T=3s: プレゼント 1、2、3 がすべて準備でき、最後のプレゼントである 3 が勝ちます。 T=4s: プレゼント 4、5、6 はすべて準備が整い、最後のプレゼントである 6 が勝ちます。
上の図は、現在のキュー内の未処理のプレゼンテーションの例を示しています。 現在 1 は 3 秒の時間を対象とします。 だから、プレゼントは3sまで行われません。 ただし、現在の 2 は実際には以前の時間、2 秒をターゲットとし、3 つのターゲットも 3 を示します。 そのため、時刻 3 では、1、2、3 がすべて完了し、現在の 3 が実際に有効になる現在になります。 その後、次の現在の 4 は 4 で満たされますが、0 をターゲットとする Present 5 と、ターゲット時間が設定されていない Present 6 によってすぐにオーバーライドされます。 5 と 6 の両方が できるだけ早く 有効になります。
C++ の例
bool SimpleEventSynchronizationExample(
_In_ ID3D11Device* pD3D11Device,
_In_ IPresentationManager* pPresentationManager,
_In_ IPresentationSurface* pPresentationSurface,
_In_ vector<com_ptr_failfast<ID3D11Texture2D>>& textures,
_In_ vector<com_ptr_failfast<IPresentationBuffer>>& presentationBuffers)
{
// Track a time we'll be presenting to below. Default to the current time, then increment by
// 1/10th of a second every present.
SystemInterruptTime presentTime;
QueryInterruptTimePrecise(&presentTime.value);
// Build an array of events that we can wait on to perform various actions in our work loop.
vector<unique_event> waitEvents;
// The lost event will be the first event in our list. This is an event that signifies that
// something went wrong in the system (due to extreme conditions such as memory pressure, or
// driver issues) that indicate that the presentation manager has been lost, and should no
// longer be used, and instead should be recreated.
unique_event lostEvent;
pPresentationManager->GetLostEvent(&lostEvent);
waitEvents.emplace_back(std::move(lostEvent));
// Add each buffer's available event to the list of events we will be waiting on.
for (UINT bufferIndex = 0; bufferIndex < presentationBuffers.size(); bufferIndex++)
{
unique_event availableEvent;
presentationBuffers[bufferIndex]->GetAvailableEvent(&availableEvent);
waitEvents.emplace_back(std::move(availableEvent));
}
// Iterate for 120 presents.
constexpr UINT numberOfPresents = 120;
for (UINT onPresent = 0; onPresent < numberOfPresents; onPresent++)
{
// Advance our present time 1/10th of a second in the future. Note the API accepts
// time in 100ns units, or 1/1e7 of a second, meaning that 1 million units correspond to
// 1/10th of a second.
presentTime.value += 1'000'000;
// Wait for the lost event or an available buffer. Since WaitForMultipleObjects prioritizes
// lower-indexed events, it is recommended to put any higher importance events (like the
// lost event) first, and then follow up with buffer available events.
DWORD waitResult = WaitForMultipleObjects(
static_cast<UINT>(waitEvents.size()),
reinterpret_cast<HANDLE*>(waitEvents.data()),
FALSE,
INFINITE);
// Failfast if the wait hit an error.
FAIL_FAST_IF((waitResult - WAIT_OBJECT_0) >= waitEvents.size());
// Our lost event was the first event in the array. If this is signaled, the caller
// should recreate the presentation manager. This is very similar to how Direct3D devices
// can be lost. Assume our caller knows to handle this return value appropriately.
if (waitResult == WAIT_OBJECT_0)
{
return false;
}
// Otherwise, compute the buffer corresponding to the available event that was signaled.
UINT bufferIndex = waitResult - (WAIT_OBJECT_0 + 1);
// Draw red to that buffer
DrawColorToSurface(
pD3D11Device,
textures[bufferIndex],
1.0f, // red
0.0f, // green
0.0f); // blue
// Bind the presentation buffer to the presentation surface. Changes in this binding will take
// effect on the next present, and the binding persists across presents. That is, any number
// of subsequent presents will imply this binding until it is changed. It is completely fine
// to only update buffers for a subset of the presentation surfaces owned by a presentation
// manager on a given present - the implication is that it simply didn't update.
//
// Similarly, note that if your application were to call SetBuffer on the same presentation
// surface multiple times without calling present, this is fine. The policy is last writer
// wins.
//
// Your application may present without first binding a presentation surface to a buffer.
// The result will be that presentation surface will simply have no content on screen,
// similar to how DComp and WinComp surfaces appear in a tree before they are rendered to.
// In that case system content will show through where the buffer would have been.
//
// Your application may also set a 'null' buffer binding after previously having bound a
// buffer and present - the end result is the same as if your application had presented
// without ever having set the content.
pPresentationSurface->SetBuffer(presentationBuffers[bufferIndex].get());
// Present at the targeted time. Note that a present can target only a single time. If an
// application wants to updates two buffers at two different times, then it must present
// two times.
//
// Presents are always processed in queue order. A present will not take effect before any
// previous present in the queue, even if it targets an earlier time. In such a case, when
// the previous present is processed, the next present will also be processed immediately,
// and override that previous present.
//
// For this reason, if your application wishes to present "now" or "early as possible", then
// it can simply present, without setting a target time. The implied target time will be 0,
// and the new present will override the previous present.
//
// If your application wants to present truly "now", and not wait for previous presents in the
// queue to be processed, then it will need to cancel previous presents. A future example
// demonstrates how to do this.
//
// Your application will receive PRESENTATION_ERROR_LOST if it attempts to Present a lost
// presentation manager. This is the only call that will return such an error. A lost
// presentation manager functions normally in every other case, so applications need only
// to handle this error at the time they call Present.
pPresentationManager->SetTargetTime(presentTime);
HRESULT hrPresent = pPresentationManager->Present();
if (hrPresent == PRESENTATION_ERROR_LOST)
{
// Our presentation manager has been lost. Return 'false' to the caller to indicate that
// the presentation manager should be recreated.
return false;
}
else
{
FAIL_FAST_IF_FAILED(hrPresent);
}
}
return true;
}
void DrawColorToSurface(
_In_ ID3D11Device* pD3D11Device,
_In_ const com_ptr_failfast<ID3D11Texture2D>& texture2D,
_In_ float redValue,
_In_ float greenValue,
_In_ float blueValue)
{
com_ptr_failfast<ID3D11DeviceContext> D3DDeviceContext;
com_ptr_failfast<ID3D11RenderTargetView> renderTargetView;
// Get the immediate context from the D3D11 device.
pD3D11Device->GetImmediateContext(&D3DDeviceContext);
// Create a render target view of the passed texture.
auto resource = texture2D.query<ID3D11Resource>();
FAIL_FAST_IF_FAILED(pD3D11Device->CreateRenderTargetView(
resource.get(),
nullptr,
renderTargetView.addressof()));
// Clear the texture with the specified color.
float clearColor[4] = { redValue, greenValue, blueValue, 1.0f }; // red, green, blue, alpha
D3DDeviceContext->ClearRenderTargetView(renderTargetView.get(), clearColor);
}
例 12 - 高度な同期 - 現在の同期フェンスを使用して未処理の現在のキューのサイズにワークフローを調整し、プレゼンテーション マネージャーの失われたイベントを処理する
次の例は、アプリケーションが将来のために多数のプレゼントを送信し、未処理のプレゼンテーションの数が特定の数量に減少するまでスリープ状態にする方法を示しています。 Windows Media Foundation などのフレームワークでは、発生する CPU ウェイクの数を最小限に抑えながら、現在のキューが枯渇しないようにするためにこの方法を調整します (これにより、スムーズな再生が妨げられ、グリッチが発生します)。 これは、プレゼンテーション ワークフロー中の CPU 電力使用量を最小限に抑える効果があります。 アプリケーションは、(割り当てられたプレゼンテーション バッファーの数に基づいて) プレゼントの最大数をキューに入れ、現在のキューが不足してキューを補充するまでスリープ状態になります。
C++ の例
bool FenceSynchronizationExample(
_In_ ID3D11Device* pD3D11Device,
_In_ IPresentationManager* pPresentationManager,
_In_ IPresentationSurface* pPresentationSurface,
_In_ vector<com_ptr_failfast<ID3D11Texture2D>>& textures,
_In_ vector<com_ptr_failfast<IPresentationBuffer>>& presentationBuffers)
{
// Track a time we'll be presenting to below. Default to the current time, then increment by
// 1/10th of a second every present.
SystemInterruptTime presentTime;
QueryInterruptTimePrecise(&presentTime.value);
// Get present retiring fence.
com_ptr_failfast<ID3D11Fence> presentRetiringFence;
FAIL_FAST_IF_FAILED(pPresentationManager->GetPresentRetiringFence(
IID_PPV_ARGS(&presentRetiringFence)));
// Get the lost event to query before presentation.
unique_event lostEvent;
pPresentationManager->GetLostEvent(&lostEvent);
// Create an event to synchronize to our queue depth with. We'll use Direct3D to signal this event
// when our synchronization fence indicates reaching a specific present.
unique_event presentQueueSyncEvent;
presentQueueSyncEvent.create(EventOptions::ManualReset);
// Cycle the present queue 10 times.
constexpr UINT numberOfPresentRefillCycles = 10;
for (UINT onRefillCycle = 0; onRefillCycle < numberOfPresentRefillCycles; onRefillCycle++)
{
// Fill up presents for all presentation buffers. We compare the presentation manager's
// next present ID to the present confirmed fence's value to figure out how
// far ahead we are. We stop when we've issued presents for all buffers.
while ((pPresentationManager->GetNextPresentId() -
presentRetiringFence->GetCompletedValue()) < presentationBuffers.size())
{
// Present buffers in cyclical pattern. We can figure out the current buffer to
// present by taking the modulo of the next present ID by the number of buffers. Note that the
// first present of a presentation manager always has a present ID of 1 and increments by 1 on
// each subsequent present. A present ID of 0 is conceptually meant to indicate that "no
// presents have taken place yet".
UINT bufferIndex = static_cast<UINT>(
pPresentationManager->GetNextPresentId() % presentationBuffers.size());
// Assert that the passed buffer is tracked as available for presentation. Because we throttle
// based on the total number of buffers, this should always be true.
NT_ASSERT(presentationBuffers[bufferIndex]->IsAvailable());
// Advance our present time 1/10th of a second in the future.
presentTime.value += 1'000'000;
// Draw red to the texture.
DrawColorToSurface(
pD3D11Device,
textures[bufferIndex],
1.0f, // red
0.0f, // green
0.0f); // blue
// Bind the presentation buffer to the presentation surface.
pPresentationSurface->SetBuffer(presentationBuffers[bufferIndex].get());
// Present at the targeted time.
pPresentationManager->SetTargetTime(presentTime);
HRESULT hrPresent = pPresentationManager->Present();
if (hrPresent == PRESENTATION_ERROR_LOST)
{
// Our presentation manager has been lost. Return 'false' to the caller to indicate that
// the presentation manager should be recreated.
return false;
}
else
{
FAIL_FAST_IF_FAILED(hrPresent);
}
};
// Now that the buffer is full, go to sleep until the present queue has been drained to
// the desired queue depth. To figure out the appropriate present to wake on, we subtract
// the desired wake queue depth from the presentation manager's last present ID. We
// use Direct3D's SetEventOnCompletion to signal our wait event when that particular present
// is retiring, and then wait on that event. Note that the semantic of SetEventOnCompletion
// is such that even if we happen to call it after the fence has already reached the
// requested value, the event will be set immediately.
constexpr UINT wakeOnQueueDepth = 2;
presentQueueSyncEvent.ResetEvent();
FAIL_FAST_IF_FAILED(presentRetiringFence->SetEventOnCompletion(
pPresentationManager->GetNextPresentId() - 1 - wakeOnQueueDepth,
presentQueueSyncEvent.get()));
HANDLE waitHandles[] = { lostEvent.get(), presentQueueSyncEvent.get() };
DWORD waitResult = WaitForMultipleObjects(
ARRAYSIZE(waitHandles),
waitHandles,
FALSE,
INFINITE);
// Failfast if we hit an error during our wait.
FAIL_FAST_IF((waitResult - WAIT_OBJECT_0) >= ARRAYSIZE(waitHandles));
if (waitResult == WAIT_OBJECT_0)
{
// The lost event was signaled - return 'false' to the caller to indicate that
// the presentation manager was lost.
return false;
}
// Iterate into another refill cycle.
}
return true;
}
例 13 - VSync 割り込みとハードウェア フリップ キューのサポート
ハードウェア フリップ キュー と呼ばれる 新しい形式のフリップ キュー 管理がこの API と共に導入され、基本的に GPU ハードウェアが CPU とは完全に独立してプレゼンテーションを管理できるようになりました。 このメインの利点は、電力効率です。 CPU がプレゼンテーション プロセスに関与する必要がない場合は、描画される電力が少なくなります。
GPU ハンドルを個別に表示することの欠点は、提示が表示されたときに CPU 状態がすぐに反映されなくなるということです。 バッファーで使用可能なイベント、同期フェンス、現在の統計情報などのコンポジション スワップチェーン API の概念は、すぐには更新されません。 代わりに、GPU は、表示された内容に関して CPU 状態を定期的にのみ更新します。 つまり、現在の状態に関するアプリケーションへのフィードバックには待機時間が伴います。
通常、アプリケーションは、一部のプレゼンテーションが表示される場合に注意しますが、他のプレゼントにはあまり気にしません。 たとえば、アプリケーションが 10 個のプレゼンテーションを発行した場合、8 番目がいつ表示されるかを知りたいと判断し、現在のキューの再補充を開始できます。 この場合、フィードバックが本当に必要なのは 8 日だけです。 1 から 7 または 9 が表示されている場合は、何も行う予定はありません。
更新プログラムの CPU 状態が表示されるかどうかは、GPU ハードウェアが VSync 割り込みを生成するように構成されているかどうかによって異なります。 この VSync 割り込みによって CPU がまだアクティブでない場合は CPU が目覚め、CPU は特別なカーネル レベルのコードを実行して、最後にチェックされてから GPU 上で発生したプレゼントに対して自身を更新し、バッファー使用可能なイベント、現在の廃止フェンス、現在の統計情報などのフィードバック メカニズムを更新します。
どのプレゼンテーションが VSync 割り込みを発行する必要があるかをアプリケーションで明示的に示せるように、プレゼンテーション マネージャーは IPresentationManager::ForceVSyncInterrupt メソッドを公開します。このメソッドは、後続のプレゼンテーションで VSync 割り込みを発行するかどうかを指定します。 この設定は、 IPresentationManager::SetTargetTime や IPresentationManager::SetPreferredPresentDuration と同様に、変更されるまでのすべての将来のプレゼンテーションに適用されます。
この設定が特定の存在で有効になっている場合、ハードウェアは、より多くの電力を使用して、その存在が表示されたときにすぐに CPU に通知しますが、アプリケーションが可能な限り迅速に応答できるように、存在が発生したときに CPU にすぐに通知されるようにします。 この設定が特定のプレゼンテーションで無効になっている場合、システムは現在の表示時に CPU の更新を延期し、電力を節約しますが、フィードバックを延期することができます。
通常、アプリケーションは、同期する存在を除き、すべてのプレゼントに対して VSync 割り込みを強制しません。 上記の例では、アプリケーションは現在のキューを補充するために 8 番目の存在が示されたときに目を覚ますことを望んでいたので、8 個のシグナルが VSync 割り込みを要求しますが、1 から 7 を提示し、9 は存在しません。
既定では、アプリケーションでこの設定が構成されていない場合、プレゼンテーション マネージャーは常に、すべての存在が表示されたときに VSync 割り込みを通知します。 電源使用量を気にしない、またはハードウェア フリップ キューのサポートを認識していないアプリケーションは、 ForceVSyncInterrupt を呼び出すだけではできないため、結果として正しく同期することが保証されます。 ハードウェア フリップ キューのサポートを認識しているアプリケーションでは、この設定を明示的に制御して、電力効率を向上させることができます。
VSync 割り込み設定に関する API の動作を説明する図を次に示します。
C++ の例
bool ForceVSyncInterruptPresentsExample(
_In_ ID3D11Device* pD3D11Device,
_In_ IPresentationManager* pPresentationManager,
_In_ IPresentationSurface* pPresentationSurface,
_In_ vector<com_ptr_failfast<ID3D11Texture2D>>& textures,
_In_ vector<com_ptr_failfast<IPresentationBuffer>>& presentationBuffers)
{
// Track a time we'll be presenting to below. Default to the current time, then increment by
// 1/10th of a second every present.
SystemInterruptTime presentTime;
QueryInterruptTimePrecise(&presentTime.value);
// Get present retiring fence.
com_ptr_failfast<ID3D11Fence> presentRetiringFence;
FAIL_FAST_IF_FAILED(pPresentationManager->GetPresentRetiringFence(
IID_PPV_ARGS(&presentRetiringFence)));
// Get the lost event to query before presentation.
unique_event lostEvent;
pPresentationManager->GetLostEvent(&lostEvent);
// Create an event to synchronize to our queue depth with. We will use Direct3D to signal this event
// when our synchronization fence indicates reaching a specific present.
unique_event presentQueueSyncEvent;
presentQueueSyncEvent.create(EventOptions::ManualReset);
// Issue 10 presents, and wake when the present queue is 2 entries deep (which happens when
// present 7 is retiring).
constexpr UINT wakeOnQueueDepth = 2;
constexpr UINT numberOfPresents = 10;
const UINT presentIdToWakeOn = numberOfPresents - 1 - wakeOnQueueDepth;
while (pPresentationManager->GetNextPresentId() <= numberOfPresents)
{
UINT bufferIndex = static_cast<UINT>(
pPresentationManager->GetNextPresentId() % presentationBuffers.size());
// Advance our present time 1/10th of a second in the future.
presentTime.value += 1'000'000;
// Draw red to the texture.
DrawColorToSurface(
pD3D11Device,
textures[bufferIndex],
1.0f, // red
0.0f, // green
0.0f); // blue
// Bind the presentation buffer to the presentation surface.
pPresentationSurface->SetBuffer(presentationBuffers[bufferIndex].get());
// Present at the targeted time.
pPresentationManager->SetTargetTime(presentTime);
// If this present is not going to retire the present that we want to wake on when it is shown, then
// we don't need immediate updates to buffer available events, present retiring fence, or present
// statistics. As such, we can mark it as not requiring a VSync interrupt, to allow for greater
// power efficiency on machines with hardware flip queue support.
bool forceVSyncInterrupt = (pPresentationManager->GetNextPresentId() == (presentIdToWakeOn + 1));
pPresentationManager->ForceVSyncInterrupt(forceVSyncInterrupt);
HRESULT hrPresent = pPresentationManager->Present();
if (hrPresent == PRESENTATION_ERROR_LOST)
{
// Our presentation manager has been lost. Return 'false' to the caller to indicate that
// the presentation manager should be recreated.
return false;
}
else
{
FAIL_FAST_IF_FAILED(hrPresent);
}
}
// Now that the buffer is full, go to sleep until presentIdToWakeOn has begun retiring. We
// configured the subsequent present to force a VSync interrupt when it is shown, which will ensure
// this wait is completed immediately.
presentQueueSyncEvent.ResetEvent();
FAIL_FAST_IF_FAILED(presentRetiringFence->SetEventOnCompletion(
presentIdToWakeOn,
presentQueueSyncEvent.get()));
HANDLE waitHandles[] = { lostEvent.get(), presentQueueSyncEvent.get() };
DWORD waitResult = WaitForMultipleObjects(
ARRAYSIZE(waitHandles),
waitHandles,
FALSE,
INFINITE);
// Failfast if we hit an error during our wait.
FAIL_FAST_IF((waitResult - WAIT_OBJECT_0) >= ARRAYSIZE(waitHandles));
if (waitResult == WAIT_OBJECT_0)
{
// The lost event was signaled - return 'false' to the caller to indicate that
// the presentation manager was lost.
return false;
}
return true;
}
例 14 - 将来予定されているプレゼンテーションの取り消し
将来のプレゼンテーションを深くキューに入れたメディア アプリケーションは、以前に発行したプレゼンテーションを取り消す場合があります。 これは、たとえば、アプリケーションがビデオを再生していて、今後多数のフレームが発行され、ユーザーがビデオ再生を一時停止することにした場合などに発生する可能性があります。 この場合、アプリケーションは現在のフレームを維持し、まだキューに入っていない将来のフレームを取り消す必要があります。 これは、メディア アプリケーションがビデオ内の別のポイントに再生を移動することを決定した場合にも発生する可能性があります。 この場合、アプリケーションでは、ビデオ内の古い位置のキューに入っていないすべてのプレゼンテーションを取り消し、新しい位置のプレゼンテーションに置き換えます。 この場合、以前のプレゼンテーションを取り消した後、アプリケーションはビデオの新しいポイントに対応する新しいプレゼンテーションを今後発行できます。
C++ の例
void PresentCancelExample(
_In_ IPresentationManager* pPresentationManager,
_In_ UINT64 firstPresentIDToCancelFrom)
{
// Assume we've issued a number of presents in the future. Something happened in the app, and
// we want to cancel the issued presents that occur after a specified time or present ID. This
// may happen, for example, when the user pauses playback from inside a media application. The
// application will want to cancel all presents posted targeting beyond the pause time. The
// cancel will apply to all previously posted presents whose present IDs are at least
// 'firstPresentIDToCancelFrom'. Note that Present IDs are always unique, and never recycled,
// so even if a present is canceled, no subsequent present will ever reuse its present ID.
//
// Also note that if some presents we attempt to cancel can't be canceled because they've
// already started queueing, then no error will be returned, they simply won't be canceled as
// requested. Cancelation takes a "best effort" approach.
FAIL_FAST_IF_FAILED(pPresentationManager->CancelPresentsFrom(firstPresentIDToCancelFrom));
// In the case where the media application scrubbed to a different position in the video, it may now
// choose to issue new presents to replace the ones canceled. This is not illustrated here, but
// previous examples that demonstrate presentation show how this may be achieved.
}
例 15 - パフォーマンスを向上させるためにバッファーサイズ変更操作をずらした
この例では、DXGI よりもパフォーマンスを向上させるために、アプリケーションでバッファーのサイズ変更を調整する方法を示します。 ムービー ストリーミング サービス クライアントが再生解像度を 720p から 1080p に変更する場合の、前のサイズ変更の例を思い出してください。 DXGI では、アプリケーションは DXGI スワップチェーンに対してサイズ変更操作を実行します。この操作により、以前のすべてのバッファーがアトミックに廃棄され、新しい 1080p バッファーがすべて一度に再割り当てされ、スワップチェーンに追加されます。 バッファーのこの種類のアトミック サイズ変更はコストがかかり、時間がかかり、グリッチが発生する可能性があります。 新しい API では、個々のプレゼンテーション バッファーを細かく制御できます。 そのため、バッファーを再割り当てし、複数のプレゼンテーションに 1 つずつ置き換えて、時間の経過と同時にワークロードを分割できます。 これは、存在するものへの影響が少なく、グリッチを引き起こす可能性がはるかに低くなります。 基本的に、n 個のプレゼンテーション バッファーを含むプレゼンテーション マネージャーの場合、'n' プレゼンテーションの場合、アプリケーションは古いサイズの古いプレゼンテーション バッファーを削除し、新しいサイズで新しいプレゼンテーション バッファーを割り当てて、それを提示できます。 'n' が表示されると、すべてのバッファーが新しいサイズになります。
C++ の例
bool StaggeredResizeExample(
_In_ ID3D11Device* pD3D11Device,
_In_ IPresentationManager* pPresentationManager,
_In_ IPresentationSurface* pPresentationSurface,
_In_ vector<com_ptr_failfast<ID3D11Texture2D>> textures,
_In_ vector<com_ptr_failfast<IPresentationBuffer>> presentationBuffers)
{
// Track a time we'll be presenting to below. Default to the current time, then increment by
// 1/10th of a second every present.
SystemInterruptTime presentTime;
QueryInterruptTimePrecise(&presentTime.value);
// Assume textures/presentationBuffers vector contains 10 100x100 buffers, and we want to resize
// our swapchain to 200x200. Instead of reallocating 10 200x200 buffers all at once,
// like DXGI does today, we can stagger the reallocation across multiple presents. For
// each present, we can allocate one buffer at the new size, and replace one old buffer
// at the old size with the new one at the new size. After 10 presents, we will have
// reallocated all our buffers, and we will have done so in a manner that's much less
// likely to produce delays or glitches.
constexpr UINT numberOfBuffers = 10;
for (UINT bufferIndex = 0; bufferIndex < numberOfBuffers; bufferIndex++)
{
// Advance our present time 1/10th of a second in the future.
presentTime.value += 1'000'000;
// Release the old texture/presentation buffer at the presented index.
auto& replacedTexture = textures[bufferIndex];
auto& replacedPresentationBuffer = presentationBuffers[bufferIndex];
replacedTexture.reset();
replacedPresentationBuffer.reset();
// Create a new texture/presentation buffer in its place.
AddNewPresentationBuffer(
pD3D11Device,
pPresentationManager,
200, // Buffer width
200, // Buffer height
&replacedTexture,
&replacedPresentationBuffer);
// Draw red to the new texture.
DrawColorToSurface(
pD3D11Device,
replacedTexture,
1.0f, // red
0.0f, // green
0.0f); // blue
// Bind the presentation buffer to the presentation surface.
pPresentationSurface->SetBuffer(replacedPresentationBuffer.get());
// Present at the targeted time.
pPresentationManager->SetTargetTime(presentTime);
HRESULT hrPresent = pPresentationManager->Present();
if (hrPresent == PRESENTATION_ERROR_LOST)
{
// Our presentation manager has been lost. Return 'false' to the caller to indicate that
// the presentation manager should be recreated.
return false;
}
else
{
FAIL_FAST_IF_FAILED(hrPresent);
}
}
return true;
}
例 16 - 現在の統計の読み取りと処理
API レポートには、送信された各プレゼンテーションの統計情報が表示されます。 大まかに言えば、現在の統計情報は、特定の存在がシステムによって処理または表示された方法を記述するフィードバック メカニズムです。 アプリケーションが受け取るために登録できる統計にはさまざまな種類があり、API 自体の統計インフラストラクチャは拡張可能であることを意図しているため、今後、より多くの種類の統計を追加できます。 この API では、統計を読み戻す方法と、現在定義されている統計の種類、およびそれらが大まかに伝える情報について説明します。
C++ の例
// This is an identifier we'll assign to our presentation surface that will be used to reference that
// presentation surface in statistics. This is to avoid referring to a presentation surface by pointer
// in a statistics structure, which has unclear refcounting and lifetime semantics.
static constexpr UINT_PTR myPresentedContentTag = 12345;
bool StatisticsExample(
_In_ ID3D11Device* pD3D11Device,
_In_ IPresentationManager* pPresentationManager,
_In_ IPresentationSurface* pPresentationSurface,
_In_ vector<com_ptr_failfast<ID3D11Texture2D>>& textures,
_In_ vector<com_ptr_failfast<IPresentationBuffer>>& presentationBuffers)
{
// Track a time we'll be presenting to below. Default to the current time, then increment by
// 1/10th of a second every present.
SystemInterruptTime presentTime;
QueryInterruptTimePrecise(&presentTime.value);
// Register to receive 3 types of statistics.
FAIL_FAST_IF_FAILED(pPresentationManager->EnablePresentStatisticsKind(
PresentStatisticsKind_CompositionFrame,
true));
FAIL_FAST_IF_FAILED(pPresentationManager->EnablePresentStatisticsKind(
PresentStatisticsKind_PresentStatus,
true));
FAIL_FAST_IF_FAILED(pPresentationManager->EnablePresentStatisticsKind(
PresentStatisticsKind_IndependentFlipFrame,
true));
// Stats come back referencing specific presentation surfaces. We assign 'tags' to presentation
// surfaces in the API that statistics will use to reference the presentation surface in a
// statistic.
pPresentationSurface->SetTag(myPresentedContentTag);
// Build an array of events that we can wait on.
vector<unique_event> waitEvents;
// The lost event will be the first event in our list. This is an event that signifies that
// something went wrong in the system (due to extreme conditions like memory pressure, or
// driver issues) that indicate that the presentation manager has been lost, and should no
// longer be used, and instead should be recreated.
unique_event lostEvent;
FAIL_FAST_IF_FAILED(pPresentationManager->GetLostEvent(&lostEvent));
waitEvents.emplace_back(std::move(lostEvent));
// The statistics event will be the second event in our list. This event will be signaled
// by the presentation manager when there are statistics to read back.
unique_event statisticsEvent;
FAIL_FAST_IF_FAILED(pPresentationManager->GetPresentStatisticsAvailableEvent(&statisticsEvent));
waitEvents.emplace_back(std::move(statisticsEvent));
// Add each buffer's available event to the list of events we will be waiting on.
for (UINT bufferIndex = 0; bufferIndex < presentationBuffers.size(); bufferIndex++)
{
unique_event availableEvent;
presentationBuffers[bufferIndex]->GetAvailableEvent(&availableEvent);
waitEvents.emplace_back(std::move(availableEvent));
}
// Iterate our workflow 120 times.
constexpr UINT iterationCount = 120;
for (UINT i = 0; i < iterationCount; i++)
{
// Wait for an event to be signaled.
DWORD waitResult = WaitForMultipleObjects(
static_cast<UINT>(waitEvents.size()),
reinterpret_cast<HANDLE*>(waitEvents.data()),
FALSE,
INFINITE);
// Failfast if the wait hit an error.
FAIL_FAST_IF((waitResult - WAIT_OBJECT_0) >= waitEvents.size());
// Our lost event was the first event in the array. If this is signaled, then the caller
// should recreate the presentation manager. This is very similar to how Direct3D devices
// can be lost. Assume our caller knows to handle this return value appropriately.
if (waitResult == WAIT_OBJECT_0)
{
return false;
}
// The second event in the array is the statistics event. If this event is signaled,
// read and process our statistics.
if (waitResult == (WAIT_OBJECT_0 + 1))
{
StatisticsExample_ProcessStatistics(pPresentationManager);
}
// Otherwise, the event corresponds to a buffer available event that is signaled.
// Compute the buffer for the available event that was signaled and present a
// frame.
else
{
DWORD bufferIndex = waitResult - (WAIT_OBJECT_0 + 2);
// Draw red to the texture.
DrawColorToSurface(
pD3D11Device,
textures[bufferIndex],
1.0f, // red
0.0f, // green
0.0f); // blue
// Bind the texture to the presentation surface.
pPresentationSurface->SetBuffer(presentationBuffers[bufferIndex].get());
// Advance our present time 1/10th of a second in the future.
presentTime.value += 1'000'000;
// Present at the targeted time.
pPresentationManager->SetTargetTime(presentTime);
HRESULT hrPresent = pPresentationManager->Present();
if (hrPresent == PRESENTATION_ERROR_LOST)
{
// Our presentation manager has been lost. Return 'false' to the caller to indicate that
// the presentation manager should be recreated.
return false;
}
else
{
FAIL_FAST_IF_FAILED(hrPresent);
}
}
}
return true;
}
void StatisticsExample_ProcessStatistics(
_In_ IPresentationManager* pPresentationManager)
{
// Dequeue a single present statistics item. This will return the item
// and pop it off the queue of statistics.
com_ptr_failfast<IPresentStatistics> presentStatisticsItem;
pPresentationManager->GetNextPresentStatistics(&presentStatisticsItem);
// Read back the present ID this corresponds to.
UINT64 presentId = presentStatisticsItem->GetPresentId();
UNREFERENCED_PARAMETER(presentId);
// Switch on the type of statistic this item corresponds to.
switch (presentStatisticsItem->GetKind())
{
case PresentStatisticsKind_PresentStatus:
{
// Present status statistics describe whether a given present was queued for display,
// skipped due to some future present being a better candidate to display on a given
// frame, or canceled via the API.
auto presentStatusStatistics = presentStatisticsItem.query<IPresentStatusStatistics>();
// Read back the status
PresentStatus status = presentStatusStatistics->GetPresentStatus();
UNREFERENCED_PARAMETER(status);
// Possible values for status:
// PresentStatus_Queued
// PresentStatus_Skipped
// PresentStatus_Canceled;
// Depending on the status returned, your application can adjust their workflow
// accordingly. For example, if your application sees that a large percentage of their
// presents are skipped, it means they are presenting more frames than the system can
// display. In such a case, your application might decided to lower the rate at which
// you present frames.
}
break;
case PresentStatisticsKind_CompositionFrame:
{
// Composition frame statistics describe how a given present was used in a DWM frame.
// It includes information such as which monitors displayed the present, whether the
// present was composed or directly scanned out via an MPO plane, and rendering
// properties such as what transforms were applied to the rendering. Composition
// frame statistics are not issued for iflip presents - only for presents issued by the
// compositor. iflip presents have their own type of statistic (described next).
auto compositionFrameStatistics =
presentStatisticsItem.query<ICompositionFramePresentStatistics>();
// Stats should come back for the present statistics item that we tagged earlier.
NT_ASSERT(compositionFrameStatistics->GetContentTag() == myPresentedContentTag);
// The composition frame ID indicates the DWM frame ID that the present was used
// in.
CompositionFrameId frameId = compositionFrameStatistics->GetCompositionFrameId();
// Get the display instance array to indicate which displays showed the present. Each
// instance of the presentation surface will have an entry in this array. For example,
// if your application adds the same presentation surface to four different visuals in the
// visual tree, then each instance in the tree will have an entry in the display instance
// array. Similarly, if the presentation surface shows up on multiple monitors, then each
// monitor instance will be accounted for in the display instance array that is
// returned.
//
// Note that the pointer returned from GetDisplayInstanceArray is valid for the
// lifetime of the ICompositionFramePresentStatistics. Your application must not attempt
// to read this pointer after the ICompositionFramePresentStatistics has been released
// to a refcount of 0.
UINT displayInstanceArrayCount;
const CompositionFrameDisplayInstance* pDisplayInstances;
compositionFrameStatistics->GetDisplayInstanceArray(
&displayInstanceArrayCount,
&pDisplayInstances);
for (UINT i = 0; i < displayInstanceArrayCount; i++)
{
const auto& displayInstance = pDisplayInstances[i];
// The following are fields that are available in a display instance.
// The LUID, VidPnSource, and unique ID of the output and its owning
// adapter. The unique ID will be bumped when a LUID/VidPnSource is
// recycled. Applications should use the unique ID to determine when
// this happens so that they don't try and correlate stats from one
// monitor with another.
displayInstance.outputAdapterLUID;
displayInstance.outputVidPnSourceId;
displayInstance.outputUniqueId;
// The instanceKind field indicates how the present was used. It
// indicates that the present was composed (rendered to DWM's backbuffer),
// scanned out (via MPO/DFlip) or composed to an intermediate buffer by DWM
// for effects.
displayInstance.instanceKind;
// The finalTransform field indicates the transform at which the present was
// shown in world space. It will include all ancestor visual transforms and
// can be used to know how it was rendered in the global visual tree.
displayInstance.finalTransform;
// The requiredCrossAdapterCopy field indicates whether or not we needed to
// copy your application's buffer to a different adapter in order to display
// it. Applications should use this to determine whether or not they should
// reallocate their buffers onto a different adapter for better performance.
displayInstance.requiredCrossAdapterCopy;
// The colorSpace field indicates the colorSpace of the output that the
// present was rendered to.
displayInstance.colorSpace;
// For example, if your application sees that the finalTransform is scaling your
// content by 2x, you might elect to pre-render that scale into your presentation
// surface, and then add a 1/2 scale. At which point, the finalTransform should
// be 1x, and some MPO hardware will be more likely to MPO a presentation surface
// with a 1x scale applied, since some hardware has a maximum they are able to
// scale in an MPO plane. Similarly, if your application's content is being scaled
// down on screen, you may wish to simply render its content at a
// smaller scale to conserve resources, and apply an enlargement transform.
}
// Additionally, we can use the CompositionFrameId reported by the statistic
// to query timing-related information about that specific frame via the new
// composition timing API, such as when that frame showed up on screen.
// Note this is achieved using a separate API from the composition swapchain API, but
// using the composition frame ID reported in the composition swapchain API to
// properly specify which frame your application wants timing information from.
COMPOSITION_FRAME_TARGET_STATS frameTargetStats;
COMPOSITION_TARGET_STATS targetStats[4];
frameTargetStats.targetCount = ARRAYSIZE(targetStats);
frameTargetStats.targetStats = targetStats;
// Specify the frameId that we got from stats in order to pass to the call
// below and retrieve timing information about that frame.
frameTargetStats.frameId = frameId;
FAIL_FAST_IF_FAILED(DCompositionGetTargetStatistics(1, &frameTargetStats));
// If the frameTargetStats comes back with a 0 frameId, it means the frame isn't
// part of statistics. This might mean that it has expired out of
// DCompositionGetTargetStatistics history, but that call keeps a history buffer
// roughly equivalent to ~5 seconds worth of frame history, so if your application
// is processing statistics from the presentation manager relatively regularly,
// by all accounts it shouldn't worry about DCompositionGetTargetStatistics
// history expiring. The more likely scenario when this occurs is that it's too
// early, and that this frame isn't part of statistics YET. In that case, your application
// should defer processing for this frame, and try again later. For the purposes
// if sample brevity, we don't bother trying again here. A good method to use would
// be to add this present info to a list of presents that we haven't gotten target
// statistics for yet, and try again for all presents in that list any time we get
// a new PresentStatisticsKind_CompositionFrame for a future frame.
if (frameTargetStats.frameId == frameId)
{
// The targetCount will represent the count of outputs the given frame
// applied to.
frameTargetStats.targetCount;
// The targetTime corresponds to the wall clock QPC time DWM was
// targeting for the frame.
frameTargetStats.targetTime;
for (UINT i = 0; i < frameTargetStats.targetCount; i++)
{
const auto& targetStat = frameTargetStats.targetStats[i];
// The present time corresponds to the targeted present time of the composition
// frame.
targetStat.presentTime;
// The target ID corresponds to the LUID/VidPnSourceId/Unique ID for the given
// target.
targetStat.targetId;
// The completedStats convey information about the time a compositor frame was
// completed, which marks the time any of its associated composition swapchain API
// presents entered the displayed state. In particular, your application might wish
// to use the 'time' to know if a present showed at a time it expected.
targetStat.completedStats.presentCount;
targetStat.completedStats.refreshCount;
targetStat.completedStats.time;
// There is various other timing statistics information conveyed by
// DCompositionGetTargetStatistics.
}
}
}
break;
case PresentStatisticsKind_IndependentFlipFrame:
{
// Independent flip frame statistics describe a present that was shown via
// independent flip.
auto independentFlipFrameStatistics =
presentStatisticsItem.query<IIndependentFlipFramePresentStatistics>();
// Stats should come back for the present statistics item that we tagged earlier.
NT_ASSERT(independentFlipFrameStatistics->GetContentTag() == myPresentedContentTag);
// The driver-approved present duration describes the custom present duration that was
// approved by the driver and applied during the present. This is how, for example, media
// will know whether or not they got 24hz mode for their content if they requested it.
independentFlipFrameStatistics->GetPresentDuration();
// The displayed time is the time the present was confirmed to have been shown
// on screen.
independentFlipFrameStatistics->GetDisplayedTime();
// The adapter LUID/VidpnSource ID describe the output on which the present took
// place. Unlike the composition statistic above, we don't report a unique ID here
// because a monitor recycle would kick the presentation out of iflip.
independentFlipFrameStatistics->GetOutputAdapterLUID();
independentFlipFrameStatistics->GetOutputVidPnSourceId();
}
break;
}
}
例 17 - 抽象化レイヤーを使用して、アプリケーションから新しいコンポジション スワップチェーン API または DXGI を使用して提示する
新しいコンポジション スワップチェーン API のシステム/ドライバー要件が高いことを考えると、新しい API がサポートされていない場合に、アプリケーションで DXGI を使用することが望ましい場合があります。 幸いなことに、プレゼンテーションに使用できる API を利用する抽象化レイヤーを導入するのは非常に簡単です。 次の例は、これを得る方法を示します。
C++ の例
// A base class presentation provider. We'll provide implementations using both DXGI and the new
// composition swapchain API, which the example will use based on what's supported.
class PresentationProvider
{
public:
virtual void GetBackBuffer(
_Out_ ID3D11Texture2D** ppBackBuffer) = 0;
virtual void Present(
_In_ SystemInterruptTime presentationTime) = 0;
virtual bool ReadStatistics(
_Out_ UINT* pPresentCount,
_Out_ UINT64* pSyncQPCTime) = 0;
virtual ID3D11Device* GetD3D11DeviceNoRef()
{
return m_d3dDevice.get();
}
protected:
com_ptr_failfast<ID3D11Device> m_d3dDevice;
};
// An implementation of PresentationProvider using a DXGI swapchain to provide presentation
// functionality.
class DXGIProvider :
public PresentationProvider
{
public:
DXGIProvider(
_In_ UINT width,
_In_ UINT height,
_In_ UINT bufferCount)
{
com_ptr_failfast<IDXGIAdapter> dxgiAdapter;
com_ptr_failfast<IDXGIFactory7> dxgiFactory;
com_ptr_failfast<IDXGISwapChain1> dxgiSwapchain;
com_ptr_failfast<ID3D11DeviceContext> d3dDeviceContext;
// Direct3D device creation flags.
UINT deviceCreationFlags =
D3D11_CREATE_DEVICE_BGRA_SUPPORT |
D3D11_CREATE_DEVICE_SINGLETHREADED;
// Create the Direct3D device.
FAIL_FAST_IF_FAILED(D3D11CreateDevice(
nullptr, // No adapter
D3D_DRIVER_TYPE_HARDWARE, // Hardware device
nullptr, // No module
deviceCreationFlags, // Device creation flags
nullptr, 0, // Highest available feature level
D3D11_SDK_VERSION, // API version
&m_d3dDevice, // Resulting interface pointer
nullptr, // Actual feature level
&d3dDeviceContext)); // Device context
// Make our way from the Direct3D device to the DXGI factory.
auto dxgiDevice = m_d3dDevice.query<IDXGIDevice>();
FAIL_FAST_IF_FAILED(dxgiDevice->GetParent(IID_PPV_ARGS(&dxgiAdapter)));
FAIL_FAST_IF_FAILED(dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory)));
// Create a swapchain matching the desired parameters.
DXGI_SWAP_CHAIN_DESC1 desc = {};
desc.Width = width;
desc.Height = height;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.BufferCount = bufferCount;
desc.Scaling = DXGI_SCALING_STRETCH;
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
FAIL_FAST_IF_FAILED(dxgiFactory->CreateSwapChainForComposition(
m_d3dDevice.get(),
&desc,
nullptr,
&dxgiSwapchain));
// Store the swapchain.
dxgiSwapchain.query_to(&m_swapchain);
}
void GetBackBuffer(
_Out_ ID3D11Texture2D** ppBackBuffer) override
{
// Get the backbuffer directly from the swapchain.
FAIL_FAST_IF_FAILED(m_swapchain->GetBuffer(
m_swapchain->GetCurrentBackBufferIndex(),
IID_PPV_ARGS(ppBackBuffer)));
}
void Present(
_In_ SystemInterruptTime presentationTime) override
{
// Convert the passed presentation time to a present interval. The implementation is
// not provided here, as there's a great deal of complexity around computing this
// most accurately, but it essentially boils down to taking the time from now and
// figuring out the number of vblanks that corresponds to that time duration.
UINT vblankIntervals = ComputePresentIntervalFromTime(presentationTime);
// Issue a present to the swapchain. If we wanted to allow for a time to be specified,
// code here could convert the time to a present duration, which could be passed here.
FAIL_FAST_IF_FAILED(m_swapchain->Present(vblankIntervals, 0));
}
bool ReadStatistics(
_Out_ UINT* pPresentCount,
_Out_ UINT64* pSyncQPCTime) override
{
// Zero our output parameters initially.
*pPresentCount = 0;
*pSyncQPCTime = 0;
// Grab frame statistics from the swapchain.
DXGI_FRAME_STATISTICS frameStatistics;
FAIL_FAST_IF_FAILED(m_swapchain->GetFrameStatistics(&frameStatistics));
// If the statistics have changed since our last read, then return the new information
// to the caller.
bool hasNewStats = false;
if (frameStatistics.PresentCount > m_lastPresentCount)
{
m_lastPresentCount = frameStatistics.PresentCount;
hasNewStats = true;
*pPresentCount = frameStatistics.PresentCount;
*pSyncQPCTime = frameStatistics.SyncQPCTime.QuadPart;
}
return hasNewStats;
}
private:
com_ptr_failfast<IDXGISwapChain4> m_swapchain;
UINT m_lastPresentCount = 0;
};
// An implementation of PresentationProvider using the composition swapchain API to provide
// presentation functionality.
class PresentationAPIProvider :
public PresentationProvider
{
public:
PresentationAPIProvider(
_In_ UINT width,
_In_ UINT height,
_In_ UINT bufferCount)
{
// Create the presentation manager and presentation surface using the function defined in a
// previous example.
MakePresentationManagerAndPresentationSurface(
&m_d3dDevice,
&m_presentationManager,
&m_presentationSurface,
m_presentationSurfaceHandle);
// Register for present statistics.
FAIL_FAST_IF_FAILED(m_presentationManager->EnablePresentStatisticsKind(
PresentStatisticsKind_CompositionFrame,
true));
// Get the statistics event from the presentation manager.
FAIL_FAST_IF_FAILED(m_presentationManager->GetPresentStatisticsAvailableEvent(
&m_statisticsAvailableEvent));
// Create and register the specified number of presentation buffers.
for (UINT i = 0; i < bufferCount; i++)
{
com_ptr_failfast<ID3D11Texture2D> texture;
com_ptr_failfast<IPresentationBuffer> presentationBuffer;
AddNewPresentationBuffer(
m_d3dDevice.get(),
m_presentationManager.get(),
width,
height,
&texture,
&presentationBuffer);
// Add the new presentation buffer and texture to our array.
m_textures.push_back(texture);
m_presentationBuffers.push_back(presentationBuffer);
// Store the available event for the presentation buffer.
unique_event availableEvent;
FAIL_FAST_IF_FAILED(presentationBuffer->GetAvailableEvent(&availableEvent));
m_bufferAvailableEvents.emplace_back(std::move(availableEvent));
}
}
void GetBackBuffer(
_Out_ ID3D11Texture2D** ppBackBuffer) override
{
// Query an available backbuffer using our available events.
DWORD waitIndex = WaitForMultipleObjects(
static_cast<UINT>(m_bufferAvailableEvents.size()),
reinterpret_cast<HANDLE*>(m_bufferAvailableEvents.data()),
FALSE,
INFINITE);
UINT bufferIndex = waitIndex - WAIT_OBJECT_0;
// Set the backbuffer to be the next presentation buffer.
FAIL_FAST_IF_FAILED(m_presentationSurface->SetBuffer(m_presentationBuffers[bufferIndex].get()));
// Return the backbuffer to the caller.
m_textures[bufferIndex].query_to(ppBackBuffer);
}
void Present(
_In_ SystemInterruptTime presentationTime) override
{
// Present at the targeted time.
m_presentationManager->SetTargetTime(presentationTime);
HRESULT hrPresent = m_presentationManager->Present();
if (hrPresent == PRESENTATION_ERROR_LOST)
{
// Our presentation manager has been lost. See previous examples regarding how to handle this.
return;
}
else
{
FAIL_FAST_IF_FAILED(hrPresent);
}
}
bool ReadStatistics(
_Out_ UINT* pPresentCount,
_Out_ UINT64* pSyncQPCTime) override
{
// Zero our out parameters initially.
*pPresentCount = 0;
*pSyncQPCTime = 0;
bool hasNewStats = false;
// Peek at the statistics available event state to see if we've got new statistics.
while (WaitForSingleObject(m_statisticsAvailableEvent.get(), 0) == WAIT_OBJECT_0)
{
// Pop a statistics item to process.
com_ptr_failfast<IPresentStatistics> statisticsItem;
FAIL_FAST_IF_FAILED(m_presentationManager->GetNextPresentStatistics(
&statisticsItem));
// If this is a composition frame stat, process it.
if (statisticsItem->GetKind() == PresentStatisticsKind_CompositionFrame)
{
// We've got new stats to report.
hasNewStats = true;
// Convert to composition frame statistic item.
auto frameStatisticsItem = statisticsItem.query<ICompositionFramePresentStatistics>();
// Query DirectComposition's target statistics API to determine the completed time.
COMPOSITION_FRAME_TARGET_STATS frameTargetStats;
COMPOSITION_TARGET_STATS targetStats[4];
frameTargetStats.targetCount = ARRAYSIZE(targetStats);
frameTargetStats.targetStats = targetStats;
// Specify the frameId we got from stats in order to pass to the call
// below and retrieve timing information about that frame.
frameTargetStats.frameId = frameStatisticsItem->GetCompositionFrameId();
FAIL_FAST_IF_FAILED(DCompositionGetTargetStatistics(1, &frameTargetStats));
// Return the statistics information for the first target.
*pPresentCount = static_cast<UINT>(frameStatisticsItem->GetPresentId());
*pSyncQPCTime = frameTargetStats.targetStats[0].completedStats.time;
// Note that there's a much richer variety of statistics information in the new
// API that can be used to infer much more than is possible with DXGI frame
// statistics.
}
}
return hasNewStats;
}
static bool IsSupportedOnSystem()
{
// Direct3D device creation flags. The composition swapchain API requires that applications disable internal
// driver threading optimizations, as these optimizations break synchronization of the API.
// If this flag isn't present, then the API will fail the call to create the presentation factory.
UINT deviceCreationFlags =
D3D11_CREATE_DEVICE_BGRA_SUPPORT |
D3D11_CREATE_DEVICE_SINGLETHREADED |
D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS;
// Create the Direct3D device.
com_ptr_failfast<ID3D11Device> d3dDevice;
com_ptr_failfast<ID3D11DeviceContext> d3dDeviceContext;
FAIL_FAST_IF_FAILED(D3D11CreateDevice(
nullptr, // No adapter
D3D_DRIVER_TYPE_HARDWARE, // Hardware device
nullptr, // No module
deviceCreationFlags, // Device creation flags
nullptr, 0, // Highest available feature level
D3D11_SDK_VERSION, // API version
&d3dDevice, // Resulting interface pointer
nullptr, // Actual feature level
&d3dDeviceContext)); // Device context
// Call the composition swapchain API export to create the presentation factory.
com_ptr_failfast<IPresentationFactory> presentationFactory;
FAIL_FAST_IF_FAILED(CreatePresentationFactory(
d3dDevice.get(),
IID_PPV_ARGS(&presentationFactory)));
// Now determine whether the system is capable of supporting the composition swapchain API based
// on the capability that's reported by the presentation factory.
return presentationFactory->IsPresentationSupported();
}
private:
com_ptr_failfast<IPresentationManager> m_presentationManager;
com_ptr_failfast<IPresentationSurface> m_presentationSurface;
vector<com_ptr_failfast<ID3D11Texture2D>> m_textures;
vector<com_ptr_failfast<IPresentationBuffer>> m_presentationBuffers;
vector<unique_event> m_bufferAvailableEvents;
unique_handle m_presentationSurfaceHandle;
unique_event m_statisticsAvailableEvent;
};
void DXGIOrPresentationAPIExample()
{
// Get the current system time. We'll base our 'PresentAt' time on this result.
SystemInterruptTime currentTime;
QueryInterruptTimePrecise(¤tTime.value);
// Track a time we'll be presenting at below. Default to the current time, then increment by
// 1/10th of a second every present.
auto presentTime = currentTime;
// Allocate a presentation provider using the composition swapchain API if it is supported;
// otherwise fall back to DXGI.
unique_ptr<PresentationProvider> presentationProvider;
if (PresentationAPIProvider::IsSupportedOnSystem())
{
presentationProvider = std::make_unique<PresentationAPIProvider>(
500, // Buffer width
500, // Buffer height
6); // Number of buffers
}
else
{
// System doesn't support the composition swapchain API. Fall back to DXGI.
presentationProvider = std::make_unique<DXGIProvider>(
500, // Buffer width
500, // Buffer height
6); // Number of buffers
}
// Present 50 times.
constexpr UINT numPresents = 50;
for (UINT i = 0; i < 50; i++)
{
// Advance our present time 1/10th of a second in the future.
presentTime.value += 1'000'000;
// Call the presentation provider to get a backbuffer to render to.
com_ptr_failfast<ID3D11Texture2D> backBuffer;
presentationProvider->GetBackBuffer(&backBuffer);
// Render to the backbuffer.
DrawColorToSurface(
presentationProvider->GetD3D11DeviceNoRef(),
backBuffer,
1.0f, // red
0.0f, // green
0.0f); // blue
// Present the backbuffer.
presentationProvider->Present(presentTime);
// Process statistics.
bool hasNewStats;
UINT64 presentTime;
UINT presentCount;
hasNewStats = presentationProvider->ReadStatistics(
&presentCount,
&presentTime);
if (hasNewStats)
{
// Process these statistics however your application wishes.
}
}
}