Condividi tramite


Interoperabilità 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 l'interoperabilità WPF.

Buffer di visualizzazione

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

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

dei buffer di visualizzazione D3DImage

Creazione di dispositivi Direct3D9

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

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 metodo Direct3DCreate9Ex con uno schermo configurato per utilizzare il modello di driver grafico di Windows (WDDM). Usare il metodo Direct3DCreate9 in qualsiasi altra piattaforma.

Disponibilità del metodo Direct3DCreate9Ex

Il d3d9.dll ha il metodo Direct3DCreate9Ex 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 metodo Direct3DCreate9Ex è supportato, caricare la DLL e cercare l'indirizzo del processo. Il codice seguente illustra come eseguire il test per il metodo Direct3DCreate9Ex. Per un esempio di codice completo, vedere Procedura dettagliata: Creazione di contenuto Direct3D9 da ospitare 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 di una finestra HWND

La creazione di un dispositivo richiede un HWND. In generale, per Direct3D9 si crea un HWND fittizio 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 attuali

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

Impostare i campi BackBufferHeight e BackBufferWidth su 1. Impostando questi su 0, vengono adattati alle dimensioni dell'HWND.

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

Nel codice seguente viene illustrato come inizializzare lo struct 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;
}

Creazione della destinazione di rendering del buffer nascosto

Per visualizzare il contenuto Direct3D9 in un D3DImage, crea una superficie Direct3D9 e assegnala chiamando il metodo SetBackBuffer.

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 un solo adattatore, la finestra WPF può essere visualizzata su qualsiasi adattatore del sistema. Dovresti sempre scrivere codice Direct3D9 che gestisce le configurazioni multi-adattatore e controllare il supporto di tutti gli adattatori, perché WPF potrebbe spostare la superficie tra gli adattatori 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 ulteriori informazioni, consultare Considerazioni sulle prestazioni per l'interoperabilità tra Direct3D9 e 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 texture di destinazione di rendering e passare la superficie di livello 0 al metodo SetBackBuffer. 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 classe D3DImage gestisce due buffer di visualizzazione, denominati buffer nascosto e il buffer anteriore . Il back buffer è la superficie Direct3D. Le modifiche apportate al buffer di sfondo vengono copiate nel buffer principale quando si chiama il metodo Unlock, dove è mostrato sull'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 a eseguire il rendering in modalità software. Il metodo SetBackBuffer ha una sovraccarico che accetta un parametro che determina se WPF deve ricorrere al rendering software.

Quando si chiama l'overload SetBackBuffer(D3DResourceType, IntPtr) o si chiama l'overload SetBackBuffer(D3DResourceType, IntPtr, Boolean) con il parametro enableSoftwareFallback 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 la tua applicazione WPF. È possibile creare un gestore eventi per l'evento IsFrontBufferAvailableChanged per riavviare il rendering con una superficie Direct3D valida. Per riavviare il rendering, è necessario chiamare SetBackBuffer.

Quando si chiama l'overload SetBackBuffer(D3DResourceType, IntPtr, Boolean) con il parametro enableSoftwareFallback impostato su true, il sistema di rendering mantiene il riferimento al buffer posteriore 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 metodo TestCooperativeLevel. Per controllare un dispositivo Direct3D9Ex, utilizzare il metodo CheckDeviceState, perché il metodo TestCooperativeLevel è deprecato e restituisce sempre un risultato positivo. Se il dispositivo utente non è più disponibile, chiamare SetBackBuffer per rilasciare il riferimento di WPF al buffer posteriore. Se è necessario reimpostare il dispositivo, chiamare SetBackBuffer con il parametro backBuffer impostato su nulle quindi chiamare di nuovo SetBackBuffer con backBuffer impostato su una superficie Direct3D valida.

Chiamare il metodo Reset per eseguire il ripristino da un dispositivo non valido solo se si implementa il supporto multi-adattatore. In caso contrario, rilasciare tutte le interfacce Direct3D9 e ricrearle completamente. Se il layout dell'adattatore è stato modificato, gli oggetti Direct3D9 creati prima della modifica non vengono aggiornati.

Gestione del ridimensionamento

Se un D3DImage viene visualizzato a una risoluzione diversa dalla dimensione nativa, viene ridimensionato in base al BitmapScalingModecorrente, ad eccezione del fatto che Bilinear viene sostituito per Fant.

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

Esistono tre possibili approcci per gestire il ridimensionamento.

  • Partecipa al sistema di layout e crea 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 DispatcherTimer che controlla le dimensioni del contenitore diverse volte al secondo.

Ottimizzazione multi-monitor

Una riduzione significativa delle prestazioni può risultare quando il sistema di rendering sposta un D3DImage su 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 il 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-monitor.

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

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

  3. Usare il metodo IDirect3D9::GetAdapterMonitor 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 buffer nascosto D3DImage.

Nota

Se il D3DImage attraversa i monitor, le prestazioni saranno rallentate, tranne nel caso di WDDM e IDirect3D9Ex sullo stesso adattatore. In questa situazione non è possibile migliorare le prestazioni.

Nell'esempio di codice seguente viene illustrato come trovare il monitor 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 monitor quando cambiano le dimensioni o la posizione del contenitore D3DImage, oppure aggiornare il monitor usando un DispatcherTimer che si aggiorna diverse volte al secondo.

WPF Software Rendering

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

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

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

È anche possibile implementare il proprio software di rendering 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.

Vedere anche