Interoperatività di WPF e Direct3D9
È possibile includere contenuto Direct3D9 in un'applicazione Windows Presentation Foundation (WPF). Questo argomento descrive come creare contenuto Direct3D9 che possa interoperare in maniera efficiente con WPF.
![]() |
---|
In caso di utilizzo di contenuto Direct3D9 in WPF, è necessario tenere in considerazione anche le prestazioni.Per ulteriori informazioni su come ottimizzare le prestazioni, vedere Considerazioni sulle prestazioni per l'interoperabilità fra Direct3D9 e WPF. |
Buffer di visualizzazione
La classe D3DImage gestisce due buffer di visualizzazione, definiti buffer nascosto e front buffer. Il buffer nascosto rappresenta la superficie Direct3D9. Le modifiche apportate al buffer nascosto vengono copiate nel front buffer quando si chiama il metodo Unlock.
Nell'illustrazione seguente è mostrata la relazione tra buffer nascosto e front buffer.
Creazione di un dispositivo Direct3D9
Per eseguire il rendering del contenuto Direct3D9, è necessario creare un dispositivo Direct3D9. Esistono due oggetti Direct3D9 che è possibile utilizzare per creare un dispositivo, IDirect3D9 e IDirect3D9Ex. Utilizzare questi oggetti per creare rispettivamente i dispositivi IDirect3DDevice9 e IDirect3DDevice9Ex.
Creare un dispositivo chiamando uno dei metodi seguenti:
IDirect3D9 * Direct3DCreate9(UINT SDKVersion);
HRESULT Direct3DCreate9Ex(UINT SDKVersion, IDirect3D9Ex **ppD3D);
Utilizzare il metodo Direct3DCreate9Ex in Windows Vista con una visualizzazione configurata per l'utilizzo di WDDM (Windows Display Driver Model). Utilizzare il metodo Direct3DCreate9 su qualsiasi altra piattaforma.
Disponibilità del metodo Direct3DCreate9Ex
Solo d3d9.dll in Windows Vista dispone del metodo Direct3DCreate9Ex. Se si collega direttamente la funzione in Windows XP, risulta impossibile caricare l'applicazione. Per determinare se il metodo Direct3DCreate9Ex è supportato, caricare la DLL e cercare l'indirizzo di routine. Nel codice seguente viene illustrato come eseguire test per il metodo Direct3DCreate9Ex. 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 di un HWND
La creazione di un dispositivo richiede un HWND. In generale si crea un HWND fittizio per l'utilizzo da parte di Direct3D9. Nell'esempio di codice riportato di seguito 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 una struttura D3DPRESENT_PARAMETERS, ma solo alcuni parametri sono importanti. Sono stati scelti questi parametri per ridurre il footprint di memoria.
Impostare i campi BackBufferHeight e BackBufferWidth su 1. Impostandoli su 0 vengono impostati sulle dimensioni di HWND.
Impostare sempre i flag D3DCREATE_MULTITHREADED e D3DCREATE_FPU_PRESERVE per impedire il danneggiamento della memoria utilizzata da Direct3D9 e per impedire a Direct3D9 di modificare le impostazioni FPU.
Nell'esempio di codice riportato di seguito viene illustrato come inizializzare la struttura 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 contenuto Direct3D9 in un oggetto D3DImage, si crea una superficie Direct3D9 e la si assegna chiamando il metodo SetBackBuffer.
Verifica del supporto dell'adattatore
Prima di creare una superficie, verificare che tutti gli adattatori supportino le proprietà della superficie richieste. Anche se si esegue il rendering a solo un adattatore, la finestra WPF può essere visualizzata su qualsiasi adattatore nel sistema. È necessario scrivere sempre codice Direct3D9 in grado di gestire configurazioni con più adattatori e verificare il supporto di tutti gli adattatori, in quanto in WPF la superficie potrebbe essere spostata tra gli adattatori disponibili.
Nell'esempio di codice seguente viene mostrato come verificare che tutti gli adattatori presenti nel sistema supportino 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 offrano buone prestazioni nel sistema operativo di destinazione. Per ulteriori informazioni, vedere Considerazioni sulle prestazioni per l'interoperabilità fra Direct3D9 e WPF.
Una volta verificate le funzionalità del dispositivo, sarà possibile creare la superficie. Nell'esempio di codice seguente viene illustrato come creare la destinazione per il 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, configurato per l'utilizzo di WDDM, è possibile creare una trama di destinazione del rendering e passare la superficie di livello 0 al metodo SetBackBuffer. Questo approccio non è consigliato in Windows XP in quanto non è possibile creare una trama di destinazione del rendering bloccabile, per cui le prestazioni risultano ridotte.
Gestione dello stato del dispositivo
Quando la proprietà IsFrontBufferAvailable passa da true a false, in WPF non viene visualizzato D3DImage e il buffer nascosto non viene copiato nel front buffer. Di solito questo significa che il dispositivo è stato perso.
Quando il dispositivo viene perso, il codice deve arrestare il rendering e attendere la transizione della proprietà IsFrontBufferAvailable a true. Gestire l'evento IsFrontBufferAvailableChanged in modo che gli venga notificata questa transizione.
Quando avviene la transizione della proprietà IsFrontBufferAvailable da false a true, è necessario controllare il dispositivo per determinare se sia valido.
Per i dispositivi Direct3D9 chiamare il metodo TestCooperativeLevel. Per i dispositivi Direct3D9Ex chiamare il metodo CheckDeviceState, perché il metodo TestCooperativeLevel è obsoleto e restituisce sempre un risultato corretto.
Se il dispositivo è valido, chiamare nuovamente il metodo SetBackBuffer con la superficie originale.
Se il dispositivo non è valido, è necessario reimpostare il dispositivo e ricreare le risorse. La chiamata al metodo SetBackBuffer con una superficie da un dispositivo non valido genera un'eccezione.
Chiamare il metodo Reset per il recupero da un dispositivo non valido solo se si implementa un supporto per più adattatori. 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 sono aggiornati.
Gestione del ridimensionamento
Se un oggetto D3DImage è visualizzato a una risoluzione diversa da quella nativa, viene ridimensionato sulla base dell'oggetto BitmapScalingMode corrente, ad eccezione del fatto che Bilinear viene sostituito con Fant.
Se è necessaria una maggiore fedeltà, è necessario creare una nuova superficie quando il contenitore di D3DImage si ridimensiona.
Sono possibili tre approcci per gestire il ridimensionamento.
Partecipare al sistema di layout e creare una nuova superficie quando la dimensione viene modificata. Non creare troppe superfici, perché è possibile esaurire o frammentare la memoria video.
Prima di creare una nuova superficie attendere che non si siano verificati eventi di ridimensionamento per un periodo di tempo fisso.
Creare un oggetto DispatcherTimer che controlla le dimensioni del contenitore varie volte al secondo.
Ottimizzazione per più monitor
Possono verificarsi prestazioni significativamente ridotte quando il sistema di rendering sposta un oggetto D3DImage a un altro monitor.
In WDDM non si verifica alcuna riduzione delle prestazioni fintantoché i monitor sono sulla stessa scheda video e si utilizza Direct3DCreate9Ex. Se i monitor sono su schede video diverse, le prestazioni si riducono. In Windows XP le prestazioni sono sempre ridotte.
Quando D3DImage si sposta su un altro monitor, per ripristinare le buone prestazioni, è possibile creare una nuova superficie nell'adattatore corrispondente.
Per evitare la riduzione delle prestazioni, scrivere codice specificamente per il caso di più monitor. Nell'elenco seguente viene illustrato un modo per scrivere codice per più monitor.
Trovare un punto di D3DImage nello spazio dello schermo con il metodo Visual.ProjectToScreen.
Utilizzare il metodo GDI MonitorFromPoint per trovare il monitor che sta visualizzando il punto.
Utilizzare il metodo IDirect3D9::GetAdapterMonitor per trovare l'adattatore Direct3D9 del monitor.
Se l'adattatore non è lo stesso utilizzato per il buffer nascosto, creare un nuovo buffer nascosto nel nuovo monitor e assegnarlo al buffer nascosto D3DImage.
![]() |
---|
Se D3DImage gestisce più monitor, le prestazioni saranno lente, salvo nel caso WDDM e IDirect3D9Ex siano sullo stesso adattatore.Non vi è modo di migliorare le prestazioni in questa situazione. |
Nell'esempio di codice riportato di seguito 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 la dimensione o la posizione del contenitore D3DImage variano oppure aggiornare il monitor utilizzando un oggetto DispatcherTimer che si aggiorna varie volte al secondo.
Rendering nel software di WPF
Nel software WPF esegue il rendering in modo sincrono sul thread UI nelle situazioni seguenti.
Stampa
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 con la superficie. Poiché questa chiamata si verifica all'esterno del modello di blocco/sblocco, può 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.
È inoltre possibile implementare software di rendering proprio anziché chiamare l'implementazione di base.
![]() |
---|
Se il rendering WPF viene eseguito completamente nel software, D3DImage non è visualizzato perché WPF non dispone di un front buffer. |
Vedere anche
Attività
Procedura dettagliata: creazione di contenuto Direct3D9 per l'hosting in WPF
Procedura dettagliata: hosting di contenuto Direct3D9 in WPF
Riferimenti
Concetti
Considerazioni sulle prestazioni per l'interoperabilità fra Direct3D9 e WPF