Interoperabilità direct3D 12

D3D12 può essere usato per scrivere applicazioni con componenti.

Panoramica dell'interoperabilità

D3D12 può essere molto potente e consente alle applicazioni di scrivere codice grafico con efficienza simile alla console, ma non tutte le applicazioni devono reinventare la ruota e scrivere l'intero motore di rendering da zero. In alcuni casi, un altro componente o una libreria l'ha già apportata meglio o in altri casi, le prestazioni di una parte di codice non sono fondamentali quanto la correttezza e la leggibilità.

Questa sezione illustra le tecniche di interoperabilità seguenti:

  • D3D12 e D3D12 nello stesso dispositivo
  • D3D12 e D3D12 in dispositivi diversi
  • D3D12 e qualsiasi combinazione di D3D11, D3D10 o D2D nello stesso dispositivo
  • D3D12 e qualsiasi combinazione di D3D11, D3D10 o D2D in dispositivi diversi
  • D3D12 e GDI o D3D12 e D3D11 e GDI

Motivi per l'uso dell'interoperabilità

Esistono diversi motivi per cui un'applicazione vuole l'interoperabilità D3D12 con altre API. Di seguito alcuni esempi:

  • Conversione incrementale: se si vuole convertire un'intera applicazione da D3D10 o D3D11 a D3D12, mantenendolo funzionante in fasi intermedie del processo di conversione (per abilitare il test e il debug).
  • Codice casella nera: se si desidera lasciare una parte specifica di un'applicazione così come è durante la conversione del resto del codice. Ad esempio, potrebbe non essere necessario convertire gli elementi dell'interfaccia utente di un gioco.
  • Componenti non modificabili: necessità di usare componenti che non sono di proprietà dell'applicazione, che non vengono scritti nella destinazione D3D12.
  • Un nuovo componente: non vuole convertire l'intera applicazione, ma vuole usare un nuovo componente scritto con D3D12.

Esistono quattro tecniche principali per l'interoperabilità in D3D12:

  • Un'app può scegliere di fornire un elenco di comandi aperto a un componente, che registra alcuni comandi di rendering aggiuntivi in una destinazione di rendering già associata. Ciò equivale a fornire un contesto di dispositivo preparato a un altro componente in D3D11 ed è ideale per elementi come l'aggiunta di interfaccia utente/testo a un buffer nascosto già associato.
  • Un'app può scegliere di fornire una coda di comandi a un componente, insieme a una risorsa di destinazione desiderata. Ciò equivale all'uso delle API ClearState o DeviceContextState in D3D11 per fornire un contesto di dispositivo pulito a un altro componente. Questo è il funzionamento dei componenti come D2D.
  • Un componente può scegliere un modello in cui produce un elenco di comandi, potenzialmente in parallelo, che l'app è responsabile dell'invio in un secondo momento. Almeno una risorsa deve essere fornita attraverso i limiti dei componenti. Questa stessa tecnica è disponibile in D3D11 usando contesti posticipati, anche se le prestazioni in D3D12 sono più auspicabili.
  • Ogni componente ha code e/o dispositivi personalizzati e l'app e i componenti devono condividere risorse e informazioni di sincronizzazione tra i limiti dei componenti. Questo è simile all'legacy ISurfaceQueuee all'IDXGIKeyedMutex più moderno.

Le differenze tra questi scenari sono esattamente condivise tra i limiti dei componenti. Si presuppone che il dispositivo sia condiviso, ma poiché fondamentalmente è senza stato, non è davvero rilevante. Gli oggetti chiave sono l'elenco dei comandi, la coda dei comandi, gli oggetti di sincronizzazione e le risorse. Ognuno di questi ha le proprie complicazioni durante la condivisione.

Condivisione di un elenco di comandi

Il metodo più semplice di interoperabilità richiede la condivisione solo di un elenco di comandi con una parte del motore. Al termine delle operazioni di rendering, la proprietà dell'elenco di comandi torna al chiamante. La proprietà dell'elenco di comandi può essere tracciata tramite lo stack. Poiché gli elenchi di comandi sono a thread singolo, non c'è modo per un'app di eseguire un'operazione unica o innovativa usando questa tecnica.

Condivisione di una coda di comandi

Probabilmente la tecnica più comune per più componenti che condividono un dispositivo nello stesso processo.

Quando la coda dei comandi è l'unità di condivisione, deve essere presente una chiamata al componente per informarlo che tutti gli elenchi di comandi in sospeso devono essere inviati immediatamente alla coda dei comandi e le code di comandi interne devono essere sincronizzate. Equivale all'API Scaricamento D3D11 ed è l'unico modo in cui l'applicazione può inviare elenchi di comandi o primitive di sincronizzazione.

Condivisione delle primitive di sincronizzazione

Il modello previsto per un componente che opera sui propri dispositivi e/o code di comandi sarà quello di accettare un handle ID3D12Fence o condiviso e la coppia UINT64 all'inizio del lavoro, che attenderà, e quindi un secondo handle ID3D12Fence o condiviso e una coppia UINT64 che segnalerà quando tutto il lavoro è completato. Questo modello corrisponde all'implementazione corrente di IDXGIKeyedMutex e della progettazione della sincronizzazione del modello flip DWM/DXGI.

Condivisione delle risorse

Di gran lunga la parte più complessa della scrittura di un'app D3D12 che sfrutta più componenti è come gestire le risorse condivise attraverso i limiti dei componenti. Ciò è dovuto principalmente al concetto di stati delle risorse. Anche se alcuni aspetti della progettazione dello stato delle risorse sono destinati a gestire la sincronizzazione all'interno dell'elenco di comandi, altri hanno un impatto tra gli elenchi di comandi, che influiscono sul layout delle risorse e sui set validi di operazioni o caratteristiche delle prestazioni per l'accesso ai dati delle risorse.

Ci sono due modelli di affrontare questa complicazione, entrambi implicano essenzialmente un contratto tra componenti.

  • Il contratto può essere definito dallo sviluppatore del componente e documentato. Questo potrebbe essere semplice come "la risorsa deve trovarsi nello stato predefinito all'avvio del lavoro e verrà reinserci nello stato predefinito al termine del lavoro" o potrebbe avere regole più complesse per consentire operazioni come la condivisione di un buffer di profondità senza forzare la risoluzione della profondità intermedia.
  • Il contratto può essere definito dall'applicazione in fase di esecuzione, al momento in cui la risorsa viene condivisa tra i limiti dei componenti. È costituito dalle stesse due informazioni, ovvero lo stato in cui la risorsa si trova quando il componente inizia a usarlo e lo stato in cui il componente deve lasciarlo al termine.

Scelta di un modello di interoperabilità

Per la maggior parte delle applicazioni D3D12, la condivisione di una coda di comandi è probabilmente il modello ideale. Consente la proprietà completa della creazione e dell'invio del lavoro, senza il sovraccarico aggiuntivo per la memoria di avere code ridondanti e senza l'impatto sulle prestazioni di gestione delle primitive di sincronizzazione GPU.

La condivisione delle primitive di sincronizzazione è necessaria quando i componenti devono gestire proprietà di coda diverse, ad esempio il tipo o la priorità, o una volta che la condivisione deve estendersi ai limiti del processo.

La condivisione o la produzione di elenchi di comandi non sono ampiamente utilizzati esternamente da componenti di terze parti, ma potrebbero essere ampiamente usati nei componenti interni a un motore di gioco.

API di interoperabilità

L'argomento Direct3D 11 on 12 illustra l'utilizzo di gran parte della superficie API correlata ai tipi di interoperabilità descritti in questo argomento.

Vedi anche il metodo ID3D12Device::CreateSharedHandle , che puoi usare per condividere le superfici tra le API grafiche di Windows.