NV30, R300 und DirectX 9

Pixel Shader

Im folgenden Teil unseres Artikels befassen wir uns mit den neuen Pixel Shader Modellen, welche in DirectX 9.0 enthalten sind. Den Modellen (1.1 - 1.4 gibt es seit DirectX 8.1) liegt folgender Gedanke zu Grunde, da sie sich in ihrer Leistungsfähigkeit unterscheiden:
  • PS2.0 gilt als Basismodell, welches minimal benötigt wird, um als DirectX 9.0 VPU zu gelten.
  • PS2.0 Extended ist eigentlich kein neues Modell, es ist lediglich eine Erweiterung des 2.0'er Modells, welches variabel durch CAP-Bits (Bitfelder, welche je nach Fähigkeiten der Hardware gesetzt. werden) zwischen dem PS2.0 und dem PS3.0 Modell liegt.
  • PS3.0 ist als High-End Modell gedacht um zukünftiger Hardware (und Herstellern) ein Basismodell für ihre VPU's zu bieten, es könnte in DirectX 10.0 als Basismodell dienen.
  • Daneben gibt es noch die Modelle PS 2_sw und PS 3_sw, welche nur von der CPU ausgeführt werden können und für Entwickler gedacht sind .
Fähigkeiten


DX8.1 PS1.4
DX9 PS2.0 Extended
/ PS 2_sw
DX9 PS3.0
/ PS 3_sw
Version 1_1 1_4
2_0 2_x (mit CAP Bits)
3_0
2_0 2_x (mit CAP Bits)
max. Instruktionen (nicht Befehle!)
pro Durchlauf
8 28
variabel,
je nach Hardware
variabel,
je nach Hardware
variabel,
je nach Hardware
variabel,
je nach Hardware
variabel,
je nach Hardware
max. Instruktionen in einem Block 8 14 pro Phase
2 Phasen möglich
96 96-512
512-32768
96 ?
Fließkomma-Genauigkeit - -
ja ja
ja
ja ja
statische Flußsteuerung - -
- (Tiefe 1-4)
(Tiefe 1-4)
ja ja
dynamische Flußsteuerung - -
- (Tiefe 0-24)
(Tiefe 0-24)
- ja
Predication - -
- ja
ja
- ja
Arbitrary Swizzle - -
- ja
ja
- ja ?
Gradient Instructions
-
-
-
ja
ja
-
ja
No Dependent Read Limit
nicht frei möglich, nur in Verbindung mit komplexen Operationen
1 pro r# Register,
nur in der 2. Phase
jeweils max. 3 zusammengehörige
ja
ja
jeweils max. 3 zusammengehörige ja ?
No Texture Instruction Limit
-
-
32 ja
ja
32
ja ?
Loops
-
-
-
-
ja
-
-

Version
Gibt die Pixel Shader Version an. Aus der Version 2_0 wird 2_x, falls eines oder mehrere der folgenden CAP-Bits gesetzt bzw. die Werte vom 2_0'er Standard abweichen:

D3DPS20CAPS_ARBITRARYSWIZZLE
D3DPS20CAPS_GRADIENTINSTRUCTIONS
D3DPS20CAPS_PREDICATION
D3DPS20CAPS_NODEPENDENTREADLIMIT
D3DPS20CAPS_NOTEXINSTRUCTIONLIMIT
DynamicFlowControlDepth
NumTemps
StaticFlowControlDepth
NumInstructionSlots

max. Instruktionen (nicht Befehle!) pro Durchlauf
Gibt an, wie lang ein Pixel Shader Programm sein darf. Durch Schleifen und Sub-Routinen kann ein PS-Programm deutlich länger werden, als die max. 96 Instruktionen in einem Block. Manche Befehle benötigen mehrere Instruktionen, weshalb man Instruktion und Befehl nicht gleichsetzen darf.
Fließkomma-Genauigkeit
Der Pixel Shader arbeiten ab der Version 2.0 mit Fließkomma-Genauigkeit. Diese variiert zwischen min. 16 Bit (R300 - 24 Bit, NV30 - 32 Bit) für Farbwerte und min. 24 Bit (R300 - 24 Bit, NV30 - 32 Bit) für alle anderen Daten.
statische Flußsteuerung (die Tiefe gibt an, wie tief ich die Flußsteuerung verschalten kann!)
Statische Flußsteuerung ist rech einfach erklärt:

PS_2_x
defb b3, TRUE

if b3
// wenn b3 TRUE ist, dann gehe hier hin

else
// sonst mache hier weiter

endif


Jedoch sind hier keine komplexen Vergleich im IF-Block möglich, lediglich die "Constant Boolean " Register, welche pro Register nur die Werte 0 oder 1 haben können, dürfen nach dem IF stehen. Weitere Befehle sind call und rep. Ich habe also bei der statischen Flußsteuerung lediglich die "Constant Boolean" Register mit denen ich Vergleiche anstellen kann. Dies ist für komplexe Aufgaben sehr hinderlich, da man nur über Umwege (wenn überhaupt) kompliziertere Vergleichen hinbekommt.
dynamische Flußsteuerung (die Tiefe gibt an, wie tief ich die Flußsteuerung verschalten kann!)
Dynamische Flußsteuerung ist viel leistungsfähiger als ihr statische Vertreter.

PS_2_x

if_lt r3.x, r4.y
// wenn r3.x kleiner ist als r4.y, dann gehe hier hin

else
// sonst mache hier weiter

endif


Hier habe ich wesentlich mehr Möglichkeiten z.B. den IF-Block zu beginnen (_gt "größer", _lt "kleiner", _ge "größer / gleich", _le "kleiner / gleich", _eq "gleich", _ne "ungleich") , ich bin nicht an boolsche Register gebunden. Ebenso hilft die dyn. Flußsteuerung bei Schleifen, welche ich durch feinere Vergleichsmöglichkeiten z.B. "break_lt r3.x, r4.y" - bricht aus momentaner Schleife aus, wenn r3.x < r4.y - beenden kann.

Predication(als wahr behaupten)
Predication ist eine feine Sache. Im Grunde etwas ähnliches wie eine Flußsteuerung, jedoch ohne Sprünge (zumindest aus der Sicht des Programmierers). Erstmal benötigen wir eine neue Art von Register, das "Predicate Register". Im PS 2_x Modell gibt es davon 1 Stück, p0. Das Register ist ein 4D Vektor mit den 4 Komponenten xyzw, aber jede Komponente (also x, y, z, w) ist ein boolsches Register, kann also nur 0(false) oder 1(true) enthalten. Es Spielt übrigens keine Rolle, ob man bei den Registern x, y, z, w (vom Vertex Shader) oder r, g, b, a (Pixel Shader) verwendet.

PS_2_x

setp_le p0, r0, r2.z


bedeuted folgendes:
IF
(r0.x <= r2.z) THEN p0.x = true ELSE false
IF (r0.y <= r2.z) THEN p0.x = true ELSE false
IF (r0.z <= r2.z) THEN p0.x = true ELSE false
IF (r0.w <= r2.z) THEN p0.x:= true ELSE false

Andere Vergleiche sind auch möglich. So jetzt haben wir unser p0 Register gesetzt. Was machen wir nun damit?
Wir können nun p0 dazu verwenden, bestimmte Befehle auszuführen, falls das Predicate Register für die entsprechende Komponente eine 1 enthält.

p0.x=true
p0.y=false
p0.z=false
p0.w=true

(p0) add r4, r5, r6



Ohne (p0) würde das PS-Programm folgendes tun:
r4.x = r5.x + r6.x
r4.y = r5.y + r6.y
r4.z = r5.z + r6.z
r4.w = r5.w + r6.w

Mit dem (p0) Befehl jedoch, rechnet er:
IF (p0.x = true) THEN  r4.x = r5.x + r6.x
IF (p0.y = true) THEN  r4.y = r5.y + r6.y
IF (p0.z = true) THEN  r4.z = r5.z + r6.z
IF (p0.w = true) THEN r4.w = r5.w + r6.w

Es würden sich also r4.y und r4.z nicht verändern, da p0.y und p0.z nicht "true" enthalten.

Arbitrary Swizzle
Dies erkläre ich weiter unten, in der Modifikations- und Maskierbefehle Sektion.

Gradient Instructions
Mittels Gradient Instructions kann man im Pixel Shader Informationen erhalten, in welchem Winkel sich Daten im Rendertarget (Framebuffer oder Texture) befinden. Dies wird für anisotrophe Berechnungen benötigt. Bisher hatte der Pixel Shader "keine Ahnung" in welchem Winkel seine erstellten Pixeldaten später aufs Polygon kommen.
No Dependent Read Limit
Dependent Read bezeichnet man die Fähigkeit, durch den Registerinhalt Texturedaten zu adressieren. Somit kann man z.B. Texturen als Tabellen mit Werten verwenden und diese dann gezielt verwenden. Die Pixel Shader der Version 2.0 können hier nur 3 zusammengehörige ausführen.

PS_2_0

...
def c0, 1.0f, 1.3f, 1.2f, 1.0f // definiere Konstante c0 (4D-Vector)

texld  r1, t0, s1;     // lade Texturedaten(von Position 0,0) aus der Texture 0 mittels Texturesampler 1 in Input Register 1
add r1, r1, c0         // verändere Werte in r1          
texld  r2, r1, s3;     // Dependent Read/Abhängiges Lesen!!!
                       // lade Texturedaten(von Position r1.x, r1.y) aus der Texture 1 mittels Texturesampler 3 in Input Register 2

texld  r2, r2, s3;     // Dependent Read/Abhängiges Lesen mit veränderten Werte in r2 !!!
                       // lade Texturedaten(von Position r2.x, r2.y) aus der Texture 2 mittels Texturesampler 3 in Input Register 2

texld  r2, r2, s3;     // Dependent Read/Abhängiges Lesen mit veränderten Werte in r2 !!!
                       // lade Texturedaten(von Position r2.x, r2.y) aus der Texture 2 mittels Texturesampler 3 in Input Register 2
texld  r2, r2, s3;     // nicht mehr moglich mit den Pixel Shadern der Version 2.0


No Texture Instruction Limit
Pixel Shader der Version 2.0 haben hier ein Limit von 32. Ob mehr heute Sinn machen, kann ich nicht beurteilen, aber auf jeden Fall ein Weg zum völlig frei programmierbaren Pixel Shader.

PS_2_0

texld  r1, t0, s1;     // lade Texture 0 mittels Texturesampler 1 in Input Register 1
Loops
Loops / Schleifen wird es erst mit den Pixel Shadern der Version 3.0 geben, somit haben dann Vertex und Pixel Shader 3.0 den gleichen Umfang an statischer und dynamischer Flußsteuerung.

Register

DX8.1 PS1.4
R300 PS
NV30 PS
Input Register - Führen Daten in den Pixel Shader
Input / Color r 2 (4D Vektor) 2 (4D Vektor)
2 (4D Vektor) 2 (4D Vektor) 10 (4D Vektor) 2 (4D Vektor)
2 (4D Vektor)
Temp   r/w 2 (4D Vektor) 6 (4D Vektor)
12 (4D Vektor) 12-32 (4D Vektor) 12-32 (4D Vektor) 12 (4D Vektor)
12 (4D Vektor)
Constant Float r -
min. 12-32 (4D Vektor) min. 12-32 (4D Vektor) min. 12-224 (4D Vektor) 12 (4D Vektor)
32 (4D Vektor) ?
Constant Integer   r 8 (4D Vektor) 8 (4D Vektor)
- 16 (4D Vektor) 16 (4D Vektor) 16 (4D Vektor)
16 (4D Vektor) ?
Constant Boolean r - -
16 Bits 16 Bits 16 Bits 16 Bits
16 Bits
Face r - -
- - 1 (Skalar) -
-
Loop Counter u - -
- - 1 (Skalar) -
-
Predicate r - -
- 1 (4D Vektor) 1 (4D Vektor) -
?
Sampler
r
-
-
16 (4D Vektor)
16 (4D Vektor)
16 (4D Vektor)
16 (4D Vektor)
16 (4D Vektor)
Input Texture Coordinate
r
4 (4D Vektor)
6 (4D Vektor)
8 (4D Vektor)
8 (4D Vektor)
-
8 (4D Vektor)
8 (4D Vektor)
Position
r
-
-
-
-
1 (4D Vektor)
-
-
Anmerkungen








Color w (Skalar) (Skalar)
4 (Skalar) 4 (Skalar) 4 (Skalar) 4 (Skalar)
4 (Skalar)
Depth w - (Skalar)
1 (Skalar) 1 (Skalar) 1 (Skalar) 1 (Skalar)
1 (Skalar)
Anmerkungen










Bei den Pixel Shadern <2.0 habe alle Register entweder ein Integer oder Festpunkt Format. Ab 2.0 sind es Fließkomma-Register, bis auf Input/Color Register <3.0. Die Unterschiede zwischen der Version 2.0 und 3.0 sind doch noch recht groß.

Input / Color Register (v#)
"Input / Color Register" müssen vor Begin des Pixel Shader Programms deklariert werden. Bei Pixel Shadern <2.0 war dies nicht nötig. Es wird ihre Verwendung sowie der entsprechende Name festgelegt. Im Vertex Shader Programm kann nur lesend auf die "Input Register" zugegriffen werden.

PS_2_0
dcl v0.xy     // ich verwende nur die x und y Komponenten des v0 Registers

Temp Register (r[n])
"Temp Register" sind Allzweckregister. Auf sie kann man lesend und schreibend einwirken.

PS_1_1
mov r0.x, v0.x

Constant Register - Float (c[n]), Integer (i#) und Boolean (b#)
"Constant Register" gibt es seit dem Vertex Shader 2.0 drei Typen. Einmal Fleißkomma-Zahlen und dann noch die Integer/Bool Versionen, welche jedoch nur noch für Schleifen und statische Flußsteuerung benötigt werden.

PS_2_0
def c0, 1.0f, -1.0f, 0.5f, -0.5f
    // fülle die Konstante c0 mit Werten

Face Register (vFace)
Wenn der Wert im Face Register <=0 ist, dann sehen wir den Pixel von der Rückseite.

ps_3_0

dcl vFace

def c0, 0.0f, -1.0f, 0.5f, -0.5f // ein paar nützliche Konstanten
def c1, 0.0f, 0.0f, 1.0f, 0.0f   // blau
def c2, 1.0f, 0.0f, 0.0f, 0.0f   // rot

if_gt c0.x, vFace                // wenn 0>vFace, dann
    mov oC0, c1                  // sehen wir den Pixel von hinten, färbe ihn blaue
else
    mov oC0, c2                  // sonst rot
endif

Loop Counter Register (aL)
Das "Loop Counter Register" wird automatisch um 1 erhöht, wenn der Pixel Shader in einen "loop" Block eintritt.
Predicate Register (p0)
Das "Predicate Register" wird, wie im obigen Beispiel zu sehen war, zur bedingungsabhängigen Ausführung von Befehlen verwendet.

Sampler Register (s#)
Mit Hilfe des "Sampler Register" können Texturedaten in Pixel Shader geladen werden. Sie Beispiel weiter oben ( Texture Instruction Limit ).

Output Register (oC0, oC1, oC2, oC3, oDepth)
Ohne die "Output Register" würden die fertigen Werte nie auf den Pixel gelangen. Da es seit DirectX 9 vier Rendertargets gibt, kann der Pixel Shader auch vier Werte rausschreiben. Zusätzlich kann die Tiefen/Stencilinformation des Pixels verändert werden.

Modifikatoren

anwendbar auf
DX8.1 PS1.4
R300 PS
NV30 PS
Multiply by 2
_x2
Befehl
ja
ja
-
-
-
2_0

2_x

Multiply by 4
_x4
Befehl
ja
ja
-
-
-
Multiply by 8
_x8
Befehl
-
ja
-
-
-
Divide by 2
_d2
Befehl
ja
ja
-
-
-
Divide by 4
_d4
Befehl
-
ja
-
-
-
Divide by 8
_d8
Befehl
-
ja
-
-
-
Bias
_bias
Quellregister
ja
ja
-
-
-
Invert
1 -
Quellregister
ja
ja
-
-
-
Scale by 2
_x2
Quellregister
-
ja
-
-
-
Signed Scaling
_bx2
Quellregister
ja
ja
-
-
-

Absolute Value _abs Quellregister
- -
- - ja -
ja
Negate   - Quellregister
ja ja
ja ja ja ja
ja
Centroid
_centroid
nur dcl Befehl
-
-
-
-
ja
-
-
Saturate  _sat Befehl
- ja
ja ja ja
ja
ja
Partial Precision
_pp
Befehl
-
-
ja
ja
ja
ja
ja
Arbitrary Swizzle  .xyzw or .rgba Quellregister
- - - ja ja -
ja
Replicate Swizzle
.xyzw or .rgba
Quellregister
ja
ja
ja
ja
ja
ja
ja
Write Mask .xyzw or .rgba
Zielregister
ja ja
ja ja ja ja
ja


Modifkatoren haben den großen Vorteil, dass sie nicht als extra Instruktionen zählen. Somit können sie unbeschwert angewendet werden, wirken sich aber nicht auf die Anzahl der Instruktionen aus. Die Anzahl der Modifikatoren wurde ab Version 2.0 stark beschränkt, was sicher mit dem größeren Wertebereich bzw. der höheren Genauigkeit zu tun hat. Somit sind die Skalierungsmodifikatoren der älteren Pixel Shader nicht mehr nötig. Ich werde hier auch nur die Modifikatoren der Pixel Shader ab Version 2.0 erläutern.

Absolute Value
Hiermit wird der absolute Wert eines Registers gebildet.

PS_3_0
add rDest, rSrc0,
rSrc1_abs
Negate
Hiermit wird der Inhalt des Quellregisters negiert.

PS_2_0
add rDest, - rSrc0, rSrc1

Centroid
Centroid wird bei aktiviertem MultiSampling benötigt, da dort der Pixel Shader nur pro Pixel arbeitet, nicht jedoch pro Sub-Pixel wie bei SuperSampling. Da der Pixel Shader nur im Zentrum (blaue Punkte) des Pixel sampled, würde es bei folgendem Polygon zu Fehlern führen, da die Farbwerte außerhalb des Pixelzentrums liegen. Wo genau er nun sampled ist den Hardwareherstellern überlassen.
Centroid - problem

PS_3_0
dcl_centroid_color v0

Saturate
Saturate begrenzt das Resultat einer Berechnung auf den Wertebereich [0, 1]. Somit gibt es keine Überläufe.

add_sat rDest, rSrc0, 
rSrc1
Replicate Swizzle
Mit Replicate Swizzle kann man bestimmen, welche Quellregisterkomponenten in ein Zielregister geschrieben werden.

add rDest, rSrc0 , rSrc1.xxyy    // addiert zu den xy Komponenten von rSrc0 jeweils nur x von rSrc1
                                 // und zu den zw Komponenten von rSrc0 jeweils nur y von rSrc1
Arbitrary Swizzle
Arbitrary Swizzle bezeichnet die Möglichkeit, dass die Komponenten (x, y, z, w) beliebig vertauscht werden können.

add rDest, rSrc0, rSrc1.yxzw
      // vertauscht x und y beim addieren
Write Mask
Eine "Write Mask" dient dazu, festzulegen, welche Zielregisterkomponenten von einer Operation verändert werden dürfen.

add rDest.xw, rSrc0, rSrc1     // nur die xw Komponenten werden durch die Berechnung verändert


Befehle

DX 9 PS1.4
 
R300 PS
NV30 PS
Setup def, ps def, ps

def, ps, dcl,
dcl_textureType

-
defi, defb,
dcl_usage anstatt dcl
2_0 2_x
Phase
-
phase

-
-
-
Texture
tex, texbem, texbeml, texcoord,
texkill, texm3x2pad, texm3x2tex, texm3x3pad, 
texm3x3spec,  texm3x3tex,
texm3x3vspec, texreg2ar, texreg2gb
texcrd, texdepth, texkill, texld

texkill, texld, texldb, texldp
texldd
texldl
Arithmetic   add, cnd, dp3, lrp, mad,
mov, mul, nop, sub
add, bem, cmp, cnd, dp3,
dp4, lrp, mad, mov,
mul, nop, sub


add, cmp, dp2add,
dp3, dp4, mad, mov,
mul, nop, rcp,
rsq, sub
dsx, dsy
-
Macro-Ops  - -

abs, crs, exp, frc, log, lrp,
m3x2, m3x3, m3x4, m4x3,
m4x4, max, min,
nrm, pow, sincos
-
-
Flow-Control  - -

- break, break_comp, break_pred,
call, callnz, callnz_pred, else,
endif, endrep, if, if_comp, if_pred,
label, rep, ret, setp
loop, endloop


Die Pixel Shader der Version 1.1 / 1.4 haben eine stark unterschiedlichen Befehlssatz, deshalb habe ich hier jeweis den kompletten aufgeführt. Version 1.2 und 1.3 bieten zusätzlich noch ein paar Befehle, jedoch soll es hier um die Versionen 2.0 und höher gehen. Ab der Version 2.0 gibt es einen einheitlichen Befehlssatz. Als Basismodell dient der Pixel Shader 2.0. Mit jeder weiteren Pixel Shader Version kommen neue Befehle hinzu, die Befehle der Vorgängerversionen (ab 2.0) bleiben erhalten, deshalb habe ich nur die neuen Befehle in der Tabelle aufgeführt.

Setup
Zum Setup gehören Befehle, welche die Version des verwendeten Pixel Shaders festlegen, sowie die Zuordnung zwischen den Input Registern und deren Verwendung.

ps_2_0                // legt Version 2_0 des PS fest
dcl v0                // zeigt dem PS, dass man das v0 Register verwenden möchte            
Phase
Phase wird nur im 1.4'er Pixel Shader verwendet, da dort nur in Phase 2 bestimmte Operationen.

ps_1_4                // legt Version 1_4 des PS fest
...                   // irgendwelche Operatioen
phase
                // schaltet auf Phase 2 um
...                   // irgendwelche Operatioen
Texture
Mittels der Texture Befehler werde Texturen in den Pixel Shader geladen.

ps_2_0
                // legt Version 2_0 fest

dcl t0
                // zeigt dem PS, dass man das t0 Register verwenden möchte
dcl s1                // zeigt dem PS, dass man das s1 Register verwenden möchte

texld r0, t0, s1      // lädt die Texture 0 über den Sampler 1 in r0

Arithmetic
Hier werden einfache mathematische Operationen zur Verfügung gestellt.

ps_2_0                                 // legt Version 2_0 fest
def c0, 1.30f, 1.50f, 1.60f, 1.70f    / / definiert die Variable c0

mul  r0, r0, c0.x                     // multipliziert r0 mit c0.x (1.30f)

Macro-Ops
Hier werden komplexere mathematische Operationen zur Verfügung gestellt. Man könnte diese auch umgehen und selbst per arithmetischer Operationen nachbilden.

ps_2_0                 // legt Version 2_0 fest
abs  r0, r0           // berechnet den absoluten Wert von r0

Flow-Control
Hier werden alle Befehle der statischen und dynamischen Flußsteuerung zusammengefasst. Diese gibt es erst seit dem PS 2_x.

ps_2_x

if_lt r3.x, r4.y
// wenn r3.x kleiner ist als r4.y, dann gehe hier hin

else
// sonst mache hier weiter

endif


Zusammenfassend kann man sagen, dass die Pixel Shader ab Version 2.0 wesentlich einfacher als die Versionen <=1.4 zu programmieren sind. Ein Problem, genau wie bei den Vertex Shadern, gibt es auch hier: Alles zwischen Version 2.0 und 3.0 ist sehr "weich" definiert. Hier muss der Programmierer noch mehr aufpassen, als bei den Vertex Shadern, weil es hier mehr Fähigkeiten gibt, welche unterstützt werden könnten oder ebne auch nicht. Deshalb wird man als Spielerwahrscheinlich nie ein Spiel zu Gesicht bekommen, welches 2_x Shader verwendet, da einfach zu wenige Platformen dies unterstützen.


<< vorherige Seite nächste Seite >>