DirectX と XAML の相互運用機能 (DirectX と C++ を使った Windows ランタイム アプリ)

Windows 8 のリリースに伴い、Windows ストア アプリで Extensible Application Markup Language (XAML) と Microsoft DirectX を組み合わせて使うことが可能になりました。 XAML と DirectX を組み合わせれば、DirectX でレンダリングしたコンテンツと相互運用できる柔軟なユーザー インターフェイス フレームワークを構築できます。これは、グラフィックスを多用するアプリで特に役立ちます。このトピックでは、DirectX を使った Windows ストア アプリの構造について説明し、DirectX と連携する Windows ストア アプリを構築するときに使う重要な型を示します。

  DirectX API は Windows ランタイム型として定義されていないため、DirectX と相互運用する XAML Windows ストア アプリのコンポーネントを開発するときは Visual C++ コンポーネント拡張機能 (C++/CX) を使うのが一般的です。また、DirectX の呼び出しを独立した Windows ランタイム メタデータ ファイルにラップすると、C# と DirectX を使う XAML を使って Windows ストア アプリを作成できます。

XAML と DirectX

DirectX には、2D と 3D のグラフィックス用に、Direct2D と Microsoft Direct3D という 2 つの強力なライブラリがあります。XAML でも基本的な 2D のプリミティブと効果はサポートされますが、モデリングやゲームなどの多くのアプリでは、より複雑なグラフィックス サポートが必要になります。そのようなアプリでは、Direct2D と Direct3D を使ってグラフィックスの一部または全体をレンダリングし、それ以外の部分には XAML を使うことができます。

XAML と DirectX の相互運用シナリオでは、次の 2 つの概念を理解する必要があります。

  • 共有サーフェイス。Windows::UI::Xaml::Media::Brush 型を使って DirectX で間接的に描画を行うことができる、サイズが指定されたディスプレイの領域です。この領域は XAML で定義されます。共有サーフェイスについては、スワップ チェーンを表示する呼び出しの制御は行いません。共有サーフェイスの更新は XAML フレームワークの更新に同期されます。
  • スワップ チェーン。DirectX のレンダリング パイプラインのバック バッファーを提供します。これはレンダー ターゲットの完了後に表示するメモリの領域です。

次に、DirectX を使う目的を確認します。 表示ウィンドウのサイズに収まる 1 つのコントロールを作ったりアニメーション化したりするために使うのか、作ったサーフェイスが他のサーフェイスまたは画面の端でふさがれる可能性はあるのか、ゲームなどのようにリアルタイムでレンダリングして制御する必要がある出力をサーフェイスに表示するのかを確認します。

DirectX をどのように使うかを決めたら、目的に応じて次のいずれかの Windows ランタイム型を使って、DirectX のレンダリングを Windows ストア アプリに組み込みます。

  • 静的な画像を構成する場合やイベント駆動型の複雑な画像を描画する場合は、Windows::UI::Xaml::Media::Imaging::SurfaceImageSource を使って共有サーフェイスに描画します。これは、サイズが指定された DirectX の描画サーフェイスを処理する型です。通常は、ドキュメントや UI 要素でビットマップとして表示する画像やテクスチャを構成する場合に使います。この型は、高パフォーマンスのゲームのようなリアルタイムのインタラクティビティには適しません。SurfaceImageSource オブジェクトの更新が XAML ユーザー インターフェイスの更新に同期されるため、フレーム レートが安定しない、リアルタイムの入力に対する応答が遅いなど、ユーザーへの視覚的フィードバックに待ち時間が生じるためです。ただし、動的なコントロールやデータ シミュレーションであれば、更新に時間はかからず問題ありません。

    SurfaceImageSource グラフィックス オブジェクトは、他の XAML UI 要素と合成できます。それらを変換または投影すると、XAML フレームワークに不透明度や z インデックスの値が適用されます。

  • 画像が画面上のスペースよりも大きく、ユーザーがパンまたはズームできる場合は、Windows::UI::Xaml::Media::Imaging::VirtualSurfaceImageSource を使います。これは、画面よりも大きいサイズが指定された DirectX の描画サーフェイスを処理する型です。SurfaceImageSource と同様に、複雑な画像やコントロールを動的に構成する場合に使います。また、SurfaceImageSource と同様に、高パフォーマンスのゲームには適しません。VirtualSurfaceImageSource を使うことができる XAML 要素には、マップ コントロールや、画像が多い大きなドキュメント ビューアーなどがあります。

  • リアルタイムで更新されるグラフィックスを DirectX を使って表示する場合や、短い待ち時間で定期的に更新を行う必要がある場合は、SwapChainPanel クラスを使います。これにより、XAML フレームワークの更新タイマーに同期せずにグラフィックスを更新することができます。この型を使うと、グラフィックス デバイスのスワップ チェーン (IDXGISwapChain1) に直接アクセスし、XAML をレンダー ターゲットの上に配置できます。この型は、ゲームなどの全画面の DirectX アプリで XAML ベースのユーザー インターフェイスが必要な場合に便利です。 Microsoft DirectX Graphics Infrastructure (DXGI)、Direct2D、Direct3D も含めて、この方法を使うには、DirectX に関する知識が必要です。詳しくは、「Direct3D 11 用プログラミング ガイド」をご覧ください。

SurfaceImageSource

SurfaceImageSource は、DirectX で描画を行うための共有サーフェイスを提供し、ビットからアプリのコンテンツを構成します。

SurfaceImageSource オブジェクトをコード ビハインドで作って更新する基本的なプロセスを次に示します。

  1. SurfaceImageSource コンストラクターに高さと幅の値を渡して、共有サーフェイスのサイズを定義します。アルファ (不透明度) のサポートが必要かどうかも指定できます。

    例:

    SurfaceImageSource^ surfaceImageSource = ref new SurfaceImageSource(400, 300);

  2. ISurfaceImageSourceNative へのポインターを取得します。SurfaceImageSource オブジェクトを IInspectable (または IUnknown) としてキャストし、それに対する QueryInterface を呼び出して、基になる ISurfaceImageSourceNative 実装を取得します。この実装で定義されているメソッドを使って、デバイスを設定し、描画操作を実行します。

    
    
    Microsoft::WRL::ComPtr<ISurfaceImageSourceNative>	m_sisNative;
    // ...
    IInspectable* sisInspectable = (IInspectable*) reinterpret_cast<IInspectable*>(surfaceImageSource);
    sisInspectable->QueryInterface(__uuidof(ISurfaceImageSourceNative), (void **)&m_sisNative);
    	
    
    
  3. D3D11CreateDevice を呼び出してから ISurfaceImageSourceNative::SetDevice にデバイスとコンテキストを渡して、DXGI デバイスを設定します。次に例を示します。

    
    
    Microsoft::WRL::ComPtr<ID3D11Device>				m_d3dDevice;
    Microsoft::WRL::ComPtr<ID3D11DeviceContext>			m_d3dContext;
    // ...
    D3D11CreateDevice(
    		NULL,
    		D3D_DRIVER_TYPE_HARDWARE,
    		NULL,
    		flags,
    		featureLevels,
    		ARRAYSIZE(featureLevels),
    		D3D11_SDK_VERSION,
    		&m_d3dDevice,
    		NULL,
    		&m_d3dContext
    		)
    	);	
    Microsoft::WRL::ComPtr<IDXGIDevice> dxgiDevice;
    m_d3dDevice.As(&dxgiDevice);
    // ...
    m_sisNative->SetDevice(dxgiDevice.Get());
    
    
  4. IDXGISurface オブジェクトへのポインターを ISurfaceImageSourceNative::BeginDraw に渡し、DirectX を使ってそのサーフェイスに描画します。updateRect パラメーターで更新対象として指定した領域だけが描画されます。

      IDXGIDevice でアクティブにできる未処理の BeginDraw 操作は、一度に 1 つだけです。

    このメソッドは、更新されるターゲットの四角形の位置 (x、y) を示すオフセットを offset パラメーターで返します。このオフセットを使って、IDXGISurface 内の描画する位置を特定できます。

    
    ComPtr<IDXGISurface> surface;
    
    HRESULT beginDrawHR = m_sisNative->BeginDraw(updateRect, &surface, &offset);
    if (beginDrawHR == DXGI_ERROR_DEVICE_REMOVED || beginDrawHR == DXGI_ERROR_DEVICE_RESET)
    {
    		  // device changed
    }
    else
    {
        // draw to IDXGISurface (the surface paramater)
    }
    
    
    
  5. ISurfaceImageSourceNative::EndDraw を呼び出してビットマップを終了します。このビットマップを ImageBrush に渡します。

    
    m_sisNative->EndDraw();
    // ...
    // The SurfaceImageSource object's underlying ISurfaceImageSourceNative object contains the completed bitmap.
    ImageBrush^ brush = ref new ImageBrush();
    brush->ImageSource = surfaceImageSource;
    
    
    
  6. ImageBrush を使ってビットマップを描画します。

  現在、SurfaceImageSource::SetSource (IBitmapSource::SetSource から継承) を呼び出すと例外がスローされます。SurfaceImageSource オブジェクトから呼び出さないでください。

VirtualSurfaceImageSource

VirtualSurfaceImageSource は、コンテンツが画面に収まらず、仮想化しないと最適なレンダリングにならない場合に、SurfaceImageSource を拡張します。

VirtualSurfaceImageSourceSurfaceImageSource と異なる点は、IVirtualSurfaceImageSourceCallbacksNative::UpdatesNeeded コールバックを使うことです。このコールバックを実装することで、サーフェイスの領域が画面に表示できるようになったときに領域が更新されます。非表示の領域をクリアする必要はありません。この処理は XAML フレームワークで行われます。

VirtualSurfaceImageSource オブジェクトをコード ビハインドで作成および更新する基本的なプロセスを次に示します。

  1. サイズを指定して VirtualSurfaceImageSource のインスタンスを作成します。例:

    VirtualSurfaceImageSource^ virtualSIS = ref new VirtualSurfaceImageSource(2000, 2000);

  2. IVirtualSurfaceImageSourceNative へのポインターを取得します。VirtualSurfaceImageSource オブジェクトを IInspectable または IUnknown としてキャストし、それに対する QueryInterface を呼び出して、基になる IVirtualSurfaceImageSourceNative 実装を取得します。この実装で定義されているメソッドを使って、デバイスを設定し、描画操作を実行します。

    
    
    Microsoft::WRL::ComPtr<IVirtualSurfaceImageSourceNative>	m_vsisNative;
    // ...
    IInspectable* vsisInspectable = (IInspectable*) reinterpret_cast<IInspectable*>(virtualSIS);
    vsisInspectable->QueryInterface(__uuidof(IVirtualSurfaceImageSourceNative), (void **)&m_vsisNative);
    	
    
    
  3. IVirtualSurfaceImageSourceNative::SetDevice を呼び出して DXGI デバイスを設定します。例:

    
    
    Microsoft::WRL::ComPtr<ID3D11Device>				m_d3dDevice;
    Microsoft::WRL::ComPtr<ID3D11DeviceContext>			m_d3dContext;
    // ...
    D3D11CreateDevice(
    		NULL,
    		D3D_DRIVER_TYPE_HARDWARE,
    		NULL,
    		flags,
    		featureLevels,
    		ARRAYSIZE(featureLevels),
    		D3D11_SDK_VERSION,
    		&m_d3dDevice,
    		NULL,
    		&m_d3dContext
    		)
    	);	
    Microsoft::WRL::ComPtr<IDXGIDevice> dxgiDevice;
    m_d3dDevice.As(&dxgiDevice);
    // ...
    m_vsisNative->SetDevice(dxgiDevice.Get());
    
    
  4. IVirtualSurfaceImageSourceNative::RegisterForUpdatesNeeded を呼び出して、IVirtualSurfaceUpdatesCallbackNative の実装への参照を渡します。

    
    
    class MyContentImageSource : public IVirtualSurfaceUpdatesCallbackNative
    {
    // ...
      private:
         virtual HRESULT STDMETHODCALLTYPE UpdatesNeeded() override;
    }
    
    // ...
    
    HRESULT STDMETHODCALLTYPE MyContentImageSource::UpdatesNeeded()
    {
      // .. perform drawing here ...
    }
    void MyContentImageSource::Initialize()
    {
      // ...
      m_vsisNative->RegisterForUpdatesNeeded(this);
      // ...
    }
    
    
    

    VirtualSurfaceImageSource の領域を更新する必要がある場合、フレームワークで IVirtualSurfaceUpdatesCallbackNative::UpdatesNeeded の実装が呼び出されます。

    これが発生するのは、領域を描画する必要があるとフレームワークで特定されたとき (ユーザーがサーフェイスのビューをパンまたはズームしたときなど) と、その領域に対する IVirtualSurfaceImageSourceNative::Invalidate がアプリで呼び出されたときです。

  5. IVirtualSurfaceImageSourceNative::UpdatesNeeded で、IVirtualSurfaceImageSourceNative::GetUpdateRectCount メソッドと IVirtualSurfaceImageSourceNative::GetUpdateRects メソッドを使って、描画する必要があるサーフェイスの領域を特定します。

    
    
    
    
    HRESULT STDMETHODCALLTYPE MyContentImageSource::UpdatesNeeded()
    {
        HRESULT hr = S_OK;
    
        try
        {
            ULONG drawingBoundsCount = 0;  
    
    		      m_vsisNative->GetUpdateRectCount(&drawingBoundsCount);
            std::unique_ptr<RECT[]> drawingBounds(new RECT[drawingBoundsCount]);
            m_vsisNative->GetUpdateRects(drawingBounds.get(), drawingBoundsCount);
            
            for (ULONG i = 0; i < drawingBoundsCount; ++i)
            {
                // Drawing code here ...
            }
        }
        catch (Platform::Exception^ exception)
        {
            hr = exception->HResult;
        }
    
        return hr;
    }
    
    
  6. 最後に、更新する必要がある領域ごとに以下を行います。

    1. IDXGISurface オブジェクトへのポインターを IVirtualSurfaceImageSourceNative::BeginDraw に渡し、DirectX を使ってそのサーフェイスに描画します。updateRect パラメーターで更新対象として指定した領域だけが描画されます。

      IlSurfaceImageSourceNative::BeginDraw と同様に、このメソッドは、更新されるターゲットの四角形の位置 (x、y) を示すオフセットを offset パラメーターで返します。このオフセットを使って、IDXGISurface 内の描画する位置を特定できます。

        IDXGIDevice でアクティブにできる未処理の BeginDraw 操作は、一度に 1 つだけです。
      
      ComPtr<IDXGISurface> bigSurface;
      
      HRESULT beginDrawHR = m_vsisNative->BeginDraw(updateRect, &bigSurface, &offset);
      if (beginDrawHR == DXGI_ERROR_DEVICE_REMOVED || beginDrawHR == DXGI_ERROR_DEVICE_RESET)
      {
      		  // device changed
      }
      else
      {
          // draw to IDXGISurface
      }
      
      
      
    2. 具体的なコンテンツをその領域に描画します。ただし、パフォーマンスを高めるために描画を限られた領域に制限します。

    3. IVirtualSurfaceImageSourceNative::EndDraw を呼び出します。結果のビットマップが返されます。

SwapChainPanel とゲーム

SwapChainPanel は、高パフォーマンスのグラフィックスやゲームをサポートするために設計された Windows ランタイム型です。この型でスワップ チェーンを直接管理します。この例では、独自の DirectX スワップ チェーンを作成し、レンダリングされるコンテンツの表示を管理します。その後、メニュー、ヘッドアップ ディスプレイ、その他の UI オーバーレイなどの XAML 要素を SwapChainPanel オブジェクトに追加できます。

パフォーマンスを高めるために、SwapChainPanel 型には次のような制限事項があります。

  • SwapChainPanel インスタンスの数はアプリごとに 4 つ以下です。
  • OpacityRenderTransformProjectionClip の各プロパティの SwapChainPanel による継承はサポートされていません。
  • DirectX スワップ チェーンの高さと幅 (DXGI_SWAP_CHAIN_DESC1 で設定) は、アプリ ウィンドウの現在のサイズに設定することをお勧めします。このように設定しないと、表示されるコンテンツのサイズが自動的に調整されます (DXGI_SCALING_STRETCH を使用)。
  • DirectX スワップ チェーンのスケーリング モード (DXGI_SWAP_CHAIN_DESC1 で設定) は、DXGI_SCALING_STRETCH に設定する必要があります。
  • DirectX スワップ チェーンのアルファ モード (DXGI_SWAP_CHAIN_DESC1 で設定) を DXGI_ALPHA_MODE_PREMULTIPLIED に設定することはできません。
  • DirectX スワップ チェーンを作成するときは、IDXGIFactory2::CreateSwapChainForComposition を呼び出す必要があります。

SwapChainPanel の更新は、XAML フレームワークの更新ではなく、アプリのニーズに基づいて行います。SwapChainPanel の更新を XAML フレームワークの更新に同期する必要がある場合は、Windows::UI::Xaml::Media::CompositionTarget::Rendering イベントに登録します。このイベントに登録しないと、SwapChainPanel を更新するスレッドと異なるスレッドから XAML 要素を更新する場合に、クロス スレッドの問題についての検討が必要になります。

次に、SwapChainPanel を使うアプリを設計する際の一般的なヒントをいくつか紹介します。

  • SwapChainPanelWindows::UI::Xaml::Controls::Grid を継承し、同様のレイアウト動作をサポートします。Grid 型とそのプロパティについて確認しておいてください。

  • DirectX スワップ チェーンの設定後、SwapChainPanel に対する入力イベントはすべて他の XAML 要素と同じように機能します。SwapChainPanel に対しては背景ブラシを設定しません。また、SwapChainPanel を使わない DirectX アプリとは異なり、アプリの CoreWindow オブジェクトからの入力イベントを直接処理する必要はありません。

  • XAML 視覚要素のコンテンツのうち、ビジュアル ツリーで SwapChainPanel の直接の子の下にあるコンテンツは、いずれも SwapChainPanel オブジェクトの直接の子のレイアウト サイズに合わせてクリッピングされます。 変換後にそれらのレイアウトの境界に収まらないコンテンツはレンダリングされません。 そのため、XAML の Storyboard でアニメーション化する XAML コンテンツをビジュアル ツリーに配置するときは、アニメーションのすべての範囲がレイアウトの境界に収まる大きさの要素の下に配置します。

  • SwapChainPanel の直接の子にする XAML 視覚要素の数を制限します。 近接する要素は、できるだけ共通の親の下にまとめます。 ただし、XAML 要素が多すぎたり必要以上に大きいと全体のパフォーマンスに影響することがあるため、直接の子視覚要素の数とサイズについてはパフォーマンスとのバランスに注意する必要があります。同様に、アプリの SwapChainPanel の子 XAML 要素を単一の全画面の要素にすると、過剰な描画が増えてアプリのパフォーマンスが低下するため、このような要素は作成しないようにします。 一般に、アプリの SwapChainPanel に対して作成する直接の子 XAML 視覚要素は 8 つまでにします。また、各要素のレイアウト サイズは、要素のコンテンツを表示するために必要な大きさに制限する必要があります。 ただし、SwapChainPanel の子要素の下のビジュアル ツリーについては、ある程度複雑にしてもパフォーマンスはそれほど低下しません。

   一般に、DirectX アプリでは、サイズが表示ウィンドウのサイズ (通常は、ほとんどの Windows ストア ゲームのネイティブの画面解像度) と同じである横方向のスワップ チェーンを作る必要があります。 これにより、表示される XAML オーバーレイがない場合はアプリで最適なスワップ チェーンの実装が使われます。 縦モードに回転した場合、アプリは既にあるスワップ チェーンで IDXGISwapChain1::SetRotation を呼び出し、必要に応じてコンテンツに変換を適用して、同じスワップ チェーンで SetSwapChain をもう一度呼び出す必要があります。同様に、アプリは、IDXGISwapChain::ResizeBuffers 呼び出しによってスワップ チェーンのサイズが変更されるたびに、同じスワップ チェーンで SetSwapChain をもう一度呼び出す必要があります。

SwapChainPanel オブジェクトをコード ビハインドで作って更新する基本的なプロセスを次に示します。

  1. アプリのスワップ チェーン パネルのインスタンスを取得します。これらのインスタンスは、XAML では <SwapChainPanel> タグを使って示されます。

    Windows::UI::Xaml::Controls::SwapChainPanel^ swapChainPanel;

    <SwapChainPanel> タグの例を次に示します。

    
    <SwapChainPanel x:Name="swapChainPanel">
        <SwapChainPanel.ColumnDefinitions>
            <ColumnDefinition Width="300*"/>
            <ColumnDefinition Width="1069*"/>
        </SwapChainPanel.ColumnDefinitions></SwapChainPanel>
    
    
    
  2. ISwapChainPanelNative へのポインターを取得します。SwapChainPanel オブジェクトを IInspectable (または IUnknown) としてキャストし、それに対する QueryInterface を呼び出して、基になる ISwapChainPanelNative 実装を取得します。

    
    
    Microsoft::WRL::ComPtr<ISwapChainPanelNative>	m_swapChainNative;
    // ...
    IInspectable* panelInspectable = (IInspectable*) reinterpret_cast<IInspectable*>(swapChainPanel);
    panelInspectable->QueryInterface(__uuidof(ISwapChainPanelNative), (void **)&m_swapChainNative);
    	
    
    
  3. DXGI デバイスとスワップ チェーンを作成し、スワップ チェーンを SetSwapChain に渡して ISwapChainPanelNative に設定します。

    
    
    Microsoft::WRL::ComPtr<IDXGISwapChain1>				m_swapChain;	
    // ...
    DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};
            swapChainDesc.Width = m_bounds.Width;
            swapChainDesc.Height = m_bounds.Height;
            swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;           // this is the most common swapchain 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;
            swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
            swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // we recommend using this swap effect for all applications
            swapChainDesc.Flags = 0;
            		
    // QI for DXGI device
    Microsoft::WRL::ComPtr<IDXGIDevice> dxgiDevice;
    m_d3dDevice.As(&dxgiDevice);
    
    // get the DXGI adapter
    Microsoft::WRL::ComPtr<IDXGIAdapter> dxgiAdapter;
    dxgiDevice->GetAdapter(&dxgiAdapter);
    
    // get the DXGI factory
    Microsoft::WRL::ComPtr<IDXGIFactory2> dxgiFactory;
    dxgiAdapter->GetParent(__uuidof(IDXGIFactory2), &dxgiFactory);
    // create swap chain by calling CreateSwapChainForComposition
    dxgiFactory->CreateSwapChainForComposition(
                m_d3dDevice.Get(),
                &swapChainDesc,
                nullptr,		// allow on any display 
                &m_swapChain
                );
    		
    m_swapChainNative->SetSwapChain(m_swapChain.Get());
    
    
  4. DirectX スワップ チェーンに描画し、それを渡してコンテンツを表示します。

    
    HRESULT hr = m_swapChain->Present(1, 0);
    
    

    XAML 要素は、Windows ランタイムのレイアウトやレンダリング ロジックから更新が通知されると更新されます。

関連トピック

SurfaceImageSource
VirtualSurfaceImageSource
SwapChainPanel
ISwapChainPanelNative
Direct3D 11 用プログラミング ガイド
SwapChainPanel のサンプル
XAML SwapChainPanel DirectX 相互運用性のサンプル
SurfaceImageSource のサンプル
XAML SurfaceImageSource DirectX 相互運用性のサンプル
VirtualSurfaceImageSource のサンプル
Direct2D 雑誌アプリのサンプル

 

 

表示:
© 2015 Microsoft