WPF と Direct3D9 の相互運用性

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

メモメモ

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

表示バッファー

D3DImage クラスは、バック バッファーおよびフロント バッファーと呼ばれる 2 種類の表示バッファーを管理します。 バック バッファーはユーザーの Direct3D9 サーフェイスです。 バック バッファーへの変更点は、Unlock メソッドを呼び出すときに、フロント バッファーにコピーされます。

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

D3DImage 表示バッファー

Direct3D9 デバイスの作成

Direct3D9 コンテンツを表示するには、Direct3D9 デバイスを作成する必要があります。 デバイスを作成するために使用できる Direct3D9 オブジェクトには、IDirect3D9IDirect3D9Ex の 2 種類があります。 これらのオブジェクトを使用して、IDirect3DDevice9 デバイスと IDirect3DDevice9Ex デバイスを作成します。

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

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

Windows Display Driver Model (WDDM) を使用して表示するように構成されている Windows Vista では、Direct3DCreate9Ex メソッドを使用します。 それ以外のすべてのプラットフォームでは、Direct3DCreate9 メソッドを使用します。

Direct3DCreate9Ex メソッドの可用性

Direct3DCreate9Ex メソッドは、Windows Vista の d3d9.dll だけに含まれます。 この関数を Windows XP で直接リンクすると、アプリケーションの読み込みが失敗します。 Direct3DCreate9Ex メソッドがサポートされているかどうかを確認するには、この DLL を読み込み、プロシージャ アドレスを探します。 次のコードは、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;
}

表示パラメーター

デバイスを作成するには 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;
}

バック バッファーのレンダリング先の作成

D3DImage 内の Direct3D9 コンテンツを表示するには、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 では、ロック可能なレンダリング先のテクスチャを作成できず、パフォーマンスが低下するので、この方法はお勧めしません。

デバイス状態の処理

IsFrontBufferAvailable プロパティが true から false に遷移すると、WPF は D3DImage を表示せず、バック バッファーをフロント バッファーにコピーしません。 これは通常、デバイスが消失したことを意味します。

デバイスが消失した場合は、レンダリングを中止し、IsFrontBufferAvailable プロパティが true に遷移するまで待機する必要があります。 IsFrontBufferAvailableChanged イベントを処理して、この遷移が通知されるようにします。

IsFrontBufferAvailable プロパティが false から true に遷移したときは、デバイスが有効かどうかを確認する必要があります。

Direct3D9 デバイスの場合は、TestCooperativeLevel メソッドを呼び出します。 Direct3D9Ex デバイスの場合、TestCooperativeLevel メソッドは推奨されておらず、常に正常に終了するので、CheckDeviceState メソッドを呼び出します。

デバイスが有効な場合は、元のサーフェイスを使用して SetBackBuffer メソッドを再度呼び出します。

デバイスが無効な場合は、デバイスをリセットし、リソースを再作成します。 無効なデバイスからサーフェイスを使用して SetBackBuffer メソッドを呼び出すと、例外が発生します。

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

サイズ変更の処理

D3DImage がネイティブ サイズ以外の解像度で表示される場合は、Bilinear が Fant に代替されている場合を除き、現在の BitmapScalingMode に従ってスケーリングされます。

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

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

  • レイアウト システムを処理し、サイズが変更されたときに新しいサーフェイスを作成します。 ビデオ メモリの不足や断片化が発生する可能性があるので、作成するサーフェイスが多くなりすぎないようにしてください。

  • 一定期間待機してサイズ変更イベントが発生しないことを確認した後、新しいサーフェイスを作成します。

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

マルチモニターの最適化

レンダリング システムが D3DImage を別のモニターに移動したときに、パフォーマンスが大幅に低下する場合があります。

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

D3DImage が別のモニターに移動するときは、対応するアダプター上に新しいサーフェイスを作成することで、パフォーマンスを維持できます。

パフォーマンスの低下を回避するには、マルチモニター用に特別なコードを記述します。 次のリストは、マルチモニター コードを記述するための 1 つの方法を示しています。

  1. D3DImage の画面空間でのポイントを、Visual.ProjectToScreen メソッドを使用して検出します。

  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 ソフトウェアのレンダリング

WPF は、次のような場合、ソフトウェアの UI スレッドで同期的にレンダリングします。

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

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

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

メモメモ

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

参照

処理手順

チュートリアル : WPF でホストするための Direct3D9 コンテンツの作成

チュートリアル : WPF での Direct3D9 コンテンツのホスト

参照

D3DImage

概念

Direct3D9 および WPF の相互運用性のパフォーマンスに関する考慮事項