モバイル デバイスでのレンダリングを高速化するためにスケーリングされたスワップ チェーンを作成し、オーバーレイ スワップ チェーン (使用可能な場合) を使用して視覚的な品質を向上させる方法について説明します。
DirectX 11.2 のスワップ チェーン
Direct3D 11.2 を使用すると、非ネイティブ (縮小) 解像度からスケールアップされたスワップ チェーンを使用してユニバーサル Windows プラットフォーム (UWP) アプリを作成でき、フィル レートを高速化できます。 Direct3D 11.2 には、ネイティブ解像度で別のスワップ チェーンで UI を表示できるように、ハードウェア オーバーレイを使用してレンダリングするための API も含まれています。 これにより、高いフレームレートを維持しながら完全なネイティブ解像度で UI を描画できるため、モバイル デバイスと高 DPI ディスプレイ (3840 x 2160 など) を最大限に活用できます。 この記事では、重複するスワップ チェーンを使用する方法について説明します。
Direct3D 11.2 には、フリップ モデル スワップ チェーンを使用して待機時間を短縮するための新機能も導入されています。 DXGI 1.3 スワップ チェーンによる待機時間の短縮を参照してください。
スワップ チェーンのスケーリングを使用する
ゲームがダウンレベルのハードウェア (または省電力用に最適化されたハードウェア) で実行されている場合は、ディスプレイがネイティブに対応しているよりも低い解像度でリアルタイムのゲーム コンテンツをレンダリングすると便利な場合があります。 これを行うには、ゲーム コンテンツのレンダリングに使用されるスワップ チェーンがネイティブ解像度よりも小さいか、スワップチェーンのサブ領域を使用する必要があります。
最初に、完全なネイティブ解像度でスワップ チェーンを作成します。
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0}; swapChainDesc.Width = static_cast<UINT>(m_d3dRenderTargetSize.Width); // Match the size of the window. swapChainDesc.Height = static_cast<UINT>(m_d3dRenderTargetSize.Height); swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format. swapChainDesc.Stereo = false; swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling. swapChainDesc.SampleDesc.Quality = 0; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.BufferCount = 2; // Use double-buffering to minimize latency. swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All UWP apps must use this SwapEffect. swapChainDesc.Flags = 0; swapChainDesc.Scaling = DXGI_SCALING_STRETCH; // This sequence obtains the DXGI factory that was used to create the Direct3D device above. ComPtr<IDXGIDevice3> dxgiDevice; DX::ThrowIfFailed( m_d3dDevice.As(&dxgiDevice) ); ComPtr<IDXGIAdapter> dxgiAdapter; DX::ThrowIfFailed( dxgiDevice->GetAdapter(&dxgiAdapter) ); ComPtr<IDXGIFactory2> dxgiFactory; DX::ThrowIfFailed( dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory)) ); ComPtr<IDXGISwapChain1> swapChain; DX::ThrowIfFailed( dxgiFactory->CreateSwapChainForCoreWindow( m_d3dDevice.Get(), reinterpret_cast<IUnknown*>(m_window.Get()), &swapChainDesc, nullptr, &swapChain ) ); DX::ThrowIfFailed( swapChain.As(&m_swapChain) );
次に、ソース サイズを縮小解像度に設定してスケールアップするスワップ チェーンのサブリージョンを選択します。
DX フォアグラウンド スワップ チェーンのサンプルでは、パーセンテージに基づいて縮小されたサイズが計算されます。
m_d3dRenderSizePercentage = percentage; UINT renderWidth = static_cast<UINT>(m_d3dRenderTargetSize.Width * percentage + 0.5f); UINT renderHeight = static_cast<UINT>(m_d3dRenderTargetSize.Height * percentage + 0.5f); // Change the region of the swap chain that will be presented to the screen. DX::ThrowIfFailed( m_swapChain->SetSourceSize( renderWidth, renderHeight ) );
スワップ チェーンのサブ領域に一致するビューポートを作成します。
// In Direct3D, change the Viewport to match the region of the swap // chain that will now be presented from. m_screenViewport = CD3D11_VIEWPORT( 0.0f, 0.0f, static_cast<float>(renderWidth), static_cast<float>(renderHeight) ); m_d3dContext->RSSetViewports(1, &m_screenViewport);
Direct2D を使用している場合は、ソース領域を補正するために回転変換を調整する必要があります。
UI 要素のハードウェア オーバーレイ スワップ チェーンを作成する
スワップ チェーンのスケーリングを使用する場合、UI もスケールダウンされ、ぼやけて使いづらくなるという欠点があります。 オーバーレイ スワップ チェーンをハードウェアでサポートしているデバイスでは、リアルタイム のゲーム コンテンツとは別のスワップ チェーンでネイティブ解像度で UI をレンダリングすることで、この問題が完全に軽減されます。 この手法は CoreWindow スワップ チェーンにのみ適用されることに注意してください。XAML 相互運用では使用できません。
ハードウェア オーバーレイ機能を使用するフォアグラウンド スワップ チェーンを作成するには、次の手順に従います。 これらの手順は、前述のように、最初にリアルタイムのゲーム コンテンツのスワップ チェーンを作成した後に実行されます。
まず、DXGI アダプターがオーバーレイをサポートしているかどうかを確認します。 スワップ チェーンから DXGI 出力アダプターを取得します。
ComPtr<IDXGIAdapter> outputDxgiAdapter; DX::ThrowIfFailed( dxgiFactory->EnumAdapters(0, &outputDxgiAdapter) ); ComPtr<IDXGIOutput> dxgiOutput; DX::ThrowIfFailed( outputDxgiAdapter->EnumOutputs(0, &dxgiOutput) ); ComPtr<IDXGIOutput2> dxgiOutput2; DX::ThrowIfFailed( dxgiOutput.As(&dxgiOutput2) );
出力アダプターが True を返す場合、DXGI アダプターは SupportsOverlaysでオーバーレイをサポートします。
m_overlaySupportExists = dxgiOutput2->SupportsOverlays() ? true : false;
手記 DXGI アダプターがオーバーレイをサポートしている場合は、次の手順に進みます。 デバイスがオーバーレイをサポートしていない場合、複数のスワップ チェーンを使用したレンダリングは効率的ではありません。 代わりに、リアルタイム ゲーム コンテンツと同じスワップ チェーン内の解像度を下げ、UI をレンダリングします。
IDXGIFactory2::CreateSwapChainForCoreWindow を使用してフォアグラウンド スワップ チェーンを作成します。 pDesc パラメーターに指定するDXGI_SWAP_CHAIN_DESC1では、次のオプションを設定する必要があります。
- DXGI_SWAP_CHAIN_FLAG_FOREGROUND_LAYERスワップ チェーン フラグを指定して、フォアグラウンド スワップ チェーンを示します。
- DXGI_ALPHA_MODE_PREMULTIPLIEDアルファ モード フラグを使用します。 フォアグラウンド スワップ チェーンは常に事前乗算されます。
- DXGI_SCALING_NONE フラグを設定します。 フォアグラウンド スワップ チェーンは常にネイティブ解像度で動作します。
foregroundSwapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_FOREGROUND_LAYER; foregroundSwapChainDesc.Scaling = DXGI_SCALING_NONE; foregroundSwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; // Foreground swap chain alpha values must be premultiplied.
手記 スワップ チェーンのサイズが変更されるたびに、 DXGI_SWAP_CHAIN_FLAG_FOREGROUND_LAYER をもう一度設定します。
HRESULT hr = m_foregroundSwapChain->ResizeBuffers( 2, // Double-buffered swap chain. static_cast<UINT>(m_d3dRenderTargetSize.Width), static_cast<UINT>(m_d3dRenderTargetSize.Height), DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_SWAP_CHAIN_FLAG_FOREGROUND_LAYER // The FOREGROUND_LAYER flag cannot be removed with ResizeBuffers. );
2 つのスワップ チェーンが使用されている場合は、フレームの最大待機時間を 2 に増やして、DXGI アダプターが両方のスワップ チェーンを同時に (同じ VSync 間隔内で) 提示する時間が与えられるようにします。
// Create a render target view of the foreground swap chain's back buffer. if (m_foregroundSwapChain) { ComPtr<ID3D11Texture2D> foregroundBackBuffer; DX::ThrowIfFailed( m_foregroundSwapChain->GetBuffer(0, IID_PPV_ARGS(&foregroundBackBuffer)) ); DX::ThrowIfFailed( m_d3dDevice->CreateRenderTargetView( foregroundBackBuffer.Get(), nullptr, &m_d3dForegroundRenderTargetView ) ); }
フォアグラウンド スワップ チェーンでは、常に事前乗算されたアルファが使用されます。 フレームが表示される前に、各ピクセルのカラー値にアルファ値が既に乗算されている必要があります。 たとえば、50% アルファで 100% 白い BGRA ピクセルは (0.5、0.5、0.5、0.5) に設定されます。
アルファ乗算ステップは、D3D11_RENDER_TARGET_BLEND_DESC 構造体の SrcBlend フィールドを D3D11_SRC_ALPHAに設定して、アプリ ブレンド状態 (ID3D11BlendStateを参照) を適用することで、出力マージャー ステージで実行できます。 事前乗算されたアルファ値を持つアセットも使用できます。
アルファ事前乗算ステップが実行されていない場合、フォアグラウンド スワップ チェーンの色は予想よりも明るくなります。
フォアグラウンド スワップ チェーンが作成されたかどうかに応じて、UI 要素の Direct2D 描画サーフェイスをフォアグラウンド スワップ チェーンに関連付ける必要がある場合があります。
レンダー ターゲット ビューの作成:
// Create a render target view of the foreground swap chain's back buffer. if (m_foregroundSwapChain) { ComPtr<ID3D11Texture2D> foregroundBackBuffer; DX::ThrowIfFailed( m_foregroundSwapChain->GetBuffer(0, IID_PPV_ARGS(&foregroundBackBuffer)) ); DX::ThrowIfFailed( m_d3dDevice->CreateRenderTargetView( foregroundBackBuffer.Get(), nullptr, &m_d3dForegroundRenderTargetView ) ); }
Direct2D 描画サーフェイスの作成:
if (m_foregroundSwapChain) { // Create a Direct2D target bitmap for the foreground swap chain. ComPtr<IDXGISurface2> dxgiForegroundSwapChainBackBuffer; DX::ThrowIfFailed( m_foregroundSwapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiForegroundSwapChainBackBuffer)) ); DX::ThrowIfFailed( m_d2dContext->CreateBitmapFromDxgiSurface( dxgiForegroundSwapChainBackBuffer.Get(), &bitmapProperties, &m_d2dTargetBitmap ) ); } else { // Create a Direct2D target bitmap for the swap chain. ComPtr<IDXGISurface2> dxgiSwapChainBackBuffer; DX::ThrowIfFailed( m_swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiSwapChainBackBuffer)) ); DX::ThrowIfFailed( m_d2dContext->CreateBitmapFromDxgiSurface( dxgiSwapChainBackBuffer.Get(), &bitmapProperties, &m_d2dTargetBitmap ) ); } m_d2dContext->SetTarget(m_d2dTargetBitmap.Get());
// Create a render target view of the swap chain's back buffer. ComPtr<ID3D11Texture2D> backBuffer; DX::ThrowIfFailed( m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer)) ); DX::ThrowIfFailed( m_d3dDevice->CreateRenderTargetView( backBuffer.Get(), nullptr, &m_d3dRenderTargetView ) );
フォアグラウンド スワップ チェーンと、リアルタイム のゲーム コンテンツに使用されるスケーリングされたスワップ チェーンを提示します。 両方のスワップ チェーンでフレーム待機時間が 2 に設定されているため、DXGI は同じ VSync 間隔内で両方を提示できます。
// Present the contents of the swap chain to the screen. void DX::DeviceResources::Present() { // The first argument instructs DXGI to block until VSync, putting the application // to sleep until the next VSync. This ensures that we don't waste any cycles rendering // frames that will never be displayed to the screen. HRESULT hr = m_swapChain->Present(1, 0); if (SUCCEEDED(hr) && m_foregroundSwapChain) { m_foregroundSwapChain->Present(1, 0); } // Discard the contents of the render targets. // This is a valid operation only when the existing contents will be entirely // overwritten. If dirty or scroll rects are used, this call should be removed. m_d3dContext->DiscardView(m_d3dRenderTargetView.Get()); if (m_foregroundSwapChain) { m_d3dContext->DiscardView(m_d3dForegroundRenderTargetView.Get()); } // Discard the contents of the depth stencil. m_d3dContext->DiscardView(m_d3dDepthStencilView.Get()); // If the device was removed either by a disconnection or a driver upgrade, we // must recreate all device resources. if (hr == DXGI_ERROR_DEVICE_REMOVED) { HandleDeviceLost(); } else { DX::ThrowIfFailed(hr); } }