WPF と Direct3D9 の相互運用性

Windows Presentation Foundation (WPF) アプリケーションには Direct3D9 コンテンツを含めることができます。 このトピックでは、Direct3D9 コンテンツを作成して WPF と効率的に相互運用する方法について説明します。

注意

WPF で Direct3D9 コンテンツを使用する場合は、パフォーマンスについても考慮する必要があります。 パフォーマンスを最適化する方法の詳細については、「Direct3D9 および WPF の相互運用性のパフォーマンスに関する考慮事項」を参照してください。

バッファーを表示する

D3DImage クラスを使用して、"バック バッファー" と "フロント バッファー" という 2 つのディスプレイ バッファーを管理します。 バック バッファーは Direct3D9 のサーフェイスです。 Unlock メソッドを呼び出すと、バック バッファーに対する変更がフロント バッファーに転送されます。

次の図は、バック バッファーとフロント バッファーの関係を示しています。

D3DImage display buffers

Direct3D9 デバイスの作成

Direct3D9 コンテンツをレンダリングするには、Direct3D9 デバイスを作成する必要があります。 デバイスの作成に使用できる Direct3D9 オブジェクトには、IDirect3D9IDirect3D9Ex の 2 つがあります。 これらのオブジェクトを使用して、それぞれ IDirect3DDevice9 デバイスと IDirect3DDevice9Ex デバイスを作成します。

次のいずれかのメソッドを呼び出してデバイスを作成します。

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

  • HRESULT Direct3DCreate9Ex(UINT SDKVersion, IDirect3D9Ex **ppD3D);

Windows Vista 以降のオペレーティング システムでは、Windows ディスプレイ ドライバー モデル (WDDM) を使用するように構成されたディスプレイで Direct3DCreate9Ex メソッドを使用します。 他のプラットフォームでは Direct3DCreate9 メソッドを使用します。

Direct3DCreate9Ex メソッドの可用性

Windows Vista 以降のオペレーティング システムにのみ、d3d9.dll に Direct3DCreate9Ex メソッドがあります。 Windows XP でこの関数を直接リンクすると、アプリケーションの読み込みに失敗します。 Direct3DCreate9Ex メソッドがサポートされているかどうかを確認するには、DLL を読み込んで proc アドレスを探します。 次のコードは、Direct3DCreate9Ex メソッドをテストする方法を示しています。 完全なコード例については、「チュートリアル: WPF でホストするための Direct3D9 コンテンツの作成」を参照してください。

HRESULT
CRendererManager::EnsureD3DObjects()
{
    HRESULT hr = S_OK;

    HMODULE hD3D = NULL;
    if (!m_pD3D)
    {
        hD3D = LoadLibrary(TEXT("d3d9.dll"));
        DIRECT3DCREATE9EXFUNCTION pfnCreate9Ex = (DIRECT3DCREATE9EXFUNCTION)GetProcAddress(hD3D, "Direct3DCreate9Ex");
        if (pfnCreate9Ex)
        {
            IFC((*pfnCreate9Ex)(D3D_SDK_VERSION, &m_pD3DEx));
            IFC(m_pD3DEx->QueryInterface(__uuidof(IDirect3D9), reinterpret_cast<void **>(&m_pD3D)));
        }
        else
        {
            m_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
            if (!m_pD3D) 
            {
                IFC(E_FAIL);
            }
        }

        m_cAdapters = m_pD3D->GetAdapterCount();
    }

Cleanup:
    if (hD3D)
    {
        FreeLibrary(hD3D);
    }

    return hr;
}

HWND の作成

デバイスを作成するには HWND が必要です。 通常、Direct3D9 に使用するダミーの HWND を作成します。 次のコード例は、ダミーの HWND を作成する方法を示しています。

HRESULT
CRendererManager::EnsureHWND()
{
    HRESULT hr = S_OK;

    if (!m_hwnd)
    {
        WNDCLASS wndclass;

        wndclass.style = CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc = DefWindowProc;
        wndclass.cbClsExtra = 0;
        wndclass.cbWndExtra = 0;
        wndclass.hInstance = NULL;
        wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
        wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
        wndclass.lpszMenuName = NULL;
        wndclass.lpszClassName = szAppName;

        if (!RegisterClass(&wndclass))
        {
            IFC(E_FAIL);
        }

        m_hwnd = CreateWindow(szAppName,
                            TEXT("D3DImageSample"),
                            WS_OVERLAPPEDWINDOW,
                            0,                   // Initial X
                            0,                   // Initial Y
                            0,                   // Width
                            0,                   // Height
                            NULL,
                            NULL,
                            NULL,
                            NULL);
    }

Cleanup:
    return hr;
}

Present パラメーター

デバイスの作成にも D3DPRESENT_PARAMETERS 構造体が必要ですが、重要なのは一部のパラメーターのみです。 これらのパラメーターは、メモリ フットプリントを最小限に抑えるために選択されます。

BackBufferHeight フィールドと BackBufferWidth フィールドを 1 に設定します。 これらを 0 に設定すると、HWND のディメンションに設定されます。

Direct3D9 に使用されるメモリの破損を防ぎ、Direct3D9 によって FPU 設定が変更されないようにするために、常に D3DCREATE_MULTITHREADED フラグと D3DCREATE_FPU_PRESERVE フラグを設定してください。

次のコードは、D3DPRESENT_PARAMETERS 構造体を初期化する方法を示しています。

HRESULT 
CRenderer::Init(IDirect3D9 *pD3D, IDirect3D9Ex *pD3DEx, HWND hwnd, UINT uAdapter)
{
    HRESULT hr = S_OK;

    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = TRUE;
    d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
    d3dpp.BackBufferHeight = 1;
    d3dpp.BackBufferWidth = 1;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;

    D3DCAPS9 caps;
    DWORD dwVertexProcessing;
    IFC(pD3D->GetDeviceCaps(uAdapter, D3DDEVTYPE_HAL, &caps));
    if ((caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) == D3DDEVCAPS_HWTRANSFORMANDLIGHT)
    {
        dwVertexProcessing = D3DCREATE_HARDWARE_VERTEXPROCESSING;
    }
    else
    {
        dwVertexProcessing = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
    }

    if (pD3DEx)
    {
        IDirect3DDevice9Ex *pd3dDevice = NULL;
        IFC(pD3DEx->CreateDeviceEx(
            uAdapter,
            D3DDEVTYPE_HAL,
            hwnd,
            dwVertexProcessing | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE,
            &d3dpp,
            NULL,
            &m_pd3dDeviceEx
            ));

        IFC(m_pd3dDeviceEx->QueryInterface(__uuidof(IDirect3DDevice9), reinterpret_cast<void**>(&m_pd3dDevice)));  
    }
    else 
    {
        assert(pD3D);

        IFC(pD3D->CreateDevice(
            uAdapter,
            D3DDEVTYPE_HAL,
            hwnd,
            dwVertexProcessing | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE,
            &d3dpp,
            &m_pd3dDevice
            ));
    }

Cleanup:
    return hr;
}

バック バッファー レンダー ターゲットの作成

Direct3D9 コンテンツを D3DImage で表示するには、Direct3D9 サーフェイスを作成し、SetBackBuffer メソッドを呼び出してそれを割り当てます。

アダプター サポートの確認

サーフェイスを作成する前に、必要なサーフェイスのプロパティがすべてのアダプターでサポートされていることを確認します。 1 つのアダプターのみにレンダリングする場合でも、システム内の任意のアダプターに WPF ウィンドウが表示されることがあります。 WPF によってサーフェイスが使用可能なアダプター間で移動される可能性があるため、常にマルチアダプター構成を処理する Direct3D9 コードを書き、すべてのアダプターのサポートを確認する必要があります。

次のコード例は、Direct3D9 のサポートについてシステム上のすべてのアダプターを確認する方法を示しています。

HRESULT
CRendererManager::TestSurfaceSettings()
{
    HRESULT hr = S_OK;

    D3DFORMAT fmt = m_fUseAlpha ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8;

    // 
    // We test all adapters because because we potentially use all adapters.
    // But even if this sample only rendered to the default adapter, you
    // should check all adapters because WPF may move your surface to
    // another adapter for you!
    //

    for (UINT i = 0; i < m_cAdapters; ++i)
    {
        // Can we get HW rendering?
        IFC(m_pD3D->CheckDeviceType(
            i,
            D3DDEVTYPE_HAL,
            D3DFMT_X8R8G8B8,
            fmt,
            TRUE
            )); 

        // Is the format okay?
        IFC(m_pD3D->CheckDeviceFormat(
            i,
            D3DDEVTYPE_HAL,
            D3DFMT_X8R8G8B8,
            D3DUSAGE_RENDERTARGET | D3DUSAGE_DYNAMIC, // We'll use dynamic when on XP
            D3DRTYPE_SURFACE,
            fmt
            ));

        // D3DImage only allows multisampling on 9Ex devices. If we can't 
        // multisample, overwrite the desired number of samples with 0.
        if (m_pD3DEx && m_uNumSamples > 1)
        {   
            assert(m_uNumSamples <= 16);

            if (FAILED(m_pD3D->CheckDeviceMultiSampleType(
                i,
                D3DDEVTYPE_HAL,
                fmt,
                TRUE,
                static_cast<D3DMULTISAMPLE_TYPE>(m_uNumSamples),
                NULL
                )))
            {
                m_uNumSamples = 0;
            }
        }
        else
        {
            m_uNumSamples = 0;
        }
    }

Cleanup:
    return hr;
}

サーフェイスの作成

サーフェイスを作成する前に、デバイスの機能がターゲット オペレーティング システム上で良好なパフォーマンスをサポートしていることを確認します。 詳細については、「Direct3D9 および WPF の相互運用性のパフォーマンスに関する考慮事項」を参照してください。

デバイスの機能を確認したら、サーフェイスを作成できます。 次のコード例は、レンダー ターゲットを作成する方法を示しています。

HRESULT
CRenderer::CreateSurface(UINT uWidth, UINT uHeight, bool fUseAlpha, UINT m_uNumSamples)
{
    HRESULT hr = S_OK;

    SAFE_RELEASE(m_pd3dRTS);

    IFC(m_pd3dDevice->CreateRenderTarget(
        uWidth,
        uHeight,
        fUseAlpha ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8,
        static_cast<D3DMULTISAMPLE_TYPE>(m_uNumSamples),
        0,
        m_pd3dDeviceEx ? FALSE : TRUE,  // Lockable RT required for good XP perf
        &m_pd3dRTS,
        NULL
        ));

    IFC(m_pd3dDevice->SetRenderTarget(0, m_pd3dRTS));

Cleanup:
    return hr;
}

WDDM

WDDM を使用するように構成されている Windows Vista 以降のオペレーティング システムでは、レンダー ターゲット テクスチャを作成し、レベル 0 の サーフェイスを SetBackBuffer メソッドに渡すことができます。 Windows XP では、ロック可能なレンダー ターゲット テクスチャを作成できず、パフォーマンスが低下するため、このアプローチは推奨されません。

デバイスの状態の処理

D3DImage クラスを使用して、"バック バッファー" と "フロント バッファー" という 2 つのディスプレイ バッファーを管理します。 バック バッファーは、Direct3D サーフェイスです。 Unlock メソッドを呼び出すと、バック バッファーに対する変更はフロント バッファーに転送され、そのハードウェア上に表示されます。 場合によっては、フロント バッファーが使用できなくなります。 この使用できなくなる状況は、画面ロック、全画面専用の Direct3D アプリケーション、ユーザー切り替え、またはその他のシステム アクティビティが原因で発生する可能性があります。 これが発生した場合、IsFrontBufferAvailableChanged イベントを処理することで WPF アプリケーションに通知されます。 フロント バッファーが使用できなくなった場合のアプリケーションの応答は、ソフトウェア レンダリングへのフォールバックが WPF で有効かどうかによって変わります。 SetBackBuffer メソッドには、WPF がソフトウェア レンダリングにフォールバックするかどうかを指定するパラメーターを受け取るオーバーロードがあります。

SetBackBuffer(D3DResourceType, IntPtr) オーバーロードを呼び出すか、enableSoftwareFallback パラメーターを false に設定して SetBackBuffer(D3DResourceType, IntPtr, Boolean) オーバーロードを呼び出すと、フロント バッファーが使用できなくなり、何も表示されなくなったときに、レンダリング システムではバック バッファーへの参照が解放されます。 フロント バッファーを再び使用できるようになると、レンダリング システムによって IsFrontBufferAvailableChanged イベントが発生し、WPF アプリケーションに通知されます。 IsFrontBufferAvailableChanged イベントのイベント ハンドラーを作成し、有効な Direct3D サーフェイスを使用してもう一度レンダリングを再開できます。 レンダリングを再開するには、SetBackBuffer を呼び出す必要があります。

enableSoftwareFallback パラメーターを true に設定して SetBackBuffer(D3DResourceType, IntPtr, Boolean) オーバーロードを呼び出すと、フロント バッファーが使用できなくなったときにレンダリング システムにバック バッファーへの参照が保持されます。そのため、バッファーを再び使用できるようになったときに SetBackBuffer を呼び出す必要はありません。

ソフトウェア レンダリングを有効にすると、ユーザーのデバイスが使用できなくなる場合がありますが、レンダリング システムには Direct3D サーフェイスへの参照が保持されます。 Direct3D9 デバイスが使用できないかどうかを確認するには、TestCooperativeLevel メソッドを呼び出します。 Direct3D9Ex デバイスを確認するには、CheckDeviceState メソッドを呼び出します。これは、TestCooperativeLevel メソッドが非推奨になり、常に成功が返されるためです。 ユーザー デバイスが使用できなくなった場合は、SetBackBuffer を呼び出して、バック バッファーへの WPF の参照を解放します。 デバイスをリセットする必要がある場合は、backBuffer パラメーターを null に設定して SetBackBuffer を呼び出し、次に backBuffer を有効な Direct3D サーフェイスに設定して SetBackBuffer をもう一度呼び出します。

マルチアダプターのサポートを実装する場合にのみ、Reset メソッドを呼び出して無効なデバイスから復旧します。 それ以外の場合は、すべての Direct3D9 インターフェイスを解放して、完全に再作成します。 アダプターのレイアウトが変更された場合、変更前に作成された Direct3D9 オブジェクトは更新されません。

サイズ変更の処理

D3DImage がネイティブ サイズ以外の解像度で表示されている場合、BilinearFant の代わりに使用されている場合を除き、現在の BitmapScalingMode に従って拡大縮小されます。

より高い忠実性が必要な場合は、D3DImage のコンテナーのサイズが変わったときに新しいサーフェイスを作成する必要があります。

サイズ変更を処理するには、3 つの方法があります。

  • レイアウト システムに参加し、サイズが変更されたときに新しいサーフェイスを作成します。 ビデオ メモリが使い果たされたり、フラグメント化されたりする可能性があるため、あまり多くのサーフェイスを作成しないでください。

  • サイズ変更イベントが一定期間発生しなくなるまで待ってから、新しいサーフェイスを作成します。

  • コンテナーのサイズを毎秒数回チェックする DispatcherTimer を作成します。

マルチモニターの最適化

レンダリング システムによって D3DImage が別のモニターに移動されると、パフォーマンスが大幅に低下する可能性があります。

WDDM では、モニターが同じビデオ カード上にあり、Direct3DCreate9Ex を使用している限り、パフォーマンスが低下することはありません。 モニターが別のビデオ カード上にある場合は、パフォーマンスが低下します。 Windows XP では、パフォーマンスは常に低下します。

D3DImage が別のモニターに移動された場合は、対応するアダプターに新しいサーフェイスを作成して、良好なパフォーマンスを復元することができます。

パフォーマンスの低下を回避するには、マルチモニター ケース専用のコードを書きます。 次の一覧は、マルチモニター コードを書く 1 つの方法を示しています。

  1. Visual.ProjectToScreen メソッドを使用して、画面空間内の D3DImage のポイントを見つけます。

  2. MonitorFromPoint GDI メソッドを使用して、ポイントが表示されているモニターを見つけます。

  3. IDirect3D9::GetAdapterMonitor メソッドを使用して、モニターがオンになっている Direct3D9 アダプターを見つけます。

  4. アダプターがバック バッファーを使用するアダプターと同じでない場合は、新しいモニター上に新しいバック バッファーを作成し、それを D3DImage バック バッファーに割り当てます。

注意

D3DImage が複数のモニターに使用される場合、WDDM と IDirect3D9Ex が同じアダプター上にある場合を除き、パフォーマンスは低下します。 この状況でパフォーマンスを向上させる方法はありません。

次のコード例は、現在のモニターを見つける方法を示しています。

void 
CRendererManager::SetAdapter(POINT screenSpacePoint)
{
    CleanupInvalidDevices();

    //
    // After CleanupInvalidDevices, we may not have any D3D objects. Rather than
    // recreate them here, ignore the adapter update and wait for render to recreate.
    //

    if (m_pD3D && m_rgRenderers)
    {
        HMONITOR hMon = MonitorFromPoint(screenSpacePoint, MONITOR_DEFAULTTONULL);

        for (UINT i = 0; i < m_cAdapters; ++i)
        {
            if (hMon == m_pD3D->GetAdapterMonitor(i))
            {
                m_pCurrentRenderer = m_rgRenderers[i];
                break;
            }
        }
    }
}

D3DImage コンテナーのサイズまたは位置が変わったときにモニターを更新するか、1 秒あたり数回更新される DispatcherTimer を使用してモニターを更新します。

WPF のソフトウェア レンダリング

次の状況では、ソフトウェアの UI スレッドに対して WPF が同期的にレンダリングされます。

このような状況のいずれかが発生すると、レンダリング システムから CopyBackBuffer メソッドが呼び出され、ハードウェア バッファーがソフトウェアにコピーされます。 既定の実装では、サーフェイスを使用して GetRenderTargetData メソッドが呼び出されます。 この呼び出しは、ロックおよびロック解除パターンの外部で発生するため、失敗する可能性があります。 この場合、CopyBackBuffer メソッドから null が返され、画像は表示されません。

CopyBackBuffer メソッドをオーバーライドして基本実装を呼び出し、null が返される場合は、プレースホルダー BitmapSource を返すことができます。

基本実装を呼び出すのではなく、独自のソフトウェア レンダリングを実装することもできます。

注意

WPF が完全にソフトウェアでレンダリングされている場合、WPF にはフロント バッファーがないため、D3DImage は表示されません。

関連項目