Dopo tre settimane di stop dovute a pressanti impegni di lavoro e problemi di altra natura, che hanno costretto i miei colleghi della redazione di AD (a cui va un sentito ringraziamento) ad un impegno extra per coprire lo spazio solitamente occupato dalla mia rubrica, riprendiamo questa settimana con un articolo che, nel rispetto dell’alternanza proposta nel corso delle ultime uscite, sarà dedicato alle gpu. In particolare, visto l’interesse dimostrato per le operazioni di tessellation, vorrei approfondire quanto già accennato sul tessellator e le operazioni da esso svolte.
Innanzitutto, vediamo cos’è la TESSELLATION (rigorosamente scritta con la E :)). La definizione, in senso stretto, recita che la tessellation o tiling consiste nel ripetere una figura geometrica (o un gruppo di figure) chiusa fino a riempire un’intera superficie senza soluzione di continuità. In caso di utilizzo di una sola forma, si parla di pure tessellation. Quindi, questa mirabolante feature DX11 non è altro che la mera riproduzione di un triangolo o di un insieme di triangoli? In linea di massima e detta in parole molto povere si, anche se questa semplice operazione può portare notevoli benefici in termini di qualità d’immagine e di utilizzo di banda passante e memoria.Di fatto la tessellation, com’è applicata da una moderna GPU, è qualcosa di un po’ più complesso ma preferisco procedere per gradi, iniziando dalla semplice definizione e arrivando all’architettura di un tessellator DX11 ed ai suoi compiti.
Qualcuno potrebbe obiettare che anche geometry shader e geometry instancing hanno come output dei triangoli o delle patch e potrebbero essere usati in luogo della tessellation. Vero, però i GS non sono adatti alle operazioni di tessellation perchè il loro output è di tipo seriale
Il GI (geometry instancing), invece, può effettivamente essere usato come forma di primitiva tessellation; il suo limite principale è quello di permettere la ripetizione di una singola figura (sia essa un triangolo o una mesh più complessa).
L’emulazione della tessellation via GI su pipeline DX10 fa uso di una mesh pre renderizzata (di fatto ridotta ad una texture) che viene presa come base per effettuare l’operazione di “tiling”. In pratica si usano i dati contenuti in questa tabella per replicare la struttura della mesh stessa
per ottenere la figura finale con tessellation.
Gli stadi di una pipeline grafica coinvolti in questo tipo di operazione (su cui non mi dilungherò oltre) sono l’input assembler, il vertex buffer e i vertex shader; la tecnica è quella di campionare una texture non filtrata durante le operazioni geometriche, su cui scrivere i dati relativi alla mesh utilizzata come base per la tessellation. Sempre nell’ambito delle operazioni geometriche, si procede alla valutazione del LoD per stabilire il grado di tessellation.
Detto ciò, torniamo a parlare della tessellation vera e propria. Come detto, lo scopo del ricorso alla tessellation è quello di generare nuovi vertici a partire da un gruppo ristretto di dati geometrici. Questo può portare a vantaggi sia nelle prestazioni che nel risparmio di banda passante e di memoria occupata ma anche di miglioramenti a livello di qualità d’immagine.
Ovvio che tutti questi vantaggi non sono conseguibili nello stesso tempo: se si punta al miglioramento della qualità d’immagine che, nel caso specifico, consiste nel ridurre le spigolosità delle figure e degli oggetti a schermo, rendendoli più realistici, difficilmente si avrà un contestuale miglioramento delle prestazioni o una minor occupazione di banda. Mentre se, al contrario, si punta all’ottenimento delle migliori performance, lo si farà, presumibilmenmte, senza ottenere visibili migliorie a livello grafico. Torneremo su questo punto più avanti. Iniziamo, adesso, ad introdurre le tecniche di tessellation cominciando col familiarizzare con alcuni termini.
Con patch si intende una primitiva come quella di figura
in cui sono indicati anche i control point i cui valori rappresentano l’input del tessellator, sia esso implementato seguendo lo schema DX11 tramite hull shader, sia esso faccia uso di vertex shader in luogo dei primi.
Lo stadio preposto a vaòutare l’entità della tessellation stabilisce, in base alle indicazioni fornite dal coder e ai valori del LoD, quanto nuovi punti è necessario introdurre per ogni control point e, di conseguenza, in quanti poligoni deve essere suddivisa la mesh di partenza. Per attuare questa suddivisione si può far ricorso a tessellation di tipo regolare, che fa uso di poligoni regolari e congruenti, o di tessellation di tipo semi regular, che fa uso di poligoni di tipo regolare ma differenti tra loro. Quello che si fa è prendere un control point e creare qualcosa del genere
nel caso di regular tessellation o a qualcosa del tipo
nel caso di irregular tessellation.
Quello al centro è un control point tra quelli presennti in input mentre i numeri che ciascuna figura riporta in basso, indicano quante figure sono generate dall’operazione di tessellation per quel control point (6, 4, 5 e 4 rispettivamente per le 4 figure) e da quanti lati è composto ciascuno dei poligoni generati (nel primo caso si tratta di 6 triangoli, nel secondo di 4 quadrati, nel terzo di 3 triangoli e 2 quadrati).
Altro parametro di cui tener conto è il tessellation edge factor che, detto in parole semplici, rappresenta il numero di segmenti in cui viene suddiviso un lato di uno dei poligoni della patch d’origine. Esempi di differenti tessellation edge factor sono riportati nella figura seguente
in cui tre lati hanno edge factor pari a 4 mentre il quarto lo ha uguale a 1 considerando che la figura di partenza era questa in basso
La definizione del tessellation edge factor è importante anche in virtù del fatto che due patch contigue devono presentare, sulle rispettive superfici di contatto, lo stesso edge factor, pena la generazione di artefatti.
A questo punto, possimao iniziare a vedere cosa accade ad una patch che viene utilizzata come input per le operazioni di tessellation. Per questo scopo, diamo una ripassata alla pipeline grafica che include il tessellator
Il primo stadio, l’input assembler, riceve i dati da vertex e index buffer che, aloro volta, sono stati riempiti dalla cpu. Questi dati contengono le informazioni di base necessarie alla creazione della geometria del frame. Il suo output sarà costituito da primitive che potranno contenere un numero di vertici variabile tra 1 e 32. I dati manipolati a questo livello, appartengono all’object o model space che dir si voglia.
Le mesh così ottenute sono inviate ai vertex shader, che possono compiere una serie di operazioni, come cambiare le coordinate (ad esempio di tipo bezier->B-spline o B-spline->nurbs, ecc) , effettuare operazioni di trasformazioni sui control point per ottenere effetti tipo skeletal animation. In questa fase avviene anche il passaggio da object space a world space
L’output dei VS, costituito dai control point della patch così elaborata, diventa input per il successivo stadio che, nelle DX11, è costituto dagli hull shader. A livello macroscopico, gli hull shader si possono schematizzare come segue
Gli HS prendono in input i valori dei control point definiti dai VS (che, ricordo, possono essere al massimo 32 valori di tipo vect4, corrispondenti a 128 scalari) e, da questi e dalle caratteristiche che defiìniscono la mesh, ricava un nuovo set di control point, sempre in numero massimo pari a 32. Nel fare questa operazione, gli HS possono decidere se variare il numero dei control point, aumentandolo o diminuendolo. In sintesi, gli HS non fanno altro che prendere i control point di una mesh e, in base alle loro posizioni ed alle indicazioni fornite dal coder sulle operazioni di tessellation, fornire degli ooutput tra cui, in particolare, una nuova serie di control point e, per ciascuno di essi, un tessellation factor.
I dati relativi ai nuovi control point finiscono in ingresso nei domain shader, mentre il tessellation factor che fornisce indicazioni su quanti sono i poligoni in cui ciscuna figura geometrica formante la mesh deve essere scoposta, diventa l’input del tessellator vero e proprio. Come detto, il tessellation factor si ricava dai valori dei control point in ingresso, dall’algoritmo di tessellation e dai relativi valori dei control point in uscita.
Il tessellator è schematizzabile, a livello macroscopico, come una sorta di blackbox che opera per patch formata da più “domini” il cui input è il tessellation factor ed il cui output è una patch in cui ciscuno dei domini è stato suddiviso in un determinato numero di poligoni. Se guardiamo all’interno di un tessellator, scopriamo due stadi, il primo dei quali lavora a fp32 e si occupa di decodificare e ottimizzare l’input per il successivo stadio che lavora a 16 bit fixed point e che si occupa di “fare il lavoro sporco” ovvero quello di tessellation vera e propria.
L’utilizzo della notazione in virgola fissa a 16 bit deriva dal dover soddisfare esigenze di velocità di esecuzione senza penalizzare la qualità delle operazioni svolte.
L’ultimo stadio, il domain shader, è schematizzabile come segue
e presenta in input sia i control point ricavati a livello di hull shader che l’output del tessellator. I domain shader svolgono i seguenti compiti: si occupano di confrontare il risultato della tessellation con le indicazioni fornite dai control, point generati dagli HS; trasformano, in base ai dati degli HS le serie di linee, triangoli e altri poligoni generati in fase di tessellation in nuovi vertici da renderizzare; controllano che i risultati ottenuti siano congruenti con il valore di LoD impostato; procedono, se necessario, ad effettuare altre operazioni, come, ad esempio, l’applicazione di displaced map. In questa fase, si sta operando a livello di view space.
Dopo questo breve cenno sul funzionamenot del tessellator, nel prossimo capitolo dedicato alle gpu seguiremo l’evoluzione di questo stadio della pipeline grafica e faremo alcune considerazioni di tipo quantitativo su risparmio di banda e memoria ed eventuale incremento prestazionale imputabile all’utilizzo delle operazioni di tessellation. Infine si farà un breve cenno all’implementazione delle stesse operazioni secondo ATi e nVIDIA.