WPF でホストできる Direct3D9 コンテンツの作成

更新 : 2008 年 7 月

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

Cc656710.alert_note(ja-jp,VS.90).gifメモ :

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 バック バッファに割り当てます。

Cc656710.alert_note(ja-jp,VS.90).gifメモ :

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 を返すことができます。

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

Cc656710.alert_note(ja-jp,VS.90).gifメモ :

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

参照

処理手順

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

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

概念

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

参照

D3DImage

履歴の変更

日付

履歴

理由

2008 年 7 月

新しいトピックを追加

SP1 機能変更