Compartir a través de


Interoperación de WPF y Direct3D9

Puedes incluir contenido de Direct3D9 en una aplicación de Windows Presentation Foundation (WPF). En este tema se describe cómo crear contenido de Direct3D9 para que interopera eficazmente con WPF.

Nota:

Al usar contenido de Direct3D9 en WPF, también debe pensar en el rendimiento. Para obtener más información sobre cómo optimizar el rendimiento, vea Consideraciones de rendimiento para la interoperabilidad de Direct3D9 y WPF.

Mostrar búferes

La D3DImage clase administra dos búferes de visualización, que se denominan búfer de reserva y búfer frontal. El búfer de fondo es la superficie Direct3D9. Los cambios en el búfer de fondo se copian al búfer de pantalla al llamar al método Unlock.

En la ilustración siguiente se muestra la relación entre el búfer de reserva y el búfer frontal.

Búferes de visualización D3DImage

Creación de dispositivos Direct3D9

Para representar contenido de Direct3D9, debe crear un dispositivo Direct3D9. Hay dos objetos Direct3D9 que puede usar para crear un dispositivo y IDirect3D9IDirect3D9Ex. Use estos objetos para crear IDirect3DDevice9 y IDirect3DDevice9Ex dispositivos, respectivamente.

Cree un dispositivo llamando a uno de los métodos siguientes.

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

En Windows Vista o en un sistema operativo posterior, use el Direct3DCreate9Ex método con una pantalla configurada para usar el modelo de controlador de pantalla de Windows (WDDM). Use el Direct3DCreate9 método en cualquier otra plataforma.

Disponibilidad del método Direct3DCreate9Ex

El d3d9.dll tiene el Direct3DCreate9Ex método solo en Windows Vista o versiones posteriores de sistemas operativos. Si vincula directamente la función en Windows XP, la aplicación no se puede cargar. Para determinar si el método Direct3DCreate9Ex es compatible, cargue el archivo DLL y busque la dirección de procedimiento. En el código siguiente se muestra cómo probar el Direct3DCreate9Ex método . Para obtener un ejemplo de código completo, vea Tutorial: Crear contenido de Direct3D9 para hospedar en WPF.

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;
}

Creación de HWND

La creación de un dispositivo requiere un HWND. En general, se crea un HWND ficticio para que Direct3D9 lo use. En el ejemplo de código siguiente se muestra cómo crear un HWND ficticio.

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;
}

Parámetros presentes

La creación de un dispositivo también requiere una D3DPRESENT_PARAMETERS estructura, pero solo algunos parámetros son importantes. Estos parámetros se eligen para minimizar la superficie de memoria.

Establezca los campos BackBufferHeight y BackBufferWidth en 1. Establecerlos en 0 hace que se establezcan en las dimensiones del HWND.

Siempre establezca las marcas D3DCREATE_MULTITHREADED y D3DCREATE_FPU_PRESERVE para evitar que se corrompa la memoria utilizada por Direct3D9 y para evitar que Direct3D9 modifique la configuración de la FPU.

En el código siguiente se muestra cómo inicializar la D3DPRESENT_PARAMETERS estructura.

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;
}

Creación del objetivo de renderizado del búfer de reserva

Para mostrar el contenido de Direct3D9 en D3DImage, primero debe crear una superficie de Direct3D9 y asignarla llamando al método SetBackBuffer.

Comprobación de la compatibilidad del adaptador

Antes de crear una superficie, compruebe que todos los adaptadores admiten las propiedades de superficie que necesite. Aunque solo se represente en un adaptador, la ventana WPF puede mostrarse en cualquier adaptador del sistema. Siempre debe escribir código de Direct3D9 que gestione las configuraciones de múltiples adaptadores y debe comprobar todos los adaptadores para verificar compatibilidad, puesto que WPF podría mover la superficie entre los adaptadores disponibles.

En el ejemplo de código siguiente se muestra cómo comprobar todos los adaptadores del sistema para obtener compatibilidad con 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;
}

Creación de la superficie

Antes de crear una superficie, compruebe que las funcionalidades del dispositivo admiten un buen rendimiento en el sistema operativo de destino. Para obtener más información, vea Consideraciones de rendimiento para la interoperabilidad de Direct3D9 y WPF.

Cuando haya comprobado las funcionalidades del dispositivo, puede crear la superficie. En el ejemplo de código siguiente se muestra cómo crear el destino de representación.

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

En Windows Vista y en los sistemas operativos posteriores que están configurados para usar el WDDM, se puede crear una textura de destino de renderización y pasar la superficie de nivel 0 al método SetBackBuffer. Este enfoque no se recomienda en Windows XP, ya que no se puede crear una textura de destino de representación bloqueable y se reducirá el rendimiento.

Control del estado del dispositivo

La D3DImage clase administra dos búferes de visualización, que se denominan búfer de reserva y búfer frontal. El back buffer es la superficie de Direct3D. Los cambios en el búfer trasero se copian hacia el búfer frontal al llamar al método Unlock, donde se muestran en el hardware. En ocasiones, el búfer frontal deja de estar disponible. Esta falta de disponibilidad puede deberse a bloqueos de pantalla, aplicaciones de Direct3D exclusivas de pantalla completa, cambio de usuario u otras actividades del sistema. Cuando esto ocurre, se notifica a la aplicación WPF mediante el control del evento IsFrontBufferAvailableChanged. La forma en que la aplicación responde al búfer delantero volviéndose no disponible depende de si WPF está configurado para volver a la representación por software. El SetBackBuffer método tiene una sobrecarga que toma un parámetro que especifica si WPF vuelve a la representación de software.

Cuando se llama a la SetBackBuffer(D3DResourceType, IntPtr) sobrecarga o se llama a la SetBackBuffer(D3DResourceType, IntPtr, Boolean) sobrecarga con el enableSoftwareFallback parámetro establecido en false, el sistema de representación libera su referencia al búfer trasero cuando el búfer frontal deja de estar disponible y nada se muestra. Cuando el búfer frontal está disponible de nuevo, el sistema de representación genera el IsFrontBufferAvailableChanged evento para notificar a su aplicación WPF. Puede crear un controlador de eventos para el evento IsFrontBufferAvailableChanged que reinicie la representación con una superficie de Direct3D válida. Para reiniciar la representación, debe llamar a SetBackBuffer.

Cuando se llama a la sobrecarga SetBackBuffer(D3DResourceType, IntPtr, Boolean) con el parámetro enableSoftwareFallback establecido en true, el sistema de representación conserva su referencia al búfer de reserva cuando el búfer frontal deja de estar disponible, por lo que no es necesario llamar a SetBackBuffer cuando el búfer frontal está disponible de nuevo.

Cuando la representación de software está habilitada, puede haber situaciones en las que el dispositivo del usuario deje de estar disponible, pero el sistema de representación conserva una referencia a la superficie de Direct3D. Para comprobar si un dispositivo Direct3D9 no está disponible, llame al TestCooperativeLevel método . Para verificar un dispositivo Direct3D9Ex, debe llamar al método CheckDeviceState, ya que el método TestCooperativeLevel está en desuso y siempre devuelve éxito. Si el dispositivo de usuario no está disponible, llame al SetBackBuffer para liberar la referencia de WPF al búfer secundario. Si necesita restablecer el dispositivo, llame a SetBackBuffer con el parámetro backBuffer establecido en null, y vuelva a llamar a SetBackBuffer con backBuffer establecido en una superficie de Direct3D válida.

Llame al método Reset para recuperarse de un dispositivo no válido solo si implementa soporte multi-adaptador. De lo contrario, libere todas las interfaces de Direct3D9 y vuelva a crearlas completamente. Si el diseño del adaptador ha cambiado, los objetos Direct3D9 creados antes de que el cambio no se actualizan.

Manejo del cambio de tamaño

Si D3DImage se muestra a una resolución distinta de su tamaño nativo, se escala según el BitmapScalingMode actual, salvo que se reemplace Bilinear por Fant.

Si necesita mayor fidelidad, debe crear una nueva superficie cuando cambie el tamaño del D3DImage contenedor.

Hay tres enfoques posibles para manejar la redimensión.

  • Participe en el sistema de diseño y cree una nueva superficie cuando cambie el tamaño. No cree demasiadas superficies, ya que puede agotar o fragmentar la memoria de vídeo.

  • Espere hasta que no se haya producido un evento de cambio de tamaño durante un período fijo de tiempo para crear la nueva superficie.

  • Cree un DispatcherTimer elemento que compruebe las dimensiones del contenedor varias veces por segundo.

Optimización de varios monitores

El rendimiento se puede reducir significativamente cuando el sistema de representación mueve un D3DImage objeto a otro monitor.

En WDDM, siempre y cuando los monitores estén en la misma tarjeta de vídeo y use Direct3DCreate9Ex, no hay ninguna reducción en el rendimiento. Si los monitores están en tarjetas de vídeo independientes, se reduce el rendimiento. En Windows XP, el rendimiento siempre se reduce.

D3DImage Cuando se mueve a otro monitor, puede crear una nueva superficie en el adaptador correspondiente para restaurar un buen rendimiento.

Para evitar la penalización del rendimiento, escriba código específicamente para el caso de varios monitores. En la lista siguiente se muestra una manera de escribir código de varios monitores.

  1. Busque un punto del D3DImage espacio en pantalla con el Visual.ProjectToScreen método .

  2. Use el MonitorFromPoint método GDI para buscar el monitor que muestra el punto.

  3. Use IDirect3D9::GetAdapterMonitor para determinar en qué adaptador de Direct3D9 está el monitor.

  4. Si el adaptador no es el mismo que el adaptador con el búfer de reserva, cree un nuevo búfer de reserva en el nuevo monitor y asígnelo al D3DImage búfer de reserva.

Nota:

Si el D3DImage se extiende entre monitores, el rendimiento será lento, excepto en el caso de WDDM y IDirect3D9Ex en el mismo adaptador. No hay ninguna manera de mejorar el rendimiento en esta situación.

En el ejemplo de código siguiente se muestra cómo buscar el monitor actual.

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;
            }
        }
    }
}

Actualice el monitor cuando cambie el tamaño o la D3DImage posición del contenedor o actualice el monitor mediante un DispatcherTimer que se actualiza varias veces por segundo.

Representación de software de WPF

WPF se representa sincrónicamente en el subproceso de la interfaz de usuario en software en las situaciones siguientes.

Cuando se produce una de estas situaciones, el sistema de representación llama al método CopyBackBuffer para copiar el búfer de hardware en software. La implementación predeterminada llama al método GetRenderTargetData con tu superficie. Dado que esta llamada se produce fuera del patrón Lock/Unlock, puede producirse un error. En este caso, el CopyBackBuffer método devuelve null y no se muestra ninguna imagen.

Puede invalidar el CopyBackBuffer método , llamar a la implementación base y, si devuelve null, puede devolver un marcador de posición BitmapSource.

También puede implementar su propia renderización de software en lugar de llamar a la implementación base.

Nota:

Si WPF se representa completamente en software, D3DImage no se muestra porque WPF no tiene un búfer frontal.

Consulte también