Interoperabilidad entre WPF y Direct3D9

Puede incluir contenido Direct3D9 en una aplicación de Windows Presentation Foundation (WPF). En este tema se describe cómo crear contenido Direct3D9 para que interopere de forma eficaz con WPF.

Nota:

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

Búferes de pantalla

La clase D3DImage administra dos búferes de pantalla, que se denominan búfer de reserva y búfer frontal. El búfer de reserva es la superficie de Direct3D9. Los cambios en el búfer de reserva se copian al búfer frontal 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 pantalla de D3DImage

Creación de dispositivos Direct3D9

Para representar el contenido Direct3D9, debe crear un dispositivo Direct3D9. Hay dos objetos direct3D9 que puede usar para crear un dispositivo, IDirect3D9 y IDirect3D9Ex. Use estos objetos para crear dispositivos IDirect3DDevice9 y IDirect3DDevice9Ex, 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 método Direct3DCreate9Ex con una pantalla configurada para usar Windows Display Driver Model (WDDM). Use el método Direct3DCreate9 en cualquier otra plataforma.

Disponibilidad del método Direct3DCreate9Ex

El d3d9.dll tiene el método Direct3DCreate9Ex solo en Windows Vista o en un sistema operativo posterior. Si vincula directamente la función en Windows XP, la aplicación no se puede cargar. Para determinar si se admite el método Direct3DCreate9Ex, cargue el archivo DLL y busque la dirección del procesador. En el código siguiente se muestra cómo puede utilizar el método Direct3DCreate9Ex. Para obtener un ejemplo de código completo, consulte Tutorial: Crear contenido Direct3D9 para hospedarlo 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 estructura D3DPRESENT_PARAMETERS, 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.

Establezca siempre las marcas D3DCREATE_MULTITHREADED y D3DCREATE_FPU_PRESERVE para evitar daños en la memoria que usa Direct3D9 y para evitar que Direct3D9 cambie la configuración de FPU.

En el código siguiente se muestra cómo inicializar la estructura 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;
}

Creación del destino de representación del búfer de reserva

Para mostrar el contenido Direct3D9 en D3DImage, cree una superficie de Direct3D9 y asígnela 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 necesita. Incluso si se representa en un solo adaptador, la ventana de WPF puede mostrarse en cualquier adaptador del sistema. Siempre debe escribir código de Direct3D9 que controle las configuraciones de varios adaptadores y debe comprobar todos los adaptadores para que sean compatibles, ya 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 ver si son compatibles 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, consulte Consideraciones de rendimiento para la interoperabilidad entre Direct3D9 y WPF.

Cuando haya comprobado las funcionalidades del dispositivo, puede crear la superficie. En el código de ejemplo siguiente se muestra cómo crear el destino de la presentació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 sistemas operativos posteriores, que están configurados para usar WDDM, puede crear una textura de destino de representació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 clase D3DImage administra dos búferes de pantalla, que se denominan búfer de reserva y búfer frontal. El búfer de reserva es la superficie de Direct3D. Los cambios en el búfer de reserva se copian al búfer frontal cuando se llama al método Unlock, donde se muestra en el hardware. En ocasiones, el búfer frontal deja de estar disponible. Esta falta de disponibilidad puede deberse a bloqueos de pantalla, aplicaciones Direct3D exclusivas de pantalla completa, cambio de usuario u otras actividades del sistema. Cuando esto ocurre, se notifica a la aplicación WPF al controlar el evento IsFrontBufferAvailableChanged. La forma en que la aplicación responde al búfer frontal que no está disponible depende de si WPF está habilitado para revertir a la representación de software. El método SetBackBuffer tiene una sobrecarga que toma un parámetro que especifica si WPF revierte a la representación de software.

Cuando se llama a la sobrecarga SetBackBuffer(D3DResourceType, IntPtr) o se llama a la sobrecarga SetBackBuffer(D3DResourceType, IntPtr, Boolean) con el parámetro enableSoftwareFallback establecido en false, el sistema de representación libera su referencia al búfer de reserva cuando el búfer frontal deja de estar disponible y no se muestra nada. Cuando el búfer frontal está disponible de nuevo, el sistema de representación genera el evento IsFrontBufferAvailableChanged para notificar a la aplicación WPF. Puede crear un controlador de eventos para que el evento IsFrontBufferAvailableChanged reinicie la representación de nuevo 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 la 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 método TestCooperativeLevel. Para comprobar un dispositivo Direct3D9Ex, llame al método CheckDeviceState, ya que el método TestCooperativeLevel está en desuso y siempre devuelve un valor correcto. Si el dispositivo de usuario no está disponible, llame a SetBackBuffer para liberar la referencia de WPF al búfer de reserva. Si necesita restablecer el dispositivo, llame a SetBackBuffer con el parámetro backBuffer establecido en null y, a continuación, llame a SetBackBuffer de nuevo con backBuffer establecido en una superficie Direct3D válida.

Llame al método Reset para recuperar de un dispositivo no válido solo si implementa compatibilidad con varios adaptadores. 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 del cambio no se actualizan.

Control del cambio de tamaño

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

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

Hay tres enfoques posibles para controlar el cambio de tamaño.

  • 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 objeto DispatcherTimer que compruebe las dimensiones del contenedor varias veces por segundo.

Optimización de varios monitores

Cuando el sistema de representación mueve un objeto D3DImage a otro monitor, se puede reducir el rendimiento considerablemente.

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.

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

Para evitar una disminució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 para varios monitores.

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

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

  3. Use el método IDirect3D9::GetAdapterMonitor para buscar en qué adaptador de Direct3D9 está activado 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 búfer de reserva de D3DImage.

Nota:

Si D3DImage sobrepasa los monitores, el rendimiento se verá reducido, 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 identificar 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 posición del contenedor D3DImage, o bien actualice el monitor mediante un objeto DispatcherTimer que se actualiza varias veces por segundo.

Representación de software de WPF

WPF se representa sincrónicamente en el subproceso de 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 el software. La implementación predeterminada llama al método GetRenderTargetData con la superficie. Dado que esta llamada se produce fuera del patrón Bloquear/Desbloquear, puede producirse un error. En este caso, el método CopyBackBuffer devuelve null y no se muestra ninguna imagen.

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

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

Nota:

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

Vea también