NV30, R300 und DirectX 9

Vertex Shader

Im folgenden Teil unseres Artikels befassen wir uns mit den neuen Vertex Shader Modellen, welche in DirectX 9.0 enthalten sind. Den Modellen (1.1 gibt es seit DirectX 8.0) liegt folgender Gedanke zu Grunde, da sie sich in ihrer Leistungsfähigkeit unterscheiden:
  • VS2.0 gilt als Basismodell, welches minimal benötigt wird, um als DirectX 9.0 VPU zu gelten.
  • VS2.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 VS2.0 und dem VS3.0 Modell liegt.
  • VS3.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 VS 2_sw und VS 3_sw, welche nur von der CPU ausgeführt werden können und für Entwickler gedacht sind.
Fähigkeiten

DX9 VS2.0 Extended
VS 2_sw
DX9 VS3.0
/ VS 3_sw

Version 1_1 2_0 2_x
3_0
2_0 2_x
max. Instruktionen (nicht Befehle!)
pro Durchlauf
128 65280 65280 65280
65280 65536
max. Instruktionen in einem Block 128 256 256
512-32768
256 256
Fließkomma-Genauigkeit ja ja ja
ja
ja ja
statische Flußsteuerung - (Tiefe 1) (Tiefe 1-4)
(Tiefe 1-4)
(Tiefe 1) (Tiefe ?)
dynamische Flußsteuerung - - (Tiefe 0-24)
(Tiefe 0-24)
- (Tiefe ?)
Predication - - ja
ja
- ?
Texture Lookup - - -
ja
- ?
Vertex Stream Frequency
-
-
-
ja
-
-

Version
Gibt die Vertex 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 Standard abweichen:

D3DVS20CAPS_PREDICATION
DynamicFlowControlDepth
NumTemps
StaticFlowControlDepth

Instruktionen (nicht Befehle!) pro Durchlauf
Gibt an, wie lang ein Vertex Shader Programm sein darf. Durch Schleifen und Sub-Routinen kann ein VS-Programm deutlich länger werden, als die max. 256 Instruktionen in einem Block. Manche Befehle benötigen mehrere Instruktionen, weshalb man Instruktion und Befehl nicht gleichsetzen darf.
Fließkomma-Genauigkeit
Der Vertex Shader arbeitet seit Version 1.0 mit 32 Bit Fließkomma-Genauigkeit.
statische Flußsteuerung (die Tiefe gibt an, wie tief ich die Flußsteuerung verschalten kann!)
Statische Flußsteuerung ist rech einfach erklärt:

vs_2_0
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 z.B. loop, call, 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 hinbekomme.
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.

vs_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. Eine if_p0 Anweisung ist ebenfalls möglich, wobei p0 das Predicationsregister ist.
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 VS3.0 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.

vs_3_0

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 VS-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.


Texture Lookup
Mittel "Texture Lookup" können Texturedaten(4 Stück) im Vertex Shader geladen werden. Somit könnte man z.B. ein eigenes Displacement Mapping realisieren oder andere Effekte.

SetSamplerState(D3DVERTEXTEXTURESAMPLER[0...3], ...);
SetTexture(D3DVERTEXTEXTURESAMPLER[0...3], ...);

vs_3_0
dcl_2d s0 // 2D Texture
...
mov rSrcTexcoord .w, MipMap-Level
texld rDest, rSrcTexcoord, s0
Vertex Stream Frequency
Bei den bisherigen Vertex Shadern (<3.0) und der festen T&L Einheit wird der Vertex Shader einmal pro Vertex ausgeführt. Die "Input Register" wurden bei jedem Aufruf mit neuen Vertexdaten von einem Vertex Stream versorgt. Mit einer Änderung der Frequenz kann man nun z.B. erreichen, dass ein bestimmter Teil der "Input Register" mit einer niedrigeren Frequenz neue Daten bekommen. Stellt man die Frequenz z.B. auf 2, dann bekommen die "Input Register" (welche dem Stream zugeorndet sind) lediglich bei jedem zweiten verarbeitetem Eckpuntk neue Daten.


Register

R300 VS
NV30 VS
Input Register - Führen Daten in den Vertex Shader
Input  r 16 ( 4D Vektor) 16 (4D Vektor) 16 (4D Vektor) 16 (4D Vektor) 16 (4D Vektor)
16 (4D Vektor)
Temp   r/w 12 (4D Vektor) 12 (4D Vektor) 12-32 (4D Vektor) 12-32 (4D Vektor) 16 (4D Vektor)
16 (4D Vektor)
Constant Float r min. 96 (4D Vektor) min. 256 (4D Vektor) min. 256 (4D Vektor) min. 256 (4D Vektor) 256 (4D Vektor)
256 (4D Vektor)
Constant Integer   r - 16 (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
Address u/w 1 (Skalar) 1 (4D Vektor) 1 (4D Vektor) 1 (4D Vektor) 1 (4D Vektor)
2 (4D Vektor)?
Loop Counter u - 1 (Skalar) 1 (Skalar) 1 (Skalar) 1 (Skalar)
1 (Skalar)
Predicate r/w - - - 1 (4D Vektor) -
?
Sampler
r
-
4 (Skalar)
4 (Skalar)
4 (Skalar)
4 (Skalar)
4 (Skalar)
Anmerkungen




In allen VS-Modellen <3.0 konnten nur die "Constant Float Register - c[]" indexiert werden. Bei VS3.0 können durch das "Loop Counter Register - aL" Register auch die "Input Register - v[]" und die "Output - o[]" Register.



Position w (4D Vektor) 1 (4D Vektor) 1 (4D Vektor) - 1 (4D Vektor)
1 (4D Vektor)
Point Size w (Skalar) 1 (Skalar) 1 (Skalar) - 1 (Skalar)
1 (Skalar)
Fog Coordinate w (Skalar) 1 (Skalar) 1 (Skalar) - 1 (Skalar)
1 (Skalar)
Texture Coordinate w (4D Vektor) (4D Vektor) 8 (4D Vektor) - 8 (4D Vektor)
8 (4D Vektor)
Diffuse/Specular Color w 2 (4D Vektor) (4D Vektor) 2 (4D Vektor) - 2 (4D Vektor)
2 (4D Vektor)
Output
w
-
-
-
12 (4D Vektor)
-
-
Anmerkungen




Das VS3.0 Modell beseitzt keine starren Vorgaben mehr, bei der Registerzuornung.

Der NV30 besitzt noch 2 (4D Vektor) "Back-Facing Color Register, sowie 6 Clip-Distance Register(Skalar)".

Ob diese unter DirectX 9.0 nutzbar sind ist noch unklar!



Bei den Registern unterscheiden sich die Vertex Shader der Version 2.0 / 2.0 Extended kaum noch. Lediglich die Anzahl der "Temp Register" ändert sich. Gegenüber der Vertex Shader Version 1.1 sind die Unterschiede schon größer. Einzig die Vertex Shader ab Version 2.0 Extended / 3.0 besitzen das "Predicate Register", sowie die 3.0'er auch universelle "Output" Register.

Input Register (v#)
"Input Register" müssen zu Begin des Vertex Shader Programms deklariert werden. Es wird ihre Verwendung sowie die Zuordnung festgelegt. Im Vertex Shader Programm kann nur lesend auf die "Input Register" zugegriffen werden.

vs_1_1
dcl_position0 v0
dcl_normal0 v3
dcl_texcoord0 v6

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

vs_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 den Typ "float", welchen es bereits in den Vorgängerversionen 1.0 und 1.1 gab, sowie "integer" und "boolean". Die "float" Version wird für allgemeine Berechnungen genutzt. Die "integer" und "boolean" hingegen für statische/dynamische Flußsteuerung und als Schleifenzähler.
Address Register (a0)
"Address Register" werden zur Adressierung verwendet. Es können lediglich die "Constant Float Register" indirekt adressiert werden. Hier ein kurzes Beispiel:

vs_1_1
mov r0.x, c[a0.x + n].x // indirekt
mov r0.x, c[n].x // direkt
Loop Counter Register (aL)
Das "Loop Counter Register" wird automatisch um 1 erhöht, wenn der Vertex 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 im Vertex Shader verwendet werden. Sie Beispiel weiter oben.


Output Register (oPos, oPts, oFog, oT#, oD# / o#)
Ohne die "Output Register" würden die fertigen Eckpunkte niemals aus dem Vertex Shader heraus kommen. Beim VS3.0 gibt es nur noch 12 o# Register. Hier ein kurzes (unvollständiges) Beispiel:

// Defenition eines Eckpunktes
struct CUSTOMVERTEX_POS_NORM_COLOR1_TEX1
{
    float       x, y, z;        // Koordinaten    (4 Byte *3)
    float       nx, ny, nz;     // Normalen    
  (4 Byte *3)
    DWORD       color1;         // diffuse Farbe    (4 Byte *1)
    float       tu1, tv1;       // Texturekoordinaten (4 Byte *2)
};

// Eine Vertex Shader Deklaration, welche die Verbindung zwischen "Input Registern" und Vertexdaten ist
D3DVERTEXELEMENT9 decl[] =
{
    { 0, 0,  D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
    //v0 enthält unsere Koordinaten, Rückgabe in oPos

   
    { 4, 12, D3DDECLTYPE_FLOAT3,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0 },
    //v4 enthält unsere Normalen, sie befinden sich 12 Bytes (4 Bytes * 3 Komponenten) vom Anfang des Streams entfernt,
    //kein
Rückgabe

    { 7, 24, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_DIFFUSE,  0 },
    //v7 enthält unsere diffuse Farbe, 24 Bytes (4 Bytes * 6 Komponenten) vom Anfang des Streams entfernt
    //Rückgabe in oD0

    { 8, 28, D3DDECLTYPE_FLOAT2,   D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
    //v8 enthält unsere Texturekoordinaten, 28 Bytes (4 Bytes * 7 Komponenten) vom Anfang des Streams entfernt
    //Rückgabe in oT0

    D3DDECL_END()
};

// die Vertex Shader Konstante c[0] wird gefüllt
float constants[4] = {0, 0.5f, 1.0f, 2.0f};
m_pd3dDevice->SetVertexShaderConstantF( 0, (float*)&constants, 1 );

// die Vertex Shader Konstante c[4] wird gefüllt
D3DXMATRIX mat;
D3DXMatrixMultiply( &mat, &m_matView, &m_matProj );
D3DXMatrixTranspose( &mat, &mat );
m_pd3dDevice->SetVertexShaderConstantF( 4, (float*)&mat, 4 );

// die Vertex Shader Konstante c[8] wird gefüllt
float color[4] = {1,1,1,1};
m_pd3dDevice->SetVertexShaderConstantF( 8, (float*)&color, 1 );

// die Vertex Shader Konstante c[12] wird gefüllt
float lightDir[4] = {-1,0,1,0};
m_pd3dDevice->SetVertexShaderConstantF( 12, (float*)&lightDir, 1 );


// Vertex Shader
// v0  Eckpunktkoordinaten
// v4  Normalen
// v7  diffuse Farbe
// v8  Texturekoordinaten
// c4  Matrix für die persp. Transformation
// c12 Richtung des Lichtes

vs_1_1                       // Version

dcl_position0 v0             // Eckpunktkoordinaten v0 zuweisen
dcl_color     v7              // Farbwerte v7 zuweisen
dcl_normal0   v4              // Normalen v4 zuweisen
dcl_texcoord  v8
                          // Texturekoordinaten v8 zuweisen

m4x4 oPos,    v0,    c4      // transformiere Eckpunkte(v0) mit Hilfe der Matrix(c4) durch den Befehl "m4x4", schreiben in oPos
dp3  r0     , v4   , c12     // Lichtberechnung Normale(v4) *(Skalarprodukt) Lichtvektor(c12), schreiben in r0

mul  oD0    , r0.x , v7      // Vertexfarbe berechnen Farbe * Lichtintensität, schreiben in oD0
                            
mov  oT0.xy , v8             // kopiere Texturekoordinaten (.xy weil es nur 2 Koordinaten(U,V) sind)

So, ich hoffe der Zusammenhang zwischen der Vertex Shader Deklaration und den "Output Registern" ist ein wenig verständlich geworden.


Modifikations- und Maskierbefehle

anwendbar auf
R300 VS
NV30 VS
Absolute Value abs Quellregister
- - - ja -
ja
Negate   - Quellregister
ja ja ja ja ja
ja
Saturate  sat Befehl
- ja ja ja
ja
ja
Replicate Swizzle  .xyzw or .rgba Quellregister
ja ja ja ja ja
ja
Arbitrary Swizzle
xyzw or .rgba
Quellregister
ja
ja
ja
ja
ja
ja
Write Mask .xyzw or .rgba
Zielregister
ja ja ja ja ja
ja


Modifikations- und Maskierbefehle haben den großen Vorteil, dass sie nicht als extra Instruktionen zählen. Somit können sie unbeschwert angewendet werden und wirken sich nicht auf die aktuelle Anzahl der Instruktionen in einem Vertex Shader Programm aus.

Absolute Value
Hiermit wird der absolute Wert eines Registers gebildet.

add rDest, rSrc0, rSrc1_abs


Negate
Hiermit wird der Inhalt des Quellregisters negiert.

add rDest, - rSrc0 , rSrc1
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/Source 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, jedoch muss die Reihenfolge erhalten bleiben.

add rDest.xw, rSrc0            // nur die x und w Komponenten werden durch den Befehl verändert


Befehle

R300 VS
NV30 VS
Setup dcl_usage, def, vs -
-
dcl_textureType
2_0
2_x
Arithmetic   add, dp3, dp4, dst, lit, mad, max, min,
mov, mul, nop, rcp, rsg, sqe, slt, sub
mova -
-
Macro-Ops  exp, expp, frc, log, logp, m3x2, m3x3, m3x4, m4x3, m4x4 abs, crs, lrp,
nrm, pow, sgn, sincos
-
-
Flow-Control  - call, callnz, else, endif, endloop,
endrep, if, label, loop, rep, ret
break, break_comp, break_pred, callnz, callnz_pred, if_comp, if_pred, setp -
Texture 
-
-
-
texldl


Als Basismodell dient der Vertex Shader 1.1. Mit jeder weiteren Vertex Shader Version kommen neue Befehle hinzu, die Befehle der Vorgängerversionen bleiben erhalten, deshalb habe ich nur die neuen Befehle je Version in der Tabelle aufgeführt.

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

vs_1_1                // legt Version 1_1 des VS fest
dcl_position0 v0       // erklärt das v0 Register zum Register mit den Normalen eines Eckpunktes            
Arithmetic
Hier werden einfache mathematische Operationen zur Verfügung gestellt.

mul  oD0, r0.x, v7    // r0.x wird mit v7(skalarer Wert) multipliziert und das Resultat in oD0(diffuse Farbe) geschrieben

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

dp3  r0, v4, c12      // rechnet einfach:
                      // r0.w = (v4.x * c12.x) + (v4.y * c12.y) + (v4.z * c12.z);
                      // r0.x = r0.y = 
r0.z = r0.w;

Flow-Control
Hier werden alle Befehle der statischen und dynamischen Flußsteuerung zusammengefasst. Diese gibt es erst seit VS2.0. Die dyn. Flußsteuerung wird erst mittels der Extended Version des VS2.0 verfügbar sein.

vs_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 Vertex Shader 2.0 und höher ein guter Schritt in die richtige Richtung sind.  Wie sich die statische und die dynamische Flußsteuerung auf die Performance auswirkt, bleibt abzuwarten. Sehr interessant sind auch die Möglichkeiten der Version 3.0, gerade wegen der Möglichkeit Texturedaten zu verwenden. Ein Problem gibt es jedoch: Alles zwischen Version 2.0 und 3.0 ist sehr "weich" definiert. Sprich, als Programmierer kann man sich nicht sicher sein, dass ein VS_2_x Programm auf jeder 2_x Hardware läuft, da 2_x eben über CAP-Bits und ein paar Werte definiert wird. Somit dürfte dieser Standard in der Spielepraxis recht unbedeutend sein, da 2_x Hardware nicht gleich 2_x Hardware ist.


<< vorherige Seite nächste Seite >>