Crear contenido Direct3D9 que pueda hospedarse en WPF

Actualización: Julio de 2008

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

Nota

Cuando se utiliza contenido de Direct3D9 en WPF, también es preciso pensar en el rendimiento. Para obtener más información acerca de cómo optimizar el rendimiento, vea Consideraciones de rendimiento para la interoperabilidad entre Direct3D9 y WPF.

Búferes de presentación

La clase D3DImage administra dos búferes de presentación, denominados 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 en el búfer frontal cuando se llama al método Unlock.

La ilustración siguiente 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 de Direct3D9

Para representar contenido de Direct3D9, debe crear un dispositivo de Direct3D9. Hay dos objetos Direct3D9 que puede utilizar para crear un dispositivo, IDirect3D9 e IDirect3D9Ex. Utilice estos objetos para crear los dispositivos IDirect3DDevice9 e IDirect3DDevice9Ex, respectivamente.

Para crear dispositivo, llame a uno de los métodos siguientes.

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

Utilice el método Direct3DCreate9Ex en Windows Vista con una presentación configurada para utilizar el modelo de controladores de pantalla de Windows (WDDM). Utilice el método Direct3DCreate9 en todas las demás plataformas.

Disponibilidad del método Direct3DCreate9Ex

Solo el archivo d3d9.dll de Windows Vista tiene el método Direct3DCreate9Ex. Si vincula directamente la función en Windows XP, la aplicación no se cargará. Para determinar si el método Direct3DCreate9Ex se admite, cargue la DLL y busque la dirección de procedimiento. En el código siguiente se muestra cómo comprobar el método Direct3DCreate9Ex. Para obtener un ejemplo de código completo, vea 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

Para crear un dispositivo se requiere un HWND. En general, se crea un HWND ficticio para que Direct3D9 lo utilice. 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

Para crear un dispositivo, también se requiere una estructura D3DPRESENT_PARAMETERS, pero únicamente son importantes algunos parámetros. 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 de HWND.

Siempre establezca las marcas D3DCREATE_MULTITHREADED y D3DCREATE_FPU_PRESERVE para evitar que se dañe la memoria utilizada por Direct3D9 y que Direct3D9 cambie los valores 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;
}

Crear el destino de representación del búfer de reserva

Para mostrar el contenido de Direct3D9 en D3DImage, cree una superficie de Direct3D9 y asígnela llamando al método SetBackBuffer.

Comprobar la compatibilidad del adaptador

Antes de crear una superficie, compruebe que todos los adaptadores admiten las propiedades de superficie que se necesitan. Aunque represente en un único adaptador, la ventana de WPF se puede mostrarse en cualquier adaptador del sistema. Siempre se debe escribir código de Direct3D9 que administre configuraciones de varios adaptadores. Además, deberá comprobar la compatibilidad de todos los adaptadores, porque WPF podría mover la superficie entre los adaptadores disponibles.

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

Crear la superficie

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

Cuando haya comprobado las capacidades 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 configurado para utilizar el WDDM, puede crear una textura de destino de representación y la superficie de nivel 0 al método SetBackBuffer. Este enfoque no se recomienda en Windows XP, porque no se puede crear una textura de destino de representación bloqueable y se reducirá el rendimiento.

Administrar el estado del dispositivo

Cuando la propiedad IsFrontBufferAvailable efectúa la transición de true a false, WPF no muestra D3DImage ni copia el búfer de reserva en el búfer frontal. Esto suele significar que se ha perdido el dispositivo.

Cuando se pierde el dispositivo, el código debe dejar de representarse y esperar a que la propiedad IsFrontBufferAvailable efectúe la transición a true. Controle el evento IsFrontBufferAvailableChanged para que se le notifique esta transición.

Cuando la propiedad IsFrontBufferAvailable efectúa la transición de false a true, debe comprobar el dispositivo para determinar si es válido.

Para los dispositivos de Direct3D9, llame al método TestCooperativeLevel. Para los dispositivos de Direct3D9Ex, llame al método CheckDeviceState, porque el método TestCooperativeLevel está obsoleto y siempre devuelve un estado correcto.

Si el dispositivo es válido, llame de nuevo al método SetBackBuffer con la superficie original.

Si el dispositivo no es válido, debe restablecerlo y crear los recursos de nuevo. Al llamar al método SetBackBuffer con una superficie desde un dispositivo no válido, se inicia una excepción.

Llame al método Reset para recuperarse de un dispositivo no válido únicamente si implementa la compatibilidad con varios adaptadores. De lo contrario, libere todas las interfaces de Direct3D9 y créelas de nuevo completamente. Si el diseño del adaptador ha cambiado, los objetos de Direct3D9 creados antes del cambio no se actualizan.

Administrar los cambios de tamaño

Si D3DImage se muestra en una resolución distinta de su tamaño nativo, se escala según el valor de BitmapScalingMode actual, con la salvedad de que se reemplaza Fant con Bilinear.

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

Hay tres posibles enfoques para administrar los cambios de tamaño.

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

  • Para crear la nueva superficie, espere hasta que transcurra un período de tiempo fijo durante el que no se haya producido ningún evento de cambio de tamaño.

  • Cree un objeto DispatcherTimer que comprueba 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, el rendimiento se puede reducir de manera significativa.

En el WDDM, siempre que los monitores se encuentren en la misma tarjeta de vídeo y se utilice Direct3DCreate9Ex, el rendimiento no se reduce en absoluto. Si los monitores se encuentran en tarjetas de vídeo independientes, se reduce el rendimiento. En Windows XP, el rendimiento se reduce siempre.

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

Para evitar la reducción del rendimiento, escriba el código específicamente para 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 de la pantalla con el método Visual.ProjectToScreen.

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

  3. Utilice el método IDirect3D9::GetAdapterMonitor para buscar en qué adaptador de Direct3D9 se encuentra el monitor.

  4. Si el adaptador no es el mismo que tiene 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 abarca dos o más monitores, el rendimiento será lento, excepto si el WDDM e IDirect3D9Ex están 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 la posición o el tamaño del contenedor de D3DImage, o bien actualice el monitor mediante un objeto DispatcherTimer que se actualiza varias veces por segundo.

Representación de software de WPF

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

Cuando sucede una de estas situaciones, el sistema de representación llama al método CopyBackBuffer para que copie 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 modelo 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 objeto BitmapSource de marcador de posición.

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

Nota

Si WPF efectúa la representación completamente en software, D3DImage no se muestra, porque WPF no tiene un búfer frontal.

Vea también

Tareas

Tutorial: Crear contenido Direct3D9 para hospedarlo en WPF

Tutorial: Hospedar contenido Direct3D9 en WPF

Conceptos

Consideraciones de rendimiento para la interoperabilidad entre Direct3D9 y WPF

Referencia

D3DImage

Historial de cambios

Fecha

Historial

Motivo

Julio de 2008

Se ha agregado un tema nuevo.

Cambio de características de SP1.