Condividi tramite


Programmazione asincrona (DirectX e C++)

Questo argomento illustra vari aspetti da considerare quando si usa la programmazione asincrona e il threading con DirectX.

Programmazione asincrona e DirectX

Se si sta imparando a usare DirectX o si è già esperti, è consigliabile inserire tutta la pipeline di elaborazione grafica in un solo thread. In una determinata scena di un gioco sono presenti risorse comuni, ad esempio bitmap, shader e altri asset che richiedono l'accesso esclusivo. Queste risorse richiedono la sincronizzazione di qualsiasi accesso a queste risorse tra i thread paralleli. Il rendering è un processo difficile da parallelizzare tra più thread.

Tuttavia, se il gioco è sufficientemente complesso o se si sta cercando di ottenere prestazioni migliori, è possibile usare la programmazione asincrona per parallelizzare alcuni dei componenti non specifici della pipeline di rendering. L'hardware moderno include più CPU core e hyperthreading che l'app dovrebbe sfruttare. Ci si può assicurare di usare la programmazione asincrona per alcuni dei componenti del gioco che non necessitano dell'accesso diretto al contesto di dispositivo Direct3D, ad esempio:

  • I/O dei file
  • fisica
  • AI
  • networking
  • audio
  • controlli
  • Componenti dell'interfaccia utente basati su XAML

L'app può gestire questi componenti in più thread simultanei. L'I/O dei file, in particolare il caricamento degli asset, trae un notevole vantaggio dal caricamento asincrono, perché il gioco o l'app può essere in uno stato interattivo mentre vengono caricati o trasmessi diversi asset (anche centinaia). Il modo più semplice per creare e gestire questi thread consiste nell'usare la Parallel Patterns Library e il modello di attività contenuto nello spazio dei nomi di concorrenza definito in PPLTasks.h. L'uso di Parallel Patterns Library sfrutta direttamente più CPU core e hyperthreading e può migliorare tutti gli elementi, dai tempi di caricamento percepiti agli hitches e ai ritardi che includono calcoli della CPU o elaborazione di rete intensivi.

Nota In un'app UWP (Universal Windows Platform), l'interfaccia utente viene eseguita interamente in un apartment a thread singolo (STA). Se si sta creando un'interfaccia utente per un gioco DirectX usando l'interoperabilità XAML, è possibile accedere solo ai controlli con STA.

 

Multithreading con dispositivi Direct3D

Il multithreading per i contesti di dispositivo è disponibile solo nei dispositivi grafici che supportano un livello di funzionalità Direct3D di 11_0 o versione successiva. Tuttavia, si potrebbe voler massimizzare l'uso della potente GPU in molte piattaforme, ad esempio piattaforme di gioco dedicate. Nel caso più semplice, è possibile separare il rendering di una sovrimpressione HUD (Heads-up Display) dal rendering e dalla proiezione della scena 3D e far sì che entrambi i componenti usino pipeline parallele separate. Entrambi i thread devono usare lo stesso ID3D11DeviceContext per creare e gestire gli oggetti risorsa (texture, mesh, shader e altri asset), che è a thread singolo e che richiede l'implementazione di un qualche tipo di meccanismo di sincronizzazione (ad esempio sezioni critiche) per accedervi in modo sicuro. Inoltre, anche se è possibile creare elenchi di comandi separati per il contesto di dispositivo in thread diversi (per il rendering posticipato), non è possibile riprodurre questi elenchi di comandi contemporaneamente nella stessa istanza ID3D11DeviceContext.

Ora, l'app può anche usare ID3D11Device, sicuro per il multithreading, per creare oggetti risorsa. Quindi, perché non usare sempre ID3D11Device anziché ID3D11DeviceContext? Il supporto dei driver per il multithreading potrebbe non essere disponibile per alcune interfacce grafiche. È possibile eseguire query sul dispositivo e verificare se supporta il multithreading, ma se si desidera raggiungere un pubblico più vasto, è possibile usare ID3D11DeviceContext a thread singolo per gestire gli oggetti delle risorse. Detto questo, quando il driver di dispositivo grafico non supporta il multithreading o gli elenchi di comandi, Direct3D 11 tenta di gestire l'accesso sincronizzato al contesto di dispositivo internamente; e se gli elenchi di comandi non sono supportati, fornisce un'implementazione software. Di conseguenza, è possibile scrivere codice multithreading che verrà eseguito su piattaforme con interfacce grafiche che non supportano il supporto driver per l'accesso al contesto di dispositivo multithreading.

Se l'app supporta thread separati per l'elaborazione degli elenchi di comandi e per la visualizzazione di fotogrammi, è probabile che si voglia mantenere attiva la GPU, elaborando gli elenchi di comandi visualizzando i fotogrammi in modo tempestivo senza stub o ritardo percepibili. In questo caso, è possibile usare un ID3D11DeviceContext separato per ogni thread e condividere risorse (ad esempio texture) creandole con il flag D3D11_RESOURCE_MISC_SHARED. In questo scenario, è necessario chiamare ID3D11DeviceContext::Flush nel thread di elaborazione per completare l'esecuzione dell'elenco di comandi prima di visualizzare i risultati dell'elaborazione dell'oggetto risorsa nel thread di visualizzazione.

Rendering posticipato

Il rendering posticipato registra i comandi grafici in un elenco di comandi in modo che possano essere riprodotti in un altro momento ed è progettato per supportare il rendering in un thread durante la registrazione dei comandi per il rendering su thread aggiuntivi. Dopo averli completati, questi comandi possono essere eseguiti nel thread che genera l'oggetto di visualizzazione finale (buffer di frame, texture o altro output grafico).

Creare un contesto posticipato usando ID3D11Device::CreateDeferredContext (anziché D3D11CreateDevice o D3D11CreateDeviceAndSwapChain, che creano un contesto immediato). Per maggiori informazioni, vedere Rendering immediato e posticipato.