共用方式為


建立 WPF 中可以裝載的 Direct3D9 內容

更新: 2008 年 7 月

您可以將 Direct3D9 內容包含在 Windows Presentation Foundation (WPF) 應用程式中。這個主題會說明如何建立 Direct3D9 內容,以能有效率地和 WPF 交互操作。

注意事項:

在 WPF 中使用 Direct3D9 內容時,您也需要考慮效能。如需如何最佳化效能的詳細資訊,請參閱 Direct3D9 和 WPF 互通性的效能考量

顯示緩衝區

D3DImage 類別管理兩個顯示緩衝區,分別稱為「背景緩衝區」(Back Buffer) 和「前景緩衝區」(Front Buffer)。背景緩衝區是您的 Direct3D9 介面。當您呼叫 Unlock 方法時,對背景緩衝區的變更會複製到前景緩衝區。

下圖顯示了背景緩衝區和前景緩衝區之間的關聯性 (Relationship)。

D3DImage 顯示緩衝區

建立 Direct3D9 裝置

若要呈現 Direct3D9 內容,您必須建立 Direct3D9 裝置。有兩種 Direct3D9 物件可以用於建立裝置,分別是 IDirect3D9IDirect3D9Ex。請使用這些物件分別建立 IDirect3DDevice9IDirect3DDevice9Ex 裝置。

透過呼叫下列方法之一建立裝置。

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

對於顯示設定為使用 Windows 顯示驅動程式模型 (WDDM) 的 Windows Vista,請使用 Direct3DCreate9Ex 方法。對於其他的任何平台,請使用 Direct3DCreate9 方法。

Direct3DCreate9Ex 方法的可用性

只有 Windows Vista 上的 d3d9.dll 具有 Direct3DCreate9Ex 方法。如果您在 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 的裝置。一般而言,您可以建立空的 HWND 供 Direct3D9 使用。下列程式碼範例顯示如何建立空的 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 結構,但只有少數參數比較重要。選擇這些參數是為了最小化記憶體耗用量。

BackBufferHeightBackBufferWidth 欄位設定為 1。將這兩項設定為 0 會造成它們設為 HWND 的維度。

一定要設定 D3DCREATE_MULTITHREADEDD3DCREATE_FPU_PRESERVE 旗標,以避免中斷 Direct3D9 使用的記憶體,以及避免 Direct3D9 變更 FPU 設定。

下列程式碼顯示如何初始化 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 方法,以對 Direct3D9 介面進行指派。

驗證配接器支援

在建立介面前,驗證是否所有的配接器都支援您需要的介面屬性。就算您僅呈現至一種配接器,在系統中的任何一個配接器上都可能顯示此 WPF 視窗。您應該永遠撰寫處理多配接器設定的 Direct3D9 程式碼,並應該檢查所有的配接器以取得支援,因為 WPF 可能在可用的配接器間移動介面。

下列程式碼範例示範如何檢查系統上所有配接器的 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 裝置請呼叫 CheckDeviceState 方法,因為 TestCooperativeLevel 方法已被取代,且永遠傳回成功。

如果裝置有效,請以原本的介面再次呼叫 SetBackBuffer 方法。

如果裝置無效,您必須重設裝置,並重新建立資源。透過無效的裝置中的介面呼叫 SetBackBuffer 方法,會引發例外狀況。

只有當您實作多配接器支援時,才呼叫 Reset 方法,由無效的裝置復原。不然就釋放所有的 Direct3D9 介面,全部重新建立。如果配接器的版面配置改變,在變更前建立的 Direct3D9 物件不會更新。

處理大小調整

如果 D3DImage 顯示的解析度不是原生大小,則會根據目前的 BitmapScalingMode 縮放,除了 Bilinear 以 Fant 取代的情況以外。

如果您需要更高的精確度,則必須在 D3DImage 的容器大小變更時建立新的介面。

能夠處理大小調整的方法有三種。

  • 加入配置系統,並在大小變更時建立新的介面。不要建立太多介面,因為您可能會讓視訊記憶體耗盡或分割化。

  • 在等待一定時間後,如果重新調整大小的事件沒有發生,再建立新的介面。

  • 建立 DispatcherTimer,每秒鐘檢查幾次容器維度。

多監視器最佳化

當呈現系統將 D3DImage 移動至另一個監視器,可能造成效能大幅降低。

在 WDDM 上,只要監視器在同一個視訊卡上,且您使用 Direct3DCreate9Ex,效能就不會降低。如果監視器在不同的視訊卡上,效能會降低。在 Windows XP 上,效能一定會降低。

D3DImage 移動至另一個監視器時,您可以在對應的配接器上建立新的介面,以還原為良好的效能。

若要避免效能減損,請特別為多監視器的情形撰寫程式碼。下列清單顯示撰寫多監視器程式碼的一種方法。

  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 容器的大小或位置改變時更新監視器,或使用每秒鐘更新幾次的 DispatcherTimer 更新監視器。

WPF 軟體呈現

在下列情況中,WPF 會在軟體中的 UI 執行緒上同步呈現。

當發生這些情況之一時,呈現系統會呼叫 CopyBackBuffer 方法以將硬體緩衝區複製到軟體。預設實作會以您的介面呼叫 GetRenderTargetData 方法。由於這個呼叫發生於 Lock/Unlock 模式外,因此可能會發生失敗。在這個情況中,CopyBackBuffer 方法會傳回 null 且不會顯示任何影像。

您可以覆寫 CopyBackBuffer 方法,呼叫基底實作,如果傳回 null,您可以傳回預留位置 BitmapSource

您也可以實作自己的軟體呈現,而不要呼叫基底實作。

注意事項:

如果 WPF 在軟體中完整呈現,因為 WPF 沒有前景緩衝區,就不會顯示 D3DImage

請參閱

工作

逐步解說:建立裝載於 WPF 中的 Direct3D9 內容

逐步解說:在 WPF 中裝載 Direct3D9 內容

概念

Direct3D9 和 WPF 互通性的效能考量

參考

D3DImage

變更記錄

日期

記錄

原因

2008 年 7 月

新增主題。

SP1 功能變更。