directx-12-overhead-3dmark

DirectX 12: più draw calls e meno overhead grazie alle nuove API Microsoft

Il 2015 sarà un anno di grandi cambiamenti per le API dedicate alla grafica 3D. Forse dovrà trascorrere parecchio tempo prima che gli utenti possano percepirne i reali benefici, ma gli sviluppatori hanno già le idee piuttosto chiare: l’arrivo ormai prossimo di Mantle 1.0 e DirectX 12 sta per inaugurare una rivoluzione con pochi precedenti nella storia del mezzo videoludico, paragonabile forse solo all’avvento delle prime GPU discrete negli anni ’90. Finora si è discusso moltissmo sui possibili benefici di queste nuove API, ma nessuno ha ancora avuto la possibilità di testarle con videogiochi progettati per sfruttarne appieno le potenzialità. Realisticamente, i primi titoli ottimizzati per Mantle 1.0 e DirectX 12 usciranno a partire dal 2016; nel frattempo gli sviluppatori avranno modo di conoscere meglio le funzionalità messe a disposizione dalle librerie, aprendo la strada ad orizzonti ancora inesplorati. Tra le novità di rilievo che caratterizzano le nuove API – specialmente le DirectX 12 – una in particolare sembra promettere sviluppi interessanti, soprattutto sul lato delle prestazioni: la riduzione dell’overhead causato dalle draw calls, che costituisce il fattore limitativo più importante nell’attuale generazione di videogiochi.


In sintesi, la situazione è questa: in passato le GPU discrete costituivano un limite per le CPU, perché non possedevano la potenza grafica necessaria a renderizzare scenari tridimensionali complessi; con il passare del tempo, però, le nuove tecnologie hanno consentito agli ingegneri di progettare GPU sempre più potenti, in grado di calcolare in parallelo quantità via via superiori di dati. Oggi la situazione si è invertita e sono le CPU a limitare le GPU, proprio a causa dell’overhead generato dalle draw calls. Questo problema costringe gli sviluppatori a limitare costantemente il numero di oggetti renderizzati a schermo dai motori grafici, pena un drastico calo di fps. Da qui l’importanza delle nuove API, progettate proprio per consentire ai programmatori aumentare il numero di draw calls e ridurre l’overhead: per raggiungere l’obiettivo, Microsoft e AMD hanno strutturato le loro librerie in modo che possano dialogare con l’hardware ad un livello più basso, così come avviene in ambiente console, e i primi risultati sono incoraggianti. Prima di avventurarci oltre, però, cerchiamo di capire che cosa sono le draw calls e perché è necessario gestirle in modo efficiente.



DRAW CALLS: COSA SONO E PERCHE’ E’ OPPORTUNO GESTIRLE IN MODO EFFICIENTE

Per renderizzare una scena tridimensionale occore innanzitutto preparare gli asset, che sono liste di dati contenenti tutte le informazioni relative ai vertici dei poligoni e alle texture. Convertire gli asset in immagini 3D è compito di CPU e GPU. Inizialmente i dati sono trasferiti dall’hard-disk alla RAM, per un accesso più rapido; successivamente le mesh e le texture sono caricate nella VRAM, la memoria video che risiede fisicamente sulla scheda. Se dopo essere stata caricata in VRAM una texture non serve più, allora viene eliminata anche dalla RAM di sistema, a condizione che non debba essere nuovamente utilizzata nell’immediato futuro (in questo caso occorrerebbe ricaricarla dall’hard-disk, sprecando risorse). Le mesh invece devono restare comunque nella RAM, perché la CPU ha bisogno di accedervi continuamente per il calcolo delle collisioni. Prima che la renderizzazione dell’immagine possa partire, la CPU si occupa anche di settare una lista di variabili globali che descrivono come le mesh debbano essere renderizzate: questa lista si chiama Render State. Il Render State consiste in una serie di istruzioni che spiegano alla GPU come trattare vertici, texture, materiali, illuminazione, pixel shader, etc.. Ogni singola mesh verrà renderizzata in base al Render State. In queste fasi preliminari la GPU non ha ancora ricevuto istruzioni dal processore, quindi si trova in una fase di idle e non svolge alcuna operazione.


Dopo aver definito il Render State la CPU può finalmente chiedere alla GPU di disegnare la scena: questa richiesta è la Draw Call. La Draw Call è un istruzione relativa alla singola mesh, che parte dalla CPU e arriva alla GPU transitando per alcune fasi intermedie. L’istruzione indica solamente quale mesh deve essere renderizzata, e non contiene nessun’altra informazione. Dopo che l’istruzione è partita, la GPU legge il Render State e tutti i dati relativi ai vertici, convertendo poi le informazioni in una scena tridimensionale. Il processo di conversione è conosciuto come Pipeline. Durante il pipelining la GPU crea triangoli a partire dai vertici, calcolando poi come sono disposti nello spazio tridimensionale e come texturizzare ogni singolo pixel. Durante l’operazione di rendering il grosso del lavoro è svolto quindi dalla GPU, che deve eseguire un elevato numero di task in parallelo e nel più breve tempo possibile. Ma come comunicano CPU e GPU? Prima di inviare nuove istruzioni la CPU deve attendere che la GPU finisca il suo lavoro, oppure no? Fortunatamente no. Questo creerebbe colli di bottiglia che renderebbero il lavoro in parallelo impossibile. A fare da mediatore è il Command Buffer, che si occupa di accumulare le istruzioni della CPU in modo tale che la GPU possa leggerle in modo del tutto indipendente. Nello specifico, quando la CPU vole renderizzare qualcosa inoltra la relativa istruzione nel buffer; successivamente, se la GPU ha delle risorse libere da spendere preleva l’istruzione e la esegue. L’esempio di istruzione che ci interessa, in questo caso, è la Draw Call.


Il numero di Draw Calls deve essere contenuto: il motivo è che la GPU può trasformare e renderizzare triangoli molto più velocemente rispetto al tempo che la CPU impiega per generare le istruzioni da mettere nel buffer. Se il numero di Draw Calls fosse troppo elevato si creerebbe un collo di bottiglia che costringerebbe la GPU in idle, in attesa che la CPU inoltri le istruzioni. Come se non bastasse, ogni singolo gruppo di draw calls produce un piccolo overhead che causa un notevole spreco di tempo e risorse. Nello scenario che stiamo descrivendo ci siamo limitati a considerare il caso in cui ad ogni draw call è associata una singola mesh con un solo render state, che costituisce un caso ideale. Nella realtà, purtroppo, molto spesso ad una singola mesh sono associati diversi render state, e questo rende il problema dell’overhead ancora più marcato. Un esempio di overhead può essere ricavato studiando il funzionamento del Command Buffer. Di solito CPU e GPU comunicano settando dei flag sul buffer, che riguardano principalmente i puntatori di read (lettura) e write (scrittura): se una delle due unità di elaborazione inoltra una richiesta di lettura e l’altra non ha ancora scritto sul buffer il dato da leggere, allora si genera un overhead. Di solito è la CPU a causare l’overhead, perché non riesce a soddisfare le continue richieste di lettura della GPU. Per limitare il problema è opportuno evitare di inoltrare le istruzioni in sequenza, cercando piuttosto riempire il buffer con un blocco di istruzioni collegate e poi inviare alla GPU le informazioni tutte insieme. Questo accresce il rischio che la GPU debba attendere che la CPU abbia finito di costruire la lista di istruzioni prima di iniziare a disegnare la scena, ma nel contempo riduce l’overhead di comunicazione, perchè presumibilmente mentre la CPU prepara la seconda lista di istruzioni da caricare nel buffer la GPU sarà ancora impegnata a renderizzare. In questo contesto, lo scopo delle API (DirectX, OpenGL, Mantle, etc..) è proprio quello di facilitare la comunicazione tra GPU, CPU, command buffer e driver, minimizzando così il rischio di overhead.



DIRECTX 12, GESTIONE DRAW CALLS E OVERHEAD: IL TEST CON 3DMARK

In media i moderni engine grafici richiedono la creazione di migliaia di draw calls per renderizzare le immagini 3D. Il problema è che ciascuna draw call, come vi abbiamo spiegato, rischia di generare un forte overhead sulla CPU, limitando di fatto le prestazioni dell’intero sistema. Con DirectX 12 e Mantle però è possibile ridurre l’impatto dell’overhead in modo consistente, permettendo così al sistema di renderizzare a schermo un maggior numero di oggetti, texture ed effetti di post-processing. Sebbene le nuove DirectX 12 non siano ancora state rilasciate al pubblico – occorrerà attendere qualche mese – possiamo già farci un’idea dei loro effettivi benefici grazie ad un nuovo benchmark sintetico di 3DMark, chiamato “3DMark API Overhead feature test”. Il test è stato progettato per misurare le differenze prestazionali tra DirectX 11, DirectX 12 e Mantle, e stabilire come queste si riflettano su un miglior utilizzo delle CPU multi-core. Per valutare le prestazioni di ciascuna API il benchmark genera progressivamente un numero sempre più elevato di draw calls, arrestandosi solo quando il frame rate scende sotto i 30 FPS. Al termine del processo il software moltiplica poi il numero di draw calls al secondo per il tempo trascorso prima dell’arresto, restituendo il totale. Se siete curiosi di vedere i risultati delle nuove DirectX 12, date un’occhiata a questo video:



TODAY

17 Jan

Friday

Le Rubriche

Photo Gallery