Interoperatività di WPF e Direct3D9

È possibile includere contenuto Direct3D9 in un'applicazione Windows Presentation Foundation (WPF). In questo argomento viene descritto come creare contenuto Direct3D9 in modo che interagisca in modo efficiente con WPF.

Nota

Quando si usa il contenuto Direct3D9 in WPF, è necessario considerare anche le prestazioni. Per altre informazioni su come ottimizzare le prestazioni, vedere Considerazioni sulle prestazioni per Direct3D9 e Interoperabilità WPF.

Visualizzare buffer

La D3DImage classe gestisce due buffer di visualizzazione, denominati buffer nascosto e buffer anteriore. Il buffer nascosto è la superficie Direct3D9. Le modifiche apportate al buffer nascosto vengono copiate nel buffer anteriore quando si chiama il Unlock metodo .

La figura seguente mostra la relazione tra il buffer nascosto e il buffer anteriore.

D3DImage display buffers

Creazione di dispositivi Direct3D9

Per eseguire il rendering del contenuto Direct3D9, è necessario creare un dispositivo Direct3D9. Esistono due oggetti Direct3D9 che è possibile usare per creare un dispositivo IDirect3D9 e IDirect3D9Ex. Usare questi oggetti rispettivamente per creare IDirect3DDevice9 dispositivi e IDirect3DDevice9Ex .

Creare un dispositivo chiamando uno dei metodi seguenti.

  • IDirect3D9 * Direct3DCreate9(UINT SDKVersion);

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

In Windows Vista o in un sistema operativo successivo usare il Direct3DCreate9Ex metodo con una visualizzazione configurata per l'uso del modello di driver di visualizzazione windows (WDDM). Usare il Direct3DCreate9 metodo in qualsiasi altra piattaforma.

Disponibilità del metodo Direct3DCreate9Ex

D3d9.dll ha il Direct3DCreate9Ex metodo solo in Windows Vista o in un sistema operativo successivo. Se si collega direttamente la funzione in Windows XP, l'applicazione non viene caricata. Per determinare se il Direct3DCreate9Ex metodo è supportato, caricare la DLL e cercare l'indirizzo del processo. Nel codice seguente viene illustrato come eseguire il test per il Direct3DCreate9Ex metodo . Per un esempio di codice completo, vedere Procedura dettagliata: Creazione di contenuto Direct3D9 per l'hosting in 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;
}

Creazione HWND

La creazione di un dispositivo richiede un HWND. In generale, si crea un HWND fittizio per Direct3D9 da usare. Nell'esempio di codice seguente viene illustrato come creare un HWND fittizio.

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

Parametri presenti

La creazione di un dispositivo richiede anche uno D3DPRESENT_PARAMETERS struct, ma solo alcuni parametri sono importanti. Questi parametri vengono scelti per ridurre al minimo il footprint di memoria.

Impostare i BackBufferHeight campi e BackBufferWidth su 1. Impostandoli su 0, questi vengono impostati sulle dimensioni di HWND.

Impostare sempre i D3DCREATE_MULTITHREADED flag e D3DCREATE_FPU_PRESERVE per impedire la danneggiamento della memoria usata da Direct3D9 e per impedire a Direct3D9 di modificare le impostazioni FPU.

Il codice seguente illustra come inizializzare lo D3DPRESENT_PARAMETERS struct.

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

Creazione della destinazione di rendering del buffer nascosto

Per visualizzare il contenuto Direct3D9 in un D3DImageoggetto , creare una superficie Direct3D9 e assegnarla chiamando il SetBackBuffer metodo .

Verifica del supporto dell'adapter

Prima di creare una superficie, verificare che tutti gli adattatori supportino le proprietà della superficie necessarie. Anche se si esegue il rendering su una sola scheda, la finestra WPF può essere visualizzata in qualsiasi scheda del sistema. È consigliabile scrivere sempre codice Direct3D9 che gestisce le configurazioni con più schede ed è necessario controllare tutte le schede per il supporto, perché WPF potrebbe spostare la superficie tra le schede disponibili.

L'esempio di codice seguente illustra come controllare tutti gli adattatori nel sistema per il supporto 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;
}

Creazione della superficie

Prima di creare una superficie, verificare che le funzionalità del dispositivo supportino prestazioni ottimali nel sistema operativo di destinazione. Per altre informazioni, vedere Considerazioni sulle prestazioni per Direct3D9 e Interoperabilità WPF.

Dopo aver verificato le funzionalità del dispositivo, è possibile creare la superficie. Nell'esempio di codice seguente viene illustrato come creare la destinazione di rendering.

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

In Windows Vista e nei sistemi operativi successivi, configurati per l'uso di WDDM, è possibile creare una trama di destinazione di rendering e passare la superficie di livello 0 al SetBackBuffer metodo . Questo approccio non è consigliato in Windows XP, perché non è possibile creare una trama di destinazione di rendering bloccabile e le prestazioni verranno ridotte.

Gestione dello stato del dispositivo

La D3DImage classe gestisce due buffer di visualizzazione, denominati buffer nascosto e buffer anteriore. Il buffer nascosto è la superficie Direct3D. Le modifiche apportate al buffer nascosto vengono copiate nel buffer anteriore quando si chiama il Unlock metodo , in cui viene visualizzato nell'hardware. Occasionalmente, il buffer anteriore diventa non disponibile. Questa mancanza di disponibilità può essere causata dal blocco dello schermo, dalle applicazioni Direct3D esclusive a schermo intero, dal cambio utente o da altre attività di sistema. In questo caso, l'applicazione WPF riceve una notifica gestendo l'evento IsFrontBufferAvailableChanged . Il modo in cui l'applicazione risponde al buffer anteriore che diventa non disponibile dipende dal fatto che WPF sia abilitato per eseguire il fallback al rendering software. Il SetBackBuffer metodo dispone di un overload che accetta un parametro che specifica se WPF esegue il fallback al rendering software.

Quando si chiama l'overload o si chiama l'overload SetBackBuffer(D3DResourceType, IntPtr)SetBackBuffer(D3DResourceType, IntPtr, Boolean) con il enableSoftwareFallback parametro impostato su false, il sistema di rendering rilascia il relativo riferimento al buffer nascosto quando il buffer anteriore diventa non disponibile e non viene visualizzato alcun elemento. Quando il buffer anteriore è nuovamente disponibile, il sistema di rendering genera l'evento IsFrontBufferAvailableChanged per notificare all'applicazione WPF. È possibile creare un gestore eventi per l'evento per riavviare il IsFrontBufferAvailableChanged rendering con una superficie Direct3D valida. Per riavviare il rendering, è necessario chiamare SetBackBuffer.

Quando si chiama l'overload SetBackBuffer(D3DResourceType, IntPtr, Boolean) con il enableSoftwareFallback parametro impostato su true, il sistema di rendering mantiene il riferimento al buffer nascosto quando il buffer anteriore diventa non disponibile, quindi non è necessario chiamare SetBackBuffer quando il buffer anteriore è nuovamente disponibile.

Quando il rendering software è abilitato, potrebbero verificarsi situazioni in cui il dispositivo dell'utente non è più disponibile, ma il sistema di rendering mantiene un riferimento alla superficie Direct3D. Per verificare se un dispositivo Direct3D9 non è disponibile, chiamare il TestCooperativeLevel metodo . Per controllare un dispositivo Direct3D9Ex chiamare il CheckDeviceState metodo , perché il TestCooperativeLevel metodo è deprecato e restituisce sempre esito positivo. Se il dispositivo utente non è più disponibile, chiamare SetBackBuffer per rilasciare il riferimento di WPF al buffer nascosto. Se è necessario reimpostare il dispositivo, chiamare SetBackBuffer con il backBuffer parametro impostato su nulle quindi chiamare SetBackBuffer di nuovo con backBuffer impostato su una superficie Direct3D valida.

Chiamare il metodo per eseguire il Reset ripristino da un dispositivo non valido solo se si implementa il supporto per più schede. In caso contrario, rilasciare tutte le interfacce Direct3D9 e ricrearle completamente. Se il layout dell'adattatore è stato modificato, gli oggetti Direct3D9 creati prima che la modifica non venga aggiornata.

Gestione del ridimensionamento

Se un D3DImage oggetto viene visualizzato a una risoluzione diversa dalla dimensione nativa, viene ridimensionato in base all'oggetto corrente BitmapScalingMode, ad eccezione del fatto che Bilinear viene sostituito con Fant.

Se è necessaria una maggiore fedeltà, è necessario creare una nuova superficie quando il contenitore delle D3DImage dimensioni cambia.

Esistono tre possibili approcci per gestire il ridimensionamento.

  • Partecipare al sistema di layout e creare una nuova superficie quando cambiano le dimensioni. Non creare troppe superfici, perché è possibile esaurire o frammentare la memoria video.

  • Attendere che non si sia verificato un evento di ridimensionamento per un periodo di tempo fisso per creare la nuova superficie.

  • Creare un oggetto DispatcherTimer che controlla le dimensioni del contenitore più volte al secondo.

Ottimizzazione multi-monitor

Una riduzione significativa delle prestazioni può comportare un conseguente spostamento D3DImage del sistema di rendering in un altro monitor.

In WDDM, purché i monitor si trovino nella stessa scheda video e si usa Direct3DCreate9Ex, non vi è alcuna riduzione delle prestazioni. Se i monitor si trovano su schede video separate, le prestazioni vengono ridotte. In Windows XP le prestazioni sono sempre ridotte.

Quando si D3DImage passa a un altro monitor, è possibile creare una nuova superficie sull'adattatore corrispondente per ripristinare prestazioni ottimali.

Per evitare la riduzione delle prestazioni, scrivere codice in modo specifico per il caso multi-monitor. L'elenco seguente mostra un modo per scrivere codice multi-monitoraggio.

  1. Trovare un punto dello D3DImage spazio sullo schermo con il Visual.ProjectToScreen metodo .

  2. Usare il MonitorFromPoint metodo GDI per trovare il monitor che visualizza il punto.

  3. Usare il IDirect3D9::GetAdapterMonitor metodo per trovare l'adattatore Direct3D9 su cui si trova il monitor.

  4. Se l'adattatore non è uguale all'adattatore con il buffer nascosto, creare un nuovo buffer nascosto nel nuovo monitor e assegnarlo al D3DImage buffer nascosto.

Nota

Se i D3DImage monitoraggi straddles, le prestazioni saranno lente, tranne nel caso di WDDM e IDirect3D9Ex nella stessa scheda. In questa situazione non è possibile migliorare le prestazioni.

Nell'esempio di codice seguente viene illustrato come trovare il monitoraggio corrente.

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

Aggiornare il monitoraggio quando cambiano le dimensioni o la D3DImage posizione del contenitore oppure aggiornare il monitoraggio usando un DispatcherTimer oggetto che aggiorna alcune volte al secondo.

WPF Software Rendering

WPF esegue il rendering sincrono sul thread dell'interfaccia utente nel software nelle situazioni seguenti.

Quando si verifica una di queste situazioni, il sistema di rendering chiama il CopyBackBuffer metodo per copiare il buffer hardware nel software. L'implementazione predefinita chiama il metodo con la GetRenderTargetData superficie. Poiché questa chiamata si verifica al di fuori del modello Blocco/Sblocco, potrebbe non riuscire. In questo caso, il CopyBackBuffer metodo restituisce null e non viene visualizzata alcuna immagine.

È possibile eseguire l'override del CopyBackBuffer metodo , chiamare l'implementazione di base e, se restituisce null, è possibile restituire un segnaposto BitmapSource.

È anche possibile implementare il proprio rendering software anziché chiamare l'implementazione di base.

Nota

Se il rendering di WPF viene eseguito completamente nel software, D3DImage non viene visualizzato perché WPF non dispone di un buffer anteriore.

Vedi anche