Perché la sicurezza dei thread è così importante per le API grafiche?


21

Sia Vulkan che DirectX12 sono dichiarati utilizzabili in modo thread-safe. Le persone sembrano essere entusiaste di questo.

Perché questa è considerata una caratteristica così grande? L'elaborazione "reale" viene comunque lanciata sul bridge di memoria su un'unità di elaborazione separata.

Inoltre, se è così grande, perché fino ad ora non è emersa un'API grafica thread-safe?


Questo articolo è molto più "focalizzato sul giocatore" ma potrebbe darti alcune intuizioni ... pcgamer.com/what-directx-12-means-for-gamers-and-developers
glampert

Risposte:


13

Il vantaggio principale sarebbe che sarebbe più semplice dividere le attività della CPU in più thread, senza dover risolvere tutti i problemi difficili con l'accesso all'API grafica. Normalmente dovresti rendere attuale il contesto (che potrebbe avere cattive implicazioni sulle prestazioni) o fornire una coda e chiamare l'API grafica in un singolo thread. Non penso che si ottengano prestazioni in questo modo, perché la GPU le elabora comunque in modo sequenziale, ma rende il lavoro degli sviluppatori molto più semplice.

Il motivo per cui non è stato fatto fino ad ora probabilmente è perché directx e opengl sono stati creati in un'epoca in cui il multithreading non era realmente evidente. Anche la scheda Khronos è molto conservatrice nel cambiare l'API. La loro opinione su Vulkan è anche che coesisterà accanto a OpenGL, perché entrambi hanno scopi diversi. Probabilmente non è stato fino a poco tempo fa che il paralismo è diventato così importante, poiché i consumatori hanno accesso a sempre più processori.

EDIT: non intendo dire che non si ottengono prestazioni dal lavoro in più CPU, non è utile dividere le chiamate in più thread per creare trame / shader più velocemente. Piuttosto, le prestazioni si ottengono grazie all'aumento del numero di processori impegnati e alla gestione della GPU con le cose da eseguire.


1
Come nota aggiuntiva OpenGL funziona generalmente solo su un thread, quindi un'app ad alta intensità grafica potrebbe massimizzare un core. Qualcosa come Vulkan consente a più thread di inviare comandi a una coda, il che significa che è possibile effettuare molte chiamate grafiche da più thread.
Insaponato il

9

C'è molto lavoro necessario sulla CPU per impostare un frame per la GPU e una buona parte di quel lavoro è all'interno del driver grafico. Prima di DX12 / Vulkan, il lavoro del driver grafico era essenzialmente costretto a essere sottoposto a thread singolo dal design dell'API.

La speranza è che DX12 / Vulkan risolvano tale restrizione, consentendo di eseguire il lavoro del driver in parallelo su più thread della CPU all'interno di un frame. Ciò consentirà un uso più efficiente delle CPU multicore, consentendo ai motori di gioco di spingere scene più complesse senza essere vincolati alla CPU. Questa è la speranza: se sarà realizzato nella pratica è qualcosa che dovremo aspettare per vedere nei prossimi anni.

Per elaborare un po ': l'output di un renderer del motore di gioco è un flusso di chiamate API DX / GL che descrivono la sequenza di operazioni per il rendering di un frame. Tuttavia, esiste una grande distanza tra il flusso di chiamate API e gli effettivi buffer dei comandi binari consumati dall'hardware della GPU. Il driver deve "compilare" le chiamate API nel linguaggio macchina della GPU, per così dire. Questo non è un processo banale: comporta molta traduzione dei concetti API in realtà hardware di basso livello, convalida per assicurarsi che la GPU non sia mai impostata su uno stato non valido, conflitto su allocazioni di memoria e dati, tracciamento delle modifiche di stato per emettere il comandi di basso livello corretti e così via. Il driver grafico è responsabile di tutte queste cose.

In DX11 / GL4 e API precedenti, questo lavoro viene in genere svolto da un singolo thread di driver. Anche se si chiama l'API da più thread (cosa che è possibile fare utilizzando elenchi di comandi differiti DX11, ad esempio), si aggiunge solo un po 'di lavoro a una coda per il thread del driver da mordere in seguito. Una grande ragione di ciò è il monitoraggio dello stato che ho citato prima. Molti dei dettagli di configurazione della GPU a livello hardware richiedono la conoscenza dell'attuale stato della pipeline grafica, quindi non c'è un buon modo per suddividere l'elenco dei comandi in blocchi che possono essere elaborati in parallelo: ogni blocco dovrebbe sapere esattamente quale stato dovrebbe iniziare con, anche se il blocco precedente non è stato ancora elaborato.

Questa è una delle grandi cose che sono cambiate in DX12 / Vulkan. Per prima cosa, incorporano quasi tutto lo stato della pipeline grafica in un oggetto, e per un altro (almeno in DX12) quando inizi a creare un elenco di comandi devi fornire uno stato iniziale della pipeline; lo stato non è ereditato da un elenco di comandi al successivo. In linea di principio, ciò consente al driver di non dover sapere nulla sugli elenchi di comandi precedenti prima di poter iniziare la compilazione e che a sua volta consente all'applicazione di suddividere il suo rendering in blocchi parallelizzabili, producendo elenchi di comandi completamente compilati, che possono quindi essere concatenati insieme e inviati alla GPU con un minimo sforzo.

Naturalmente, ci sono molti altri cambiamenti nelle nuove API, ma per quanto riguarda il multithreading, questa è la parte più importante.


5

Le GPU moderne generalmente hanno un'unica sezione di frontend che elabora un flusso completamente lineare di comandi dalla CPU. Che si tratti di un design hardware naturale o se si è semplicemente evoluto dai tempi in cui c'era un singolo core della CPU che generava comandi per la GPU è discutibile, ma per ora è la realtà. Quindi, se generi un singolo flusso lineare di comandi stateful, ovviamente ha senso generare quel flusso linearmente su un singolo thread sulla CPU! Destra?

Bene, le GPU moderne hanno anche un backend unificato molto flessibile che può lavorare su molte cose diverse contemporaneamente. In generale, la GPU funziona su vertici e pixel con granularità abbastanza fine. Non c'è molta differenza tra una GPU che elabora 1024 vertici in un disegno e 512 + 512 vertici in due disegni diversi.

Ciò suggerisce un modo abbastanza naturale per fare meno lavoro: invece di lanciare un gran numero di vertici sulla GPU in una singola chiamata di disegno, dividere il modello in sezioni, eseguire una cullatura economica a basso costo su quelle sezioni e inviare ogni pezzo singolarmente se passa il test di abbattimento. Se lo fai alla giusta granularità, dovresti ottenere una bella accelerazione!

Sfortunatamente, nell'attuale realtà delle API grafiche, le chiamate di disegno sono estremamente costose per la CPU. Una spiegazione semplificata del perché: i cambiamenti di stato sulla GPU potrebbero non corrispondere direttamente alle chiamate API grafiche, quindi molte chiamate API grafiche impostano semplicemente uno stato all'interno del driver e la chiamata di disegno che dipenderebbe da questo nuovo stato va e guarda tutto stato contrassegnato come modificato dall'ultima estrazione, lo scrive nel flusso di comandi per la GPU, quindi avvia effettivamente l'estrazione. Questo è tutto il lavoro svolto nel tentativo di ottenere un flusso di comandi snello e medio per l'unità frontend GPU.

Ciò si riduce al fatto che hai un budget per le chiamate di prelievo che è interamente imposto dal sovraccarico del conducente . (Penso di aver sentito che in questi giorni è possibile cavarsela con circa 5.000 per frame per un titolo di 60 FPS.) È possibile aumentarlo di una grande percentuale creando questo flusso di comandi in blocchi paralleli.

Ci sono anche altri motivi (ad esempio, timewarp asincrono per miglioramenti della latenza VR), ma questo è importante per i giochi associati alla grafica e altri software con richiamo (come i pacchetti di modellazione 3D).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.