Le versioni dei linguaggi disponibili nei linguaggi shaders per DirectX9 vanno dalle 1 alle 3. Le versioni 2_x sono qualcosa di per sé anomalo ed intermedio. Con buona probabilità le schede passeranno direttamente dalla versione 2.0 alla versione 3.0 e le versioni 2_x verranno scavalcate, un po’ come è stato per i pixel shader 1.4 ai tempi di DirectX8. Questo tutorial sarà un’aggiunta delle istruzioni dei vertex e pixel shader 2.0, cosa che Microsoft sta in effetti facendo: migliorare le versioni non modificando completamente il linguaggio ma aggiungendo nuove istruzioni (quanti di voi hanno rifiutato i pixel shader 1.4 che erano completamente diversi dai 1.3).
Vertex shader 2_X
Registri
Compare un nuovo registro: il predicate register
p0
Il registro è un vero booleano ossia finalmente è possibile utilizzare calcoli booleani. Nei vertex shader 2.0 se ricordate si potevano passare solo costanti booleane che non potevano essere quindi modificate dal codice. Il registro è solo 1 ma ha 4 canali (.x,.y,.z,.w) quindi ci sono ben 4 valori da utilizzare. Utilissimo se dovete ad esempio decidere come far comportare un vertice a seconda delle situazioni. I registri r# è aumentato di numero, da 12 a 32 a seconda della scheda video, così come i registri c#.
Controllo del flusso dinamico
Con l’introduzione del registro p è ora possibile utilizzare dei sistemi di controllo booleani. Alcuni funzionano anche senza.
if_comp src1,src2
Questo if permette di effettuare un confronto tra src1 e src2 basato sul suffisso che deve essere sostituito a comp. Se la condizione è vera sarà eseguito il codice al suo interno, altrimenti quello nel blocco else
I parametri di confronto sono
if_gt vero se il primo è maggiore del secondo
if_lt vero se il primo è minore del secondo
if_ge vero se il primo è maggiore o uguale al secondo
if_le vero se il primo è minore o uguale al secondo
if_eq vero se il primo è uguale al secondo
if_ne vero se il primo è diverso dal secondo al secondo
ad esempio
if_gt r0,r1
//qui il codice se r0 >r1
Else
//qui il codice per r0<=r1
Endif
break
Permette di uscire da un ciclo loop o rep
break_comp src1,src2
Permette di uscire da un ciclo loop o rep se si verifica la condizione. Il sistema è diciamo l’unione tra un if ed un break. I suffissi comp sono gli stessi per if.
setp_comp dst, src0, src1
Serve per assegnare un valore ad un registro predicate. Funziona anch’esso come if ma il risultato finisce nel registro p0. La comparazione è divisa per ogni canale (XYZW). Potete selezionare quali canali usare
Ad esempio
setp_gt p0,r0,r2 //p0 = (r0>r2)
if pred
Un if che usa un registro p. Dovete specificare quale canale usare
Es
if p0.x
else
endif
callnz label, pred
Chiama una subroutine se il registro predicate è true
Es
callnz l1, p0.x
breakp pred
Permette di uscire da un ciclo loop o rep se il registro p0 è true
Es
breakp p0.x
Cicli loop e rep
Ora permettono di eseguire cicli uno nell’altro. La profondità dipende dalla scheda e varia da 1 (nessuno ciclo interno) a 4. Può essere letto il valore tramite enumerazione in vs20caps nel valore staticflowcontroldepth. Lo stesso valore è considerato anche per le subroutine quindi potete chiamare una subroutine in un’altra.
A parte quelle citate non ci sono nuove istruzioni ed il funzionamento rimane invariato.
Pixel shader 2_x
I pixel shader 2_x hanno molte nuove caratteristiche in più.
Registri
Ci sono nuovi registri nel pixel shader che comunque avete già incontrato nei vertex shader 2.0.
Registro
|
Nome
|
Numero
|
Accesso
|
Descrizione
|
b#
|
Costante booleana
|
16
|
R
|
Registro costante booleana passata direttamente tramite device.
|
i#
|
Costante intera
|
16
|
R
|
Registro costante intero passato direttamente da device
|
aL
|
Registro contatore
|
1
|
R
|
Usato per i cicli
|
p#
|
Registro predicate
|
1
|
RW
|
Registro booleano modificabile tramite istruzioni di controllo di flusso
|
Controllo del flusso
Anche i pixel shader hanno blocchi if e loop
if src1
Blocco if che viene eseguito se il source1 è true. Legge solo i registri b
Es
if b0
else
endif
if_comp src1,src2
Questo if permette di effettuare un confronto tra src1 e src2 basato sul suffisso che deve essere sostituito a comp. Se la condizione è vera sarà eseguito il codice al suo interno, altrimenti quello nel blocco else
I parametri di confronto sono
if_gt vero se il primo è maggiore del secondo
if_lt vero se il primo è minore del secondo
if_ge vero se il primo è maggiore o uguale al secondo
if_le vero se il primo è minore o uguale al secondo
if_eq vero se il primo è uguale al secondo
if_ne vero se il primo è diverso dal secondo al secondo
ad esempio
if_gt r0,r1
//qui il codice se r0 >r1
Else
//qui il codice per r0<=r1
Endif
break
Permette di uscire da un ciclo loop o rep
break_comp src1,src2
Permette di uscire da un ciclo loop o rep se si verifica la condizione. Il sistema è diciamo l’unione tra un if ed un break. I suffissi comp sono gli stessi per if.
setp_comp dst, src0, src1
Serve per assegnare un valore ad un registro predicate. Funziona anch’esso come if ma il risultato finisce nel registro p0. La comparazione è divisa per ogni canale (XYZW). Potete selezionare quali canali usare
Ad esempio
setp_gt p0,r0,r2 //p0 = (r0>r2)
if pred
Un if che usa un registro p. Dovete specificare quale canale usare
Es
if p0.x
else
endif
callnz label, pred
Chiama una subroutine se il registro predicate è true
Es
callnz l1, p0.x
breakp pred
Permette di uscire da un ciclo loop o rep se il registro p0 è true
Es
breakp p0.x
Ciclo loop
I cicli loop sono l’equivalente dei for nei linguaggi di alto livello. Utilizzano come valore di riferimento i registri costanti interi i#.
loop aL, i0
//esegue il ciclo da 0 al valore i0
endloop
Il registro aL può essere usato per accedere a devi valori in forma matriciale. Es
c0[aL] significa leggere il registro c3 se aL è uguale a 3 ossia si sta al terzo passaggio del ciclo.
rep i0
//esegue il ciclo i0 volte
endrep
Come il ciclo loop ma senza il contatore aL e quindi più leggero.
Subroutine
Si possono scrivere ora anche subroutine. Le subroutine vanno scritte dopo il codice principale che deve ora essere chiuso dall’istruzione ret. Una subroutine si trova compresa fra l’istruzione label l# e l’istruzione ret. Ad esempio
ps_2_x
//istruzioni
ret
//subrotine
label l0
ret
Il valore l# è una label (in 2_x da 1 a 2047) che definiscono una particolare subroutine. I registri sono in comune tra tutte le sub e la main. Le sub sono molto utili per dividere il codice e chiamarlo solo quando serve. Per chiamare una sub si utilizzano queste istruzioni
call l#
callnz l#,b#
callnz l#,p0
La prima chiama direttamente la sub, la seconda se il registro booleano passato è true mentre la terza se il registro predicate è true (usate al solito p0.x o .y o .z o .w). Al termine della subroutine si ritorna all’istruzione successiva a call.
Istruzioni
Ci sono nuove istruzioni che si aggiungono alle precedenti.
Defb
Dichiara una costante booleana all’interno del codice
Es
Defb b0,true
Defi
Dichiara una costante intera all’interno del codice
es
defi i0,1,0,-2,3
dsx dest,src1
dsy dest,src1
Queste istruzioni sono chiamate di gradiente. In pratica passando come source il colore della texture questa (a seconda della scheda) restituirà la differenza di colore tra i vicini. Questo risulta utile perché può essere usato il risultato per eseguire l’antialiasing a livello di texture. Questo perché il colore restituito sarà proprio il difetto della seghettatura permettendo quindi di correggerla. Il primo restituisce il gradiente sulla X, il secondo sulla Y. L’algoritmo usato dipende dall’hardware quindi il risultato varia da scheda a scheda.
texldd, dest, src0, src1, src2, src3
src0:coordinate texture
src1: sampler
src2: gradiente X
src3: gradiente Y