创建可在 WPF 中承载的 Direct3D9 内容

更新: 2008 年 7 月

可以在 Windows Presentation Foundation (WPF) 应用程序中包含 Direct3D9 内容。本主题介绍如何创建 Direct3D9 内容以使其有效地与 WPF 交互操作。

说明:

当在 WPF 中使用 Direct3D9 内容时,还需要考虑性能问题。有关如何针对性能进行优化的更多信息,请参见 Direct3D9 和 WPF 互操作性的性能注意事项

显示缓冲区

D3DImage 类管理两种显示缓冲区,这两种缓冲区分别称为“后台缓冲区”和“前台缓冲区”。后台缓冲区是 Direct3D9 图面。当您调用 Unlock 方法时,会将对后台缓冲区所做的更改向前复制到前台缓冲区。

下面的插图显示后台缓冲区与前台缓冲区之间的关系。

D3DImage 显示缓冲区

Direct3D9 设备创建

若要呈现 Direct3D9 内容,必须创建 Direct3D9 设备。可用于创建设备的 Direct3D9 对象有两个:IDirect3D9IDirect3D9Ex。使用这些对象可以分别创建 IDirect3DDevice9IDirect3DDevice9Ex 设备。

通过调用下列方法之一来创建设备。

  • 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,并查找进程地址。下面的代码演示如何测试 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;
}

创建图面

在创建图面之前,应验证设备功能在目标操作系统上是否支持良好的性能。有关更多信息,请参见 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 进行伸缩,只是 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 没有前台缓冲区。

请参见

任务

演练:创建在 WPF 中承载的 Direct3D9 内容

演练:在 WPF 中承载 Direct3D9 内容

概念

Direct3D9 和 WPF 互操作性的性能注意事项

参考

D3DImage

修订记录

日期

修订记录

原因

2008 年 7 月

新增主题。

SP1 功能更改。