Share via


WPF 和 Direct3D9 互通

您可以在 Windows Presentation Foundation (WPF) 應用程式中加入 Direct3D9 內容。 本主題描述如何建立 Direct3D9 內容,使其有效率地與 WPF 交互操作。

注意

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

顯示緩衝區

類別 D3DImage 會管理兩個顯示緩衝區,這些緩衝區稱為 後端緩衝區 前端緩衝區 。 後端緩衝區是 Direct3D9 表面。 呼叫 方法時 Unlock ,會將後端緩衝區的變更向前複製到前端緩衝區。

下圖顯示後端緩衝區與前端緩衝區之間的關聯性。

D3DImage display buffers

Direct3D9 裝置建立

若要轉譯 Direct3D9 內容,您必須建立 Direct3D9 裝置。 您可以使用兩個 Direct3D9 物件來建立裝置和 IDirect3D9IDirect3D9Ex 。 使用這些物件分別建立 IDirect3DDevice9IDirect3DDevice9Ex 裝置。

呼叫下列其中一種方法來建立裝置。

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

在 Windows Vista 或更新版本的作業系統上 Direct3DCreate9Ex ,使用 方法搭配設定為使用 Windows Display Driver Model (WDDM) 的顯示器。 Direct3DCreate9在任何其他平臺上使用 方法。

Direct3DCreate9Ex 方法的可用性

d3d9.dll 只有在 Windows Vista 或更新版本的作業系統上才有 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 方法加以指派。

驗證配接器支援

建立介面之前,請確認所有配接器都支援您需要的介面屬性。 即使您只轉譯為一個介面卡,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;
}

建立 Surface

建立介面之前,請確認裝置功能支援目標作業系統上的良好效能。 如需詳細資訊,請參閱 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 會管理兩個顯示緩衝區,這些緩衝區稱為 後端緩衝區 前端緩衝區 。 後端緩衝區是 Direct3D 表面。 呼叫 Unlock 方法時,後端緩衝區的變更會向前複製到前端緩衝區,其中會顯示在硬體上。 有時候,前端緩衝區變得無法使用。 這種可用性不足的原因可能是螢幕鎖定、全螢幕專屬 Direct3D 應用程式、使用者切換或其他系統活動所造成。 發生這種情況時,您的 WPF 應用程式會藉由處理 IsFrontBufferAvailableChanged 事件來通知。 您的應用程式如何回應無法使用前端緩衝區,取決於 WPF 是否能夠回復到軟體轉譯。 SetBackBuffer方法具有多載,其採用參數,指定 WPF 是否切換回軟體轉譯。

當您呼叫 SetBackBuffer(D3DResourceType, IntPtr) 多載或呼叫 SetBackBuffer(D3DResourceType, IntPtr, Boolean) 參數設定為 false 的多載 enableSoftwareFallback 時,轉譯系統會在前端緩衝區變成無法使用且未顯示任何內容時,釋放其後端緩衝區的參考。 當前端緩衝區再次可用時,轉譯系統會 IsFrontBufferAvailableChanged 引發 事件來通知 WPF 應用程式。 您可以建立事件的事件處理常式 IsFrontBufferAvailableChanged ,以使用有效的 Direct3D 介面重新開機轉譯。 若要重新開機轉譯,您必須呼叫 SetBackBuffer

當您呼叫 SetBackBuffer(D3DResourceType, IntPtr, Boolean) 多載並將 enableSoftwareFallback 參數設定為 true 時,轉譯系統會在前端緩衝區無法使用時保留其後端緩衝區的參考,因此不需要在前端緩衝區再次可用時呼叫 SetBackBuffer

啟用軟體轉譯時,可能會有使用者裝置無法使用的情況,但轉譯系統會保留 Direct3D 介面的參考。 若要檢查 Direct3D9 裝置是否無法使用,請呼叫 TestCooperativeLevel 方法。 若要檢查 Direct3D9Ex 裝置呼叫 CheckDeviceState 方法,因為 TestCooperativeLevel 方法已被取代,且一律會傳回成功。 如果使用者裝置無法使用,請呼叫 SetBackBuffer 以釋放 WPF 對背景緩衝區的參考。 如果您需要重設裝置,請使用 設定為 null 的參數呼叫 SetBackBufferbackBuffer ,然後再次 backBuffer 呼叫 SetBackBuffer ,並將 設定為有效的 Direct3D 介面。

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 方法。 由於此呼叫發生在鎖定/解除鎖定模式之外,因此可能會失敗。 在此情況下, CopyBackBuffer 方法會傳 null 回,而且不會顯示任何影像。

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

您也可以實作自己的軟體轉譯,而不是呼叫基底實作。

注意

如果 WPF 完全在軟體中轉譯,則不會顯示 , D3DImage 因為 WPF 沒有前端緩衝區。

另請參閱