Télécharger la version PDF

Transcription

Télécharger la version PDF
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Pipeline Graphique Programmable:
Vertex et Pixel Shaders
Leçon n°7 : Principes et langage assembleur
Année universitaire
2004-2005
Pascal Mignot
[email protected]
Pipeline Graphique Programmable
U N IVERSIT É
D E R EIMS
introduction
C H AMP AGN E-A RD EN N E
depuis DirectX8, il est possible de programmer
directement sur la carte graphique certaine
partie du pipeline.
Évolutions dans DirectX9:
– Maturation et augmentation des fonctionnalités
(type et nombre d’instructions, contrôle de flux).
– Apparition du HLSL (spécifications issue de Cg,
abstraction et programmation proche du C).
– Intégration des shaders, des états du pipeline
graphique, du multipasse à travers les Effets.
Le hardware suit:
- Cartes compatibles DX9 (+ et ++)
- Sur la Radeon 9800, la GeForce 6800, le
pipeline fixe est un shader comme un autre (plus
de circuits spécialisés, le pipeline a la
complexité de ses shaders).
Vertex data
Transformation
and lighting
Vertex
shader
Viewports and clipping
Multitexturing
Pixel
shader
Fog blending
Alpha, stencil
and depth testing
Frame buffer blending
Nouvelle déclaration du FVF
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
un FVF vraiment F
Avec un pipeline programmable, les FVFs peuvent devenir presque
quelconques. Le FVF classique (pipeline fixe) ne suffit plus.
•
•
•
•
Nouveau descriptif du format = tableau ordonné de n+1 structures de type
D3DVERTEXELEMENT9 décrivant les n champs présents dans la structure du sommet dans l’ordre
dans lequel il seront donnés. Typiquement:
D3DVERTEXTELEMENT9 velt[n+1];
ème
structure est le marqueur de fin D3DDECL_END().
La n+1
chaque structure D3DVERTEXELEMENT9 décrit le contenu du champ ainsi que la manière d’y
lire les données associées:
Stream : numéro du stream dans lequel lire ce champ (important: les champs doivent être classés par ordre
croissant de numéro de stream).
Offset : offset depuis le début de la structure pour ce champ particulier (= Offset du champ précédent + sizeof
du champ précédent, à trier par Stream puis par Offset).
Type : type du champ (D3DDECLTYPE_x où x = FLOATn pour un vecteur de n=1…4 floats, D3DCOLOR pour une
couleur RGBA, … cf doc)
Method : D3DDECLMETHOD_DEFAULT (autrement: méthode de subdivision pour les objets de type patch ou pour
les objets sur lesquels s’applique une displacements map).
Usage : comment ce champ est utilisé (D3DDECLUSAGE_x où x est parmi POSITION, BLENDWEIGHT, NORMAL,
TEXCOORD, COLOR, … cf doc)
UsageIndex : numéro d’index permettant de disposer de plusieurs champs à cet usage (ex: pour 3 ensembles
de coordonnées de texture, index=0,1,2) (ex2: couleur: diffuse index=0, spéculaire=1).
un seul champ doit obligatoirement figurer où Usage=D3DDECLUSAGE_POSITION et
UsageIndex=0 (≈ D3DFVF_ XYZ, le vertex est identifié au moins par sa position).
Conséquence: il est possible de passer n’importe quoi à un VS (limite = 16 vecteurs float 4D
accessibles dans le VS par les registres v#). Le VS n’a pas l’obligation de lire tous les champs
présents.
Nouvelle déclaration du FVF
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
correspondance avec le FVF classique
Exemple de FVF à pipeline fixe:
FVFfmt = ( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_SPECULAR | D3DFVF_TEX2 |
D3DFVF_TEXCOORDSIZE3(0)); // ordre sans importance
Son équivalent en pipeline variable:
const D3DVERTEXELEMENT9 velt[6]={
{0, 0,D3DDECLTYPE_FLOAT3,
D3DDECLMETHOD_DEFAULT,
{0,12,D3DDECLTYPE_FLOAT3,
D3DDECLMETHOD_DEFAULT,
{0,24,D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT,
{0,28,D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT,
{0,32,D3DDECLTYPE_FLOAT3,
D3DDECLMETHOD_DEFAULT,
D3DDECL_END()
};
Sa structure de sommet associée:
struct VERTEX {
D3DVECTOR pos, norm;
D3DCOLOR
diff, spec;
float
u0,v0,w0;
float
u1,v1;
}
L’ordre est important si l’on utilise
des déclarations.
D3DDECLUSAGE_POSITION,
D3DDECLUSAGE_NORMAL,
D3DDECLUSAGE_COLOR,
D3DDECLUSAGE_COLOR,
D3DDECLUSAGE_TEXCOORD,
0},
0},
0},
1},
0},
//
//
//
//
//
position
normale
couleur df
couleur sp
texture 1
D3DFVF_
Type =
DECLTYPE_x
Usage =
DECLUSAGE_x
Usage
Indice =
XYZ
FLOAT3
POSITION
0
XYZBn
FLOATn
BLENDWEIGHT
0
NORMAL
FLOAT3
NORMAL
0
DIFFUSE
D3DCOLOR
COLOR
0
SPECULAR
D3DCOLOR
COLOR
1
TEXCOORDSIZEm(n)
FLOATm
TEXCOORD
n
Nouvelle déclaration du FVF
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
utilisation pratique
Le tableau D3DVERTEXTELEMENT9 ne peut pas être utilisé tel quel pour
spécifier le FVF (comme avec le pipeline fixe avec SetFVF).
•
•
Il faut créer un déclaration de FVF: avec la méthode du Device
CreateVertexDeclaration(velt, &pvdec)
velt est le tableau de D3DVERTEXELEMENT9 contenant la description
« humaine » du FVF.
pvdec est un pointeur LPDirect3DVertexDeclaration9 qui contient au
retour un pointeur sur l’interface de la description « machine » du FVF
associé à velt.
Puis indiquer que l’on veut utiliser ce FVF: avec la méthode du Device
SetVertexDeclaration(pvdec)
(à utiliser en même lieu et place que la méthode SetFVF) pour fixer un FVF défini
avec D3DVERTEXELEMENT9 en pipeline programmable.
Remarques:
• lors de la création et le remplissage du Vertex Buffer à ce format, veiller à bien
respecter les types et l’ordre des champs.
• ce type de FVF peut également être utilisé avec un pipeline fixe.
• lorsqu’une déclaration de FVF ne sera plus utilisée, pensez à la libérer avec:
pvdec->Release()
Vertex Shaders
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Qu’est-ce-que c’est?
Code particulier écrit par l’utilisateur et qui remplace le TnL.
Rappel: TnL = Transformation (World, View, Projection) + Vertex Blending + Lights & Materials.
Il est exécuté une fois par sommet.
En entrée: la liste des sommets (format FVF) dans le repère local.
En sortie: les sommets transformés dans le repère global + par sommets: les
couleurs diffuse, spéculaire, et les coordonnées de textures.
Conséquences:
– Lorsqu’un vertex shader est déclaré, le TnL n’est plus fait.
– Toutes ces fonctionnalités doivent être gérées « à la main ».
Utilisations principales:
– Nouveaux modèles d’ombrages et de surfaces (par sommet, pixel interpolé) ou de
types de luminaires.
– Blending particulier et tweening (morphing sur sommets).
– Déformation de surfaces (géométrie procédurale: vêtements, bulles).
– Displacement mapping.
– Modifications de la perspective (effet loupe, effet sous l’eau).
Dans ce cours, on étudiera les VertexShaders ≥ v2.0.
Vertex Shader
Machine virtuelle 3.0
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Vertex data
(projection space)
Vertex data
(model space)
SetVertexDeclaration
Input Registers
Input v# (RO)
SetVertexShader
Vertex Shader
ASM Instructions
SetVertexShaderConstantf
SetVertexShaderConstantb
SetVertexShaderConstanti
ALU
RO Internal registers
Constant c#
Boolean b#
Integer i#
Loop counter aL
Output registers
Output o# (WO)
Texture Samplers
Sampler s#
RW Internal registers
Temporary r#
Address a0
Predicate p0
SetTexture
Le comportement
du Sampler reste
réglé par la méthode
SetSamplerState
du Device.
Vertex Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
description des registres
Registres d’entrées:
v# : registres (16 float4) pour accéder aux champs stockés dans le FVF/Stream.
c#,i#,b# : registres (caps≥256 float4, 16 integer4, 16 bool) constants lors de
l’exécution du VS utilisés pour stocker les données nécessaires au VS.
Registres internes:
r# : registres (VS2=12 float4, VS3=32 float4) temporaires (=variables locales).
s# : (sampler id) pseudo registres (VS3=4) utilisés pour désigner la texture à
utiliser par le sampler.
aL : dans une boucle (loop), compteur de l’itération courante.
a0 : registre entier (integer), pour les conversions en entier (mova).
p0 : registre prédicat (bool4, VS2x) pour stocker et utiliser le résultat de
comparaison sur des vecteurs (setp, if).
Registres de sortie:
o# : registres (12 = 11 float4 + 1 float) dans lesquels sont stockés les résultats en
sortie du VS (position, couleur diffuse et spéculaire, coordonnées de textures,
ou toutes autres valeurs) à transmettre au PS (VS3 seulement).
oPos (position,float4), oFog (fog,float4), oPts (point size,float), oD0/oD1 (diffuse /
specular color,float4), oT# (texture coord,8 float4): registres de sortie
spécialisés (VS2 seulement).
Note: # représente le numéro du registre (si n registres, # = 0 à n-1).
Vertex Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
entrées/sorties d’un VS
Il faut assurer les communications entre le code C++ et le VS chargé dans le GPU:
• flux d’entrée:
côté C++ : définir le FVF associé au Vertex Buffer envoyé au VS. Un champ du FVF est identifié par
son Usage et son UsageIndex (cf D3DVERTEXTELEMENT9)
côté VS : définir le sens des registres v# avec dcl_TypeK v# où Type=Usage et K=UsageIndex.
Exemple: le champs dans le FVF défini par:
{0,0,D3DDECLTYPE_D3DCOLOR, D3DDECLMETHODDEFAULT, D3DDECLUSAGE_COLOR, 1}
est récupéré dans le VS dans le registre v5 avec: dcl_color1 v5 (correspondance sémantique).
Note: dcl_Type est une autre écriture de dcl_Type0 (par d’UsageIndex fait implicitement référence à 0).
•
constantes et paramètres:
constantes = registres c#,i#,b# fixés avec un def dans le VS.
paramètres = registres c#,i#,b# fixé avec un SetVertexShaderConstantx (C++) avant
l’exécution du VS.
•
sampler (VS3 seulement)
côté C++ : effectuer les SetTexture adéquat (étage 0 à 3 pour les samplers s0 à s3).
côté VS : déclarer le sampler avec decl_2d s# (resp. decl_cube, decl_volume suivant le type
de texture).
•
flux de sortie:
côté VS : définir le format de sortie du VS (différent de celui d’entrée) en remplissant les registres o#
correspondant (VS2: registres de sortie spécialisés, VS3: sens des registres de sortie à déclarer
avec decl_TypeK o# avec une syntaxe similaire au flux d’entrée).
côté C++ : les traitements suivants possibles (multitexturing, éventuellement un PS) dépendent du
format de sortie.
Vertex Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
passage de paramètres
Le passage de paramètres à un VS se fait avec les méthodes du Device:
SetVertexShaderConstantf(k,*pF,n)
stocke à partir du registre constant ck les 4 x n floats contenus dans le tableau de
réel (float *)pF.
SetVertexShaderConstanti(k,*pI,n)
stocke à partir du registre constant entier ik les 4 x n integers contenus dans le
tableau d’entiers (int *)pF.
SetVertexShaderConstantb(k,*pB,n)
stocke à partir du registre constant booleen bk les n booleans stockés dans le
tableau de booléens (BOOL *)pB,
Si n est plus grand que 1, les registres suivants sont également affectés.
Exemple:
D3DXMATRIX m; // matrice de taille 4x4
D3DXMatrixIdentity( &m );
pDEV->SetVertexShaderConstantf(4,m,4);
stocke la matrice m à partir du registre c4. c4 contient la première ligne de la matrice, c5 la seconde, c6
la troisième et c7 la quatrième.
Remarques:
– écrase les constantes déjà définies par def (à vérifier mais logique).
– le passage de paramètre peut précéder le SetVertexShader!
Vertex Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
structure d’un code assembleur
Vertex Shader 2.0 et 2.x
Vertex Shader 3.0
vs_2_0 // version
// déclaration des constantes
def c0 0,0,0,0
// déclaration des registres d’entrée
dcl_position v0
dcl_normal
v1
// debut du code
...
mov oPos, v0 // affectation sortie
...
vs_3_0
// version
// déclaration des constantes
def c0,0,0,0,0
// déclaration des samplers
dcl_2d s0
// déclaration des registres d’entrées
dcl_position v0
dcl_normal
v1
// déclaration des registres de sortie
dcl_position o0
// position
// début du code
...
mov o0, v0 // affectation sortie
...
Rappel: en vs 2.0 et vs2.x
- pas de sampler.
- l’usage des registres de sortie est déjà
prédéfinis (pas besoin de les définir).
Instructions des Vertex Shaders
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
1. Déclarations et initialisations
vs_ver
[0] définition de la version du VS (OBLIGATOIRE)
ver = 2_0, 2_x, 2_sw, 3_0 ou 3_sw (x=eXtended, sw=software only).
dcl_2d s# (resp. dcl_cube s#, dcl_volume s#) [VS3]
[0] défini le sampler s# comme une texture 2D (resp. cube, volume).
L’étage de texture # (SetTexture) devra contenir une texture compatible.
Utilisation d’une texture avec un sampler: texldl.
dcl_TypeK v#
[0] indique que le registre d’entrée v# sera chargé avec le Kième champ de type Type du FVF.
dcl_TypeK o# [VS3]
[0] indique que le registre de sortie o# est le Kième champ de type Type en sortie du VS.
un masque d’écriture peut être utilisé pour stocker plusieurs valeurs de Type différents dans
un même registre (par exemple 2 ensembles de coordonnées 2D dans un registre float4).
def c#, kx, ky, kz, kw
[0] fixe une constante flottante.
c# = [kx, ky, kz, kw] où les k sont des flottants.
defi i#, kx, ky, kz, kw
[0] fixe une constante entière.
i# = [kx, ky, kz, kw] où les k sont des entiers.
defb b#, k
[0] fixe une constante booléenne.
i# = k où k est True ou False.
Instructions des Vertex Shaders
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
2. Opérations numériques sur registres float4
Opérations scalaires
Opérations n-aires (n≥2)
Opérations vectorielles
exp d, s.M
add d, u,v
crs d, u, v
[1] exponentiel avec (M=x,y,z ou w)
d.* = exp( s.M )
log d, s.M
[1] log en base 2 (avec M=x,y,z ou w)
d.* = (s.M==0 ? –FLT_MAX : log2(abs(s.M)))
pow d, u.M1,v.M2
[3] puissance (avec M1 et M2 = x,y,z ou w)
d.* = |u.M1| v.M2
rcp d, s.M
[1] inverse avec M=x,y,z ou w
d = (s.M == 0 ? FLT_MAX : 1/s.M )
rsq d, s.M
[1] inverse de la racine carrée avec M=x,y,z
ou w
d = (s.M == 0 ? FLT_MAX : 1/sqrt(s.M) )
dst d, u,v
[1] fonction de distance (ex: atténuation)
entrée : u=(?,D*D,D*D,?), v=(?,1/D,?,1/D)
sortie : d=(1,D,D*D,1/D)
[1] addition par composante
d.* = u.* + v.*
sub d,u,v
[1] soustraction par composante
d.* = u.* - v.*
mul d,u,v
[1] multiplication par composante
d.* = u.* x v.*
mad d,u,v,w
[1] multiplication puis addition par
composante.
d.* = u.* x v.* + w.*
min d,u,v
[1] minimum par composante
d.* = MIN(u.* , v.*)
[2] produit vectoriel
d.+ = u3 ^ v3
dp3 d, u, v
[1] produit scalaire 3D
d.* = u3 • v3
dp4 d, u, v
[1] produit scalaire 4D
d.* = u4 • v4
lrp d,s,u,v
[2] interpolation linéaire entre les
vecteurs u et v.
d.* = s.* x u.* + (1- s.*) x v.*
nrm d, u
[3] normalisation 3D
d.* = u.* / longueur(u3).
max d, u, v
Opérations unaires
mov d, s
[1] affectation : d.* = s.*
abs d, s
[1] valeur absolue par composante: d.* = |s.*|
frc d, s
[1] partie fractionnaire par composante: d.* =
d.* - floor(d.*)
sgn d, s,t1,t2
[3] signe du u par composante.
d.* = (s.* > 0 ? 1 : (s.* < 0 ? -1 : 0))
t1,t2 sont des registres temporaires.
[1] maximum par composante
d.* = MAX(u.* , v.*)
slt d,u,v
[1] test inférieur par composante.
d.* = (u.* < v.* ? 1 : 0)
sge d,u,v
[1] test supérieur par composante.
d.* = (u.* >= v.* ? 1 : 0)
u.* : opération composante par composante.
u3 : utilise les 3 premières composantes.
u.x : utilise la composante x.
Opérations matricielles
mαxβ d, u, vk
[β] produit vecteur du α-D (u) et
matrice αxβ (v) résultat stocké dans
les β premières coordonnées de d.
le ième coordonnées de d contient le
produit scalaire α -D entre u et vk+i.
défini pour les couples 3x2, 3x3, 3x4,
4x3, 4x4.
En bleu: opération spécifique des vertex shaders.
Opérations numériques non indiquées: lit, logp, expp,
sincos
Instructions des Vertex Shaders
3. Modificateurs de lecture et d’écriture,
Swizzling, adressage des registres
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Définition:
source = registre contenant un paramètre passés à l’instruction
destination = registre qui contiendra le résultat (bref, le premier paramètre).
Modificateurs de source:
-s : négation du registre s.
abs s : valeur absolue du registre s.
Swizzling de source:
s.αβγδ où α,β,γ,δ = x,y,z ou w (avec ou sans répétition).
la source passée sera le vecteur: [ s.α, s.β, s.γ, s.δ ]
Ecriture équivalente:
s.αβγ ≡ s.αβγγ , s.αβ ≡ s.αβββ , s.α ≡ s.αααα
x ≡ r , y ≡ g, z ≡ b, w ≡ a (effet de style: géométrique ou couleur).
Exemple: mul r0, v0, c0.x // multiplication du vecteur v0 par c0.x
Masque d’écriture de la destination:
d.α : seul le canal α de d est mis à jour (les autres restent inchangés).
mêmes principes pour d.αβ ou d.αβγ à partir du moment où α<β<γ.
Exemple: mul r0.xw, v0, c0.x // r0.x = v0.x*c0.x et r0.w = v0.w*c0.x
Adressage relatif (VS seulement)
Limites: seuls les registres c# supportent l’adressage relatif.
Indices relatifs utilisables: les nombres entiers, les registres a0 et aL.
Affectation du registre d’adresse a0 (int4) : mova a0,s // a0.* = round(s.*)
Syntaxe de l’adressage relatif: c#[ indice ]
Exemples: c2[4] , c2[a0.x + 1] , c2[aL + 3]
Instructions des Vertex Shaders
4. Contrôles de flux
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Tests
Boucle et contrôles de boucles
if s
rep i#
[3] s est un booléen (b# ou p0.α [2.x])
if_op s1.M1, s2.M2
2.x[3] op : opérateur de comparaison parmi:
=
op
eq
≠
ne
<
lt
≤
le
[3] début de boucle simple
i#.x = nombre de répétition (255 au plus)
pendant la boucle, aL = valeur du compteur (de i#.x à 1).
endrep
>
gt
≥
ge
s1.α et s2.β sont les scalaires à comparer.
else : [1] début du bloc else.
endif : [1] fin du if
[2] fin de boucle rep: compteur incrémenté après.
loop aL, i#
[3] début de boucle
i#.x = nombre de répétitions
i#.y = valeur initiale du compteur.
i#.z = incrément du compteur.
pendant la boucle, aL = valeur du compteur de i#.y à i#y+(i#.x-1)*i#.z
Branchements et sous-routines
endloop
label l#
break
[0] définit le label l#
seulement après un ret.
la routine doit se terminer par ret.
call l#
[2] appel inconditionnel à la routine l#.
callnz l#,s
[3] appel la routine l# si s=True.
s est un booléen (b# ou p0.α [2.x])
!s inverse le booléen.
ret
[1] fin de routine ou de programme.
[2] fin de boucle loop: compteur incrémenté après
2.x[1] arret de la boucle rep (resp. loop) courante.
L’exécution continue après end_rep (resp. end_loop).
breakp p0.α
2.x[3] arret de la boucle rep (resp. loop) courante
break_op s1.M1 s2.M2
2.x[3] arret de la boucle rep (resp. loop) si la comparaison des
scalaires s1.M1 op s2.M2 est vraie.
Contrôle du registre prédicat
setp_op p0,s1,s2
2.x[1] affecte le registre prédicat pour chaque composante.
p0.* = s1.* op s2.*
Instructions des Vertex Shaders
5. Limite du contrôle de flux
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
VS2.0
VS2.x
VS3.0
Static
Nesting
Dynamic
Nesting
loop/rep
Nesting
call
Nesting
Static flow
count (FC)
FC limited
n/a
1
1
16
0 ou 24
1à4
1à4
voir VS20Caps.Dynamic
FlowControlDepth
voir VS20Caps.StaticFlow
ControlDepth
voir VS20Caps.StaticFlow
24
4
4
FC limited
24
16
ControlDepth
No Limit
Interprétation:
vs2.x et vs3 : introduction des flux dynamiques (if_op, break, break_op,
et les if, callnz, breakp pour le registre prédicat).
loop/rep nesting : nombre d’imbrication possible (1 = pas d’imbrication
possible).
call nesting : nombre d’appels consécutifs avec call possible avant un
ret (1=le call précédent doit être fini avant le suivant).
static flow count : compteur limitant le nombre d’instructions de contrôle de
flux dans le code (call=1, callnz=1, if=1, else=1, loop=1, rep=1).
static/dynamic nesting : nombre d’imbrications de if possible (static = if
sur des constantes, dynamic = if sur des valeurs booléennes évaluées
lors de l’exécution).
Instructions des Vertex Shaders
6. Compléments
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Limites sur l’accès aux registres
•
pour toutes les instructions qui ne sont pas des macros, on peut en général
utiliser le même registre en source et en destination.
exemple: add r1,c1,r1 // r1 = r1 + c1
•
le nombre d’accès en lecture à un même type registre dans une même
instruction est limité à 1 (exception: 3 pour r#).
exemple: add r1, v0,v1 // échec: accès en lecture à deux v#
add r1, v0,v0 // OK.
•
le nombre d’accès en lecture à un même registre dans une même instruction
peut être limité:
Registres
Nombre
d’accès
b#, i#, p0, s#
c#, a0, aL
v#, r#
1
2 (VS2)
illimité (VS3)
illimité
exemple: lrp r1, c1,c1,c1
Modificateur d’instruction
•
// échec en VS2: c1 lu 3 fois
// succès en VS3.
instruction_sat : exécute l’instruction et sature chaque élément du registre de
destination entre 0 et 1.
exemple: add_sat r1, v0,v0
Instructions des Vertex Shaders
7. Programmation et Macro
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
•
commentaires
on peut utiliser au choix:
les commentaires de type C++ : // comment
les commentaires de type C : /* comment */
•
macros
#define : permet de définir:
• des constantes:
exemple : #define VAL1 2,3,4,5
def c1,VAL1
• des symboles:
exemple : #define LIGHTPOS c1
def LIGHTPOS, 2,3,4,5
• des labels: (à tester)
exemple: #define CALCLUM l1
…
call CALCLUM
…
label CALCLUM
ret
#include : inclusion d’un fichier externe (syntaxe du C)
on y stocke généralement les constantes afin de pouvoir les inclure à la fois dans le code
asm du vertex shader et le code C++.
autre??? (aucune documentation disponible sur le sujet)
Instructions des Vertex Shaders
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
8. Textures (VS3 seulement)
Les spécifications VS3 introduise la possibilité d’accéder à une texture
dans un VS:
Textures dans un vertex shader:
il existe 4 étages de texture spécifiques pour les vertex shaders
(distincts des étages 0,…,15) nommés D3DVERTEXTEXTURESAMPLERi
avec i = 0…3. On utilise:
SetTexture : pour charger la texture dans ces étages.
SetSamplerState : pour fixer les comportements du sampler.
CheckDeviceFormat : pour vérifier avec D3DUSAGE_QUERY_VERTEXTEXTURE
pour vérifier les formats de texture supportés.
Instruction:
texld d, u,s#
où u sont les coordonnées de texture, s# l’étage de texture (à déclarer), et
d le registre de destination.
Vertex Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Détection des fonctionnalités
Comme d’habitude: à travers les Caps:
D3DCAPS9 pCaps;
pDEV->GetDeviceCaps(&pCaps);
Pour tester qu’un code VS2.0 pourra être exécuté sur le Device, on
examine le contenu du champ VertexShaderVersion de pCaps:
• test de la version minimale de shader
pCaps.VertexShaderVersion >= D3DVS_VERSION(2,0)
•
récupération de la version shader: MAJOR.MINOR
D3DSHADER_VERSION_MAJOR(pCaps.VertexShaderVersion)
D3DSHADER_VERSION_MINOR(pCaps.VertexShaderVersion)
Autres capacités du VS dans pCaps:
tester pCaps.x où x parmi:
MaxVertexShaderConst : nombre de
registres constant dans le VS.
MaxVShaderInstructionExecuted :
nombre maximum d’instructions
exécutables par un VS (-1 = infini).
MaxVertexShader30InstructionSlot
s : nombre maximum de slots [x]
disponibles pour un VS3.
Autres capacités spécifiques au VS2 et VS2.x
tester pCaps.VS20Caps.x où x parmi:
Caps (si >0) la prédication est autorisée (setp).
NumTemps : nombre de registres temporaires (v#)
DynamicFlowControlDepth : 0 ou 24, indiquant
la profondeur d’imbrication autorisée pour du
flow control dynamique.
StaticFlowControlDepth : la profondeur
d’imbrication autorisée du control de flux
statique (loop, rep, call, callnz_bool).
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Compilation et chargement d’un
vertex shader
Un VS est souvent créé sous forme d’un fichier texte (ex: myshader.vsh).
méthode « manuelle »
dans un terminal, utiliser le compilateur vsa.exe
C:\DX90SDK\Utilities> vsa myshader.vsh
Microsoft (R) D3DX9 Shader Assembler 4.09.00.1126
Copyright (C) Microsoft Corporation 2002-2003. All rights reserved.
assembly succeeded; see myshader.vso
le fichier vso créé doit ensuite être chargé dans un buffer de la mémoire.
méthode interne
La fonction D3DXAssembleShaderFromFile permet de charger et compiler un
vertex shader:
D3DXAssembleShaderFromFile(fname, NULL, NULL, flags, &pShaderBuf, &pErrMsg)
fname : nom du fichier contenant le vertex shader.
flags : flag de compilation 0 ou D3DXSHADER_DEBUG (mode debug). cf doc.
pShaderBuf [out] pointeur sur le code objet du shader (type=LPD3DXBUFFER).
pErrMsg [out] message d’erreurs à l’issu de la compilation (type=LPD3DXBUFFER).
Cette fonction renvoit un HRESULT (à tester pour savoir si la compilation a échouée).
La méthode GetBufferPointer() de D3DXBUFFER permet de récupérer le pointeur du
buffer qui contient le code objet du shader ou le texte du message d’erreur créé.
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Création et utilisation
d’un vertex shader
Création de l’interface vertex shader
avec la méthode du Device:
CreateVertexShader(pShaderObj,pShader)
pShaderObj : pointeur sur le code objet (DWORD *).
pShader : [out] pointeur de type LPDIRECT3DVERTEXSHADER9.
Utilisation d’un vertex shader
avec la méthode du Device:
SetVertexShader(pShader)
pShader : pointeur LPDIRECT3DVERTEXSHADER9 créé par
CreateVertexShader, ou NULL pour rétablir le VS du pipeline fixe.
Toutes les primitives dessinées à partir de ce point utilisent le
VS défini une fois par sommet.
Note 1: à signaler l’existence de la méthode SetStreamSourceFreq qui permet d’utiliser
un sommet (et donc d’invoquer un VS) plus d’une fois par sommet (VS3).
Note 2: un shader compilé peut se décompiler grâce à D3DXDisassembleShader.
Vertex Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Résumé : mise en oeuvre
avec les méthodes suivantes du Device:
Initialisation:
1.
Création de la déclaration du FVF.
CreateVertexDeclaration ou déclaration du masque D3DFVF_x
2.
Création du Vertex Shader.
CreateVertexShader (s’utilise sur le code d’un Vertex Shader déjà compilé).
Utilisation:
1.
Déclaration du format du FVF
SetVertexDeclaration ou SetFVF
2.
Définition des constantes du Vertex Shader
SetVertexShaderConstantx
3.
Chargement du Vertex Shader dans le GPU
SetVertexShader
Notes :
- les primitives doivent être au format FVF indiqué.
- les DrawPrimitive (…) utilise le VS défini en chaque sommet.
Vertex Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Exemple 1: VS minimal (sans éclairage)
// Fichier 01_BasicT.vsh
vs_2_0
// parametres en entree
dcl_position v0
dcl_color v1
dcl_texcoord v2
// transformation des points
m4x4 r0, v0,c0
mov oPos, r0
// recopie en sortie
mov oD0,v1 // couleur diff.
mov oT0,v2 // texture 0
// Variable globale
LPDIRECT3DVERTEXSHADER9 pVS0 = NULL; // Vertex Shader
// Initialisation du Shader
HRESULT InitShaders(void) {
HRESULT
s;
LPD3DXBUFFER
pShadObj, pErrMsg;
// compilation du vertex shader
s = D3DXAssembleShaderFromFile("01_BasicT.vsh", NULL, NULL, 0,
&pShadObj, &pErrMsg);
if FAILED(s) {
MessageBox(NULL, (char *)(pErrMsg->GetBufferPointer()),
"VS compilation error", MB_OK);
SAFE_RELEASE(pErrMsg);
return E_FAIL; }
// creation du vertex shader
pDEV->CreateVertexShader((DWORD *)pShadObj->GetBufferPointer(), &pVS0);
SAFE_RELEASE(pShadObj);
SAFE_RELEASE(pErrMsg);
return S_OK;
}
// Après la mise à jour d’une des
// matrice: MWorld, MView ou MProj
// matrice complète de transformation
MFull = MWorld * MView * MProj;
// transposée pour passer au shader
D3DXMatrixTranspose(&TMFull,&MFull);
// Plus de SetTransform !!!
// Les 3 transformations sont assurées par
// le Vertex Shader.
// Lors de la boucle de rendu
// Fixe les constantes
// Matrice de transformation complete (c0,c1,c2,c3)
pDEV->SetVertexShaderConstantF(0,(float *)&TMFull,4);
// fixe le vertex shader
pDEV->SetVertexShader(pVS0);
// dessin de la primitive (SetFVF avant)
pDEV->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,
nbvert,0,nbface);
Vertex Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Exemple 2: VS minimal avec lampe ponctuelle
vs_2_0
// parametres en entree
dcl_position v0
dcl_normal
v1
dcl_color
v2
dcl_texcoord v3
// calcul du vecteur L (normalisé)
sub r0, c4,v0 // direction source
nrm r1, r0
// normalisation de L
// calcul de la luminance diffuse
dp3 r2.x, v1,r1 // N.L
mul r3, r2.x,v2 // Couleur.(N.L)
// recopie en sortie de la couleur
// clamp automatique de oDx si NL<0
mov oD0,r3
// transformation du point
m4x4 r0, v0,c0
mov oPos, r0
// recopie des coordonnees de
// la texture
mov oT0,v3
// Après la mise à jour d’une des
// matrice: MWorld, MView ou MProj
// IMWorld = World inverse pour passage du repère
// global au repère local de l’objet courant
float det=D3DXMatrixDeterminant(&MWorld);
D3DXMatrixInverse(&IMWorld,&det,&MWorld);
// Mise a jour de la matrice complete
MFull = MWorld * MView * MProj;
// transposée pour le shader
D3DXMatrixTranspose(&TMFull,&MFull);
// Lors de la boucle de rendu
// position de la source dans le repere global
D3DXVECTOR3 LightPos = D3DXVECTOR3( 0.0f,-2.5f, 0.0f );
// calcul de la position de la source dans le repère de l’objet
// (pour le calcul d’illumination)
D3DXVECTOR4 LightLocalPos;
D3DXVec3Transform(&LightLocalPos,&LightPos,&IMWorld);
/*** Fixe les constantes ***/
// Transformation complète (c0,c1,c2,c3)
pDEV->SetVertexShaderConstantF(0,(float *)&TMFull,4);
// Source dans le repère de l’objet
pDEV->SetVertexShaderConstantF(4,(float *)&LightLocalPos,1);
// fixe le Vertex Shader
pDEV->SetVertexShader(pVS0);
Vertex Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Exemple 2: comment rendre le code plus lisible
vs_2_0
// valeurs principales
#define POS
v0
#define NORMAL v1
#define COLOR
v2
#define TEXTUR v3
// Table des indices des constantes
// = 1er param. de SetVertexShaderConstantx
// Transformation complète
#define MFULL
0
// Coordonnées source (rep. global)
#define PLIGHT 4
// parametres en entree
dcl_position POS
dcl_normal
NORMAL
dcl_color
COLOR
dcl_texcoord TEXTUR
// calcul du vecteur L (normalisé)
// direction de la source L
sub r0, c[PLIGHT],POS // dir. source
nrm r1, r0
// normalisation L
// calcul de la luminance diffuse
dp3 r2.x, NORMAL,r1
// N.L
mul r3, r2.x,COLOR
// Couleur.(N.L)
// recopie en sortie de la couleur
// (clamp automatique de oDx si N.L<0
mov oD0,r3
// transformation du point
m4x4 r0, POS,c[MFULL]
mov oPos, r0
// recopie en sortie des coordonnees
// de texture
mov oT0,TEXTUR
Conclusion: utilisez la définition de constante ou de nom symbolique !
Vertex Shader
U N IVERSIT É
D E R EIMS
Exemple 4: Tweening
C H AMP AGN E-A RD EN N E
// Structure de sommets
typedef struct {
D3DXVECTOR3
P0,P1;
D3DXVECTOR3
N0,N1;
DWORD
diff;
FLOAT
u,v;
} VERTEX;
vs_2_0
dcl_position0 v0
dcl_position1 v1
dcl_normal0
v2
dcl_normal1
v3
dcl_color
v4
dcl_texcoord v5
// interpolation
mov r2,v0
lrp r0, c[5],v1,r2
mov r2,v2
lrp r1, c[5],v3,r2
// illumination
sub r2, c[4],r0
nrm r3, r2
dp3 r2.x, r1,r3
mul r3, r2,v4
// sortie
mov oD0,r3
m4x4 r2, r0,c[0]
mov oPos, r2
mov oT0, v5
// initialisation du format de sommets
// creation du format pour le FVF, et fixe le format
const D3DVERTEXELEMENT9 myFVF[]={
{0, 0,D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,
{0,12,D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,
{0,24,D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,
{0,36,D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,
{0,48,D3DDECLTYPE_D3DCOLOR,D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,
{0,52,D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,
D3DDECL_END()
};
LPDIRECT3DVERTEXDECLARATION9 pVD0=NULL;
s=pDEV->CreateVertexDeclaration(myFVF,&pVD0);
// lors du rendu
// constantes c0,c1,c2,c3 : matrice de transformation
pDEV->SetVertexShaderConstantF(0,(float *)&TMFull,4);
// constante c4 : position transformée de la source
D3DXVECTOR4 V;
D3DXVECTOR3 LightPos = D3DXVECTOR3( 0.0f,-2.5f, 0.0f );
D3DXVec3Transform(&V,&LightPos,&IMWorld);
pDEV->SetVertexShaderConstantF(4,(float *)&V,1);
// constante c5 : interpolation 0=objet 1, 1=objet 2
float alf= (1.f + sinf( ctime/4000.f ))/2.f;
V=D3DXVECTOR4(alf, alf, alf, alf);
pDEV->SetVertexShaderConstantF(5,(float *)&V,1);
// fixe le format et le vertex shader
pDEV->SetVertexDeclaration(pVD0);
pDEV->SetVertexShader(pVS0);
0},
1},
0},
1},
0},
0},
VS_2_0
VS_2_a
VS_3_0
256
256
>= 512
65535
65535
>=65535
-
D
D
12
13
32
# constant registers
>=256
>=256
>=256
Static Flow Control
D
D
D
Dynamic Flow Control
-
D
D
Dynamic Flow Control Depth
-
24
24
Vertex Texture Fetch
-
-
D
# of texture samplers
-
-
4
Geometry Instancing Support
-
-
D
# of instruction slots
Max # of instructions executed
U N IVERSIT É
D E R EIMS
Instruction Predication
C H AMP AGN E-A RD EN N E
Temp Registers
Profil des
vertex
shaders
disponibles
Profils disponibles:
vs_2_0 : Radeon 9500 à 9800.
vs_2_a : GeForce FX.
vs_3_0 : GeForce Serie 6.
En terme de fonctionnalité:
2.x = vs_2_a
(cf les caps)
Récupération du profil:
D3DXGetVertexShaderProfile
# of instruction slots
nombre d’instructions maximum dans un
programme.
Max # of instructions executed
nombre maximum d’instructions exécutées (peut
être supérieur à # of instruction slots à cause des
boucles).
Instruction Predication
registre prédicat et callnz, breakp, setp , if pred.
Geometry Instancing Support:
support de SetStreamSourceFreq (voir le “Instancing
Sample” du SDK).
Pixel Shaders
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Qu’est-ce-que c’est?
Programme écrit par l’utilisateur qui remplace le multitexturing.
Rappel: Multitexturing = « mélange » des textures et des éclairages diffus et spéculaires.
Conséquences:
–
–
Lorsqu’un pixel shader est déclaré, ce mélange n’est plus fait.
Il doit être géré « à la main ».
En entrée: un pixel a peindre (point de la facette dont les sommets sont traités par le
VS), l’interpolation en ce point: de la couleur diffuse+speculaire, des coordonnées de
texture.
En sortie: la couleur du pixel a afficher.
Operation recommencée autant de fois qu’il y a de texels traités (pour toutes les facettes
non rejetées, pour les pixels visibles de ces facettes (ZSUCCESS)).
Utilisations principales:
–
–
–
nouveaux modèles d’ombrages (par pixel, géomètrie interpolée).
textures procédurales ou dynamiques.
bump-mapping.
Dans ce cours, on étudiera les Pixel Shaders ≥ v2.0.
Pixel Shader
U N IVERSIT É
D E R EIMS
Machine virtuelle 3.0
C H AMP AGN E-A RD EN N E
Input Register (RO)
SetTexture
Le comportement du Sampler
reste réglé par la méthode
SetSamplerState du Device.
SetPixelShader
SetPixelShaderConstantf
SetPixelShaderConstantb
SetPixelShaderConstanti
Texture
Samplers
Sampler s#
Interpolated Vertex Color v#
Interpolated Texture Coord. t#
Face vFace
Position vPos
Pixel Shader
ASM Instructions
RO Internal
registers
Constant c#
Boolean b#
Integer i#
Loop counter aL
ALU
Output registers (WO)
Color oC#
Depth oDepth
RW Internal
registers
Temporary r#
Address a0
Predicate p0
Pixel Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
description des registres
Registres d’entrées:
PS2 v# registres contenant le couleur (composantes entre 0 et 1) interpolée sur la
facette: 2 float4 (correspondant à oD0, oD1) à 8 bits de précision.
t# registres contenant la coordonnée de la texture interpolée sur la facette: 8 float4.
PS3 v# registres contenant la valeur (couleur, coordonnées de texture, …) interpolée
sur la facette (10 float4 à pleine précision et sans clamping).
vPos coordonnées (x,y) du pixel (zw non définis).
vFace positif sur la face avant, négatif sur la face arrière (si le CULLMODE est NONE).
Utilisable seulement avec setp_op, if_op et break_op.
Registres constants:
c#,i#,b# : registres (PS2=32 float4 PS3=224 float4, 16 integer4, 16 bool) constants lors
de l’exécution du PS, et utilisés pour stocker les données nécessaires au PS.
Registres internes:
r# : registres temporaires (PS2≥12float4 cf PS20Caps.NumTemps, PS3=32 float4).
s# : (sampler id) pseudo registres (16) désignant la texture à utiliser par le sampler.
aL : dans une boucle (loop), compteur de l’itération courante.
p0 : registre prédicat (PS*,bool4) pour la comparaison de vecteurs (setp, if).
Registres de sortie (à affecter avec mov)
oC# : registres (4 float4) contenant la couleur de sortie du pixel (oC0 en général, oC# pour
les textures multiéléments).
oDepth registre (float) profondeur utilisée par le DepthStencil buffer. Usage déconseillé car
affecte le traitement du pixel (rejet, HyperZ).
Pixel Shader
entrées/sorties d’un PS
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
L’entrée d’un PS est constituée d’un ensemble de valeurs interpolées au pixel courant à partir
des valeurs associées données aux sommets de la facette à laquelle le pixel courant
appartient. Ces valeurs (et leurs sémantiques) sont directement celles issues du VS.
• flux d’entrée du PS:
côté VS : le sens des registres o# est:
•
•
(cas VS2) prédéfini oPos (position), oD0/oD1 (diffuse / specular color), oT# (texture coord) , oFog, oPts
(cas VS3) déclaré avec dcl_TypeK o# où Type=Usage et K=UsageIndex.
côté PS : le sens des (10) registres d’entrée est:
•
(cas VS2) limité à des couleurs interpolées (v0/v1) ou des coord. de texture interpolées (t#). Ces
registres d’entrée doivent être déclarés avec dcl.
• (cas VS3) quelconque: tout TypeK (sauf principalement position0, psize0) en sortie du VS est utilisable
s’il est déclaré (dcl_TypeK v#). Tous ces registres sont interpolés en pleine précision.
Mode d’interpolation: interpolations linéaires classiques sur un triangle.
•
constantes et paramètres:
constantes = registres c#,i#,b# fixés avec un defx dans le PS.
paramètres = registres c#,i#,b# fixés avec un SetPixelShaderConstantx (C++) avant
l’exécution du PS (syntaxe et utilisation identique à son équivalent VS).
•
sampler
côté C++ : effectuer les SetTexture adéquat (étage 0 à 15 pour les samplers s0 à s15, mais
attention, au plus 8 coordonnées de textures).
côté PS : déclarer le sampler avec decl_2d s# (resp. decl_cube, decl_volume suivant le type
de texture).
•
flux de sortie du PS:
La valeur du pixel en sortie sera utilisée directement par la Rasterization (avec sa valeur de
profondeur associée) pour mettre à jour le z-buffer et le BackBuffer.
Pixel Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
structure d’un code assembleur
Pixel Shader 2.0 et 2.x
Pixel Shader 3.0
ps_2_0 // version
// déclaration des constantes
def c0 0,0,0,0
// déclaration des samplers
dcl_2d s0
// déclaration des registres d’entrée
dcl v0
// couleur
dcl t0
// coordonnées de texture
// debut du code
...
mov oC0, v0 // affectation sortie
...
ps_3_0
// version
// déclaration des constantes
def c0,0,0,0,0
// déclaration des samplers
dcl_2d s0
// déclaration des registres d’entrées
dcl_color0
v0 // couleur
dcl_texcoord0 v1 // coordonnées de texture
dcl vPos.xy
// position du pixel
dcl vFace
// orientation de la face
// début du code
...
mov oC0, v0 // affectation sortie
...
Rappel (en ps 2.0 et ps2.x) pas besoin de
déclarer le type des registres d’entrée.
Instructions des Pixel Shaders
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
1. Déclarations et initialisations
ps_ver
[0] définition de la version du PS (OBLIGATOIRE)
ver = 2_0, 2_x, 2_sw, 3_0 ou 3_sw (x=eXtended, sw=software only).
initialisations des samplers, des registres d’entrées, des constantes
voir la partie Déclarations et initialisations des VS.
2. Opérations numériques sur registres float4
–
–
voir la partie « Opérations numériques sur registres float4 » des VS.
Instructions supplémentaires spécifiques aux PS (hors instructions des samplers)
Opération vectorielle
dp2add d, u,v,w.M
[1] produit scalaire 2D + addition
d.* = u2  v2 + w.M
Opération n-aires
cmp d, u,v,w
[1] comparaison à 0 par composante
d.* = (u.* ? v.* : w.*)
Gradient (PS2 si PS20Caps, PS3)
dsx d, u
dsy d, u
gradient dans la direction x (resp. y)
u peut être v#, c# ou r#.
Note: le calcul du gradient sur le registre
considéré utilise la valeur qu’aura ce
registre à la même étape sur les pixels
voisins exécutant ce même PS.
Attention: la formule utilisée dépend du GPU.
On l’utilise donc en général seulement
avec l’instruction texldd (ou alors pour
connaître le signe du gradient).
Instructions des Pixel Shaders
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
3. Opérations spécifiques
Arret du rendu:
texkill u
stoppe le rendu du pixel courant si
n’importe laquelle des
composantes xyz est négative. Il
n’y a pas de couleur de sortie
(pixel annulé).
Echantillonnage des textures:
texld d, u,s#
place dans d la valeur
(échantillonnée et filtrée) de la
texture s# aux coordonnées u. Les
composantes de d non lues sont
mises à 1 ou 0 (suivant les cas).
texldl d, u,s#
idem sans filtrage mais le lod
utilisé est u.w + LODBIAIS.
Disponible également en VS3.
texldp d, u,s#
idem texld, sauf que les
coordonnées de texture sont
projetés avant échantillonnage en
les divisant par u.w.
texldb d, u,s#
idem texld, sauf que le LOD est
biaisé d’un facteur supplémentaire
au MIPMAPLODBIAS de u.w (PS2
w∈[-3,+3], PS3 w∈[-16,+15]).
MAXMIPLEVEL reste respecté.
texldd d, u,s#,v,w
idem texld mais utilise les
gradients en x (v obtenu avec
dsx) et en y (w obtenu avec dsy)
pour calculer le LOD adapté (PS2
sous reserve de PS20Caps).
Instructions des Pixel Shaders
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
4. Modificateurs de lecture et d’écriture, Swizzling.
Modificateurs de source: idem VS (négation et valeur absolue).
Swizzling de source: même principe que le VS.
VS2.x et VS3 : Swizzling arbitraire supporté
VS2: seulement .x .y .z .w .xyzw .yzxw .zxyw .wzyx, sauf si D3DPS20CAPS_ARBITRARYSWIZZLE.
Masque d’écriture de la destination: idem VS.
Adressage relatif: non disponible (pas de registre a0 non plus).
5. Contrôles de flux, limite du contrôle de flux, limite d’accès aux registres
idem VS (ce qui marche pour VS2.0, VS2.x, VS3.0 marche
respectivement pour PS2.0, PS2.x, PS3.0 avec les mêmes
contraintes).
6. Modificateur d’instructions
instruction_sat : instruction + saturation des composants du registre de destination entre 0 et 1.
instruction_pp : indique que l’instruction doit s’exécuter de préférence en précision partielle (gain
de vitesse fonction du GPU). Également utilisable sur les déclarations.
instruction_centroid (PS30) modifie la façon dont les coordonnées de texture sont choisies
lors du multisampling.
exemple: dcl_texcoord0_pp_centroid v0
texld_pp_centroid r0, v0, s0
add_sat_pp r0, r0, r1
Pixel Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Détection des fonctionnalités
Comme d’habitude: à travers les Caps:
D3DCAPS9 pCaps;
pDEV->GetDeviceCaps(&pCaps);
Pour tester qu’un code PS2.0 pourra être exécuté sur le Device, on examine le contenu
du champ PixelShaderVersion de pCaps:
pCaps.PixelShaderVersion >= D3DPS_VERSION(2,0)
récupération de la version shader: idem VS: macros D3DSHADER_VERSION_MAJOR et
D3DSHADER_VERSION_MINOR).
Autres capacités du PS dans pCaps:
tester pCaps.x où x parmi:
PixelShader1xMaxValue : valeur
maximale acceptée par les registres du
PS.
MaxPShaderInstructionExecuted :
nombre maximum d’instructions
exécutables par un PS (-1 = infini).
MaxPixelShader30InstructionSlots :
nombre maximum de slots [x]
disponibles pour un PS3.
Autres capacités spécifiques au PS2 et PS2.x
tester pCaps.PS20Caps.x où x parmi:
NumTemps, DynamicFlowControlDepth,
StaticFlowControlDepth: voir VS caps.
NumInstructionSlots: nombre maximum de
slots disponibles pour un PS2.
Caps & D3DPS20CAPS_y > 0 où y est parmi:
ARBITRARYSWIZZLE : swizzle arbitraire autorisé.
GRADIENTINSTRUCTIONS : instruction dsx, dsy, texldd supportés.
PREDICATION : registre prédicat p0 supporté.
NODEPENDENTREADLIMIT : en lien avec la capacité de lire une EM ou
une bumpmap dont la taille n’est pas 2nx2p (lecture dépendante de
celle de la texture).
NOTEXINSTRUCTIONLIMIT : le nombre de slots utilisé par une
instruction tex* est réduit (texld = 4 slots sans, 1 seul si activé).
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Compilation et chargement d’un
pixel shader
Un PS est souvent créé sous forme d’un fichier texte (ex:
myshader.psh).
Méthode « manuelle »
Dans un terminal, utiliser le compilateur psa.exe
C:\DX90SDK\Utilities> psa myshader.psh
Microsoft (R) D3DX9 Shader Assembler 4.09.00.1126
Copyright (C) Microsoft Corporation 2002-2003. All rights reserved.
assembly succeeded; see myshader.pso
Le fichier pso créé doit ensuite être chargé dans un buffer de la mémoire.
Méthode interne
Comme pour un VS, la fonction D3DXAssembleShaderFromFile
permet de charger et compiler le pixel shader (pour le détail, voir la
section correspondante pour les VS).
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Création et utilisation
d’un pixel shader
Création de l’interface pixel shader
avec la méthode du Device:
CreatePixelShader(pShaderObj,pShader)
pShaderObj : pointeur sur le code objet (DWORD *).
pShader : [out] pointeur de type LPDIRECT3DPIXELSHADER9.
Initialisation des textures (également pour un VS3)
SetTexture(n,pTexture)
où n est le numéro de l’étage (cette texture sera accessible grâce à travers le sampler sn) et
pTexture un pointeur sur l’interface d’un objet de type texture.
Notes:
1.
utiliser SetSamplerState pour fixer la façon dont l’échantillonneur renvoie la valeur dans la texture aux
coordonnées spécifiées.
2.
le Caps MaxSimultaneousTextures contient le nombre maximum d’étages utilisable en même temps
dans le PS.
Utilisation d’un pixel shader
avec la méthode du Device:
SetPixelShader(pShader)
pShader : pointeur LPDIRECT3DPIXELSHADER9 créé par CreatePixelShader, ou NULL pour
rétablir le PS du pipeline fixe.
ATTENTION: vérifier que l’entrée du PS utilisé est compatible avec la sortie du VS fixé.
Rappel: le PS est exécuté autant de fois que de pixels dessinés par les facettes issues de VS.
Pixel Shader
Exemple 1: PS minimal
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
// Format classique
// Format des sommets
typedef struct {
D3DVECTOR
P;
D3DVECTOR
N;
DWORD
diff;
FLOAT
u,v;
} VERTEX;
// FVF déclaré et utilisé
const int D3DFVF_VERTEX =
(D3DFVF_XYZ | D3DFVF_NORMAL |
D3DFVF_DIFFUSE | D3DFVF_TEX1);
// Fixe le FVF
pDEV->SetFVF( D3DFVF_VERTEX );
// Variable globale
LPDIRECT3DPIXELSHADER9 pPS0 = NULL; // Pixel Shader
// Initialisation du Shader
HRESULT InitShaders(void) {
HRESULT
s;
LPD3DXBUFFER
pShadObj, pErrMsg;
// compilation du vertex shader
s = D3DXAssembleShaderFromFile("05_BasicPS.psh", NULL, NULL, 0,
&pShadObj, &pErrMsg);
if FAILED(s) {
MessageBox(NULL, (char *)(pErrMsg->GetBufferPointer()),
"VS compilation error", MB_OK);
SAFE_RELEASE(pErrMsg);
return E_FAIL; }
// creation du Pixel Shader
pDEV->CreatePixelShader((DWORD *)pShadObj->GetBufferPointer(), &pPS0);
SAFE_RELEASE(pErrMsg);
SAFE_RELEASE(pShadObj);
return S_OK;
}
// Lors de la boucle de rendu
pDEV->SetTexture(0,pTX0);
// fixe le pixel shader
pDEV->SetPixelShader(pPS0);
// dessin de la primitive (SetFVF avant)
pDEV->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,nbvert,0,nbface);
Notes:
• on conserve le VS du pipeline graphique fixe.
• on utilise l’éclairage classique (lampe directionnelle, pas de matière).
• on définit une texture (damier). Les étages ne sont pas configurés.
Pixel Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Exemple 1: PS minimal (suite)
// v1 : couleur diffuse seulement
ps_2_0
dcl v0 // couleur diffuse interp.
mov oC0, v0 // copie sur sortie
// v2 : modulation diffus-texture
ps_2_0
// declaration
dcl v0 // coul. diffuse interp.
dcl t0 // coord. 0 texture interp.
dcl_2d s0 // sampler pour pTX0
// code
texld r0, t0,s0 // ech. texture
mul r1, v0,r0
mov oC0, r1
// v3 : texkill sur texture < 0.5
ps_2_0
// declaration
dcl v0 // coul. diffuse interp.
dcl t0 // coord. 0 texture interp.
dcl_2d s0 // sampler pour pTX0
// constante
def c0, 2,1,0,0.5
// code
texld r0, t0,s0 // ech. texture
sub r1, r0,c0.w
texkill r1 // si texture < 0.5
mul r1, v0,r0 // rendu normal
mov oC0, r1
Pixel Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Exemple 2: Texture procédurale
Code de base:
idem exemple 1 (sans lampe et sans texture).
Objectif:
définir une texture à partir des coordonnées.
ps_2_0
// declaration
dcl v0 // couleur diffuse interpolee
dcl t0 // coord. texture 2D entre 0 et 1
// constante
def c0, 0,1,2,0.5
def c1, 8,4,0,0
// code
mul r0.xy, t0,c1
frc r1.xy, r0
sub r2.xy, r0,r1
dp2add r0.x, r2,c0.y,c0.x
mul r1.x, r0.x,c0.w
frc r1.y, r1.x
mul r1.z, r1.y,c0.z
mul r3, v0,r1.z
mov oC0, r3
//
//
//
//
//
//
//
//
facteur d'echelle sur la texture
r1 = parties fractionnaires de x,y
r2 = [x],[y]
r0.x = [x] + [y] + 0
r1.x = r0.x / 2
r1.y = frc( r1.x )
r1.z = 2 * r1.y
modulation par la couleur
Pixel Shader
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Exemple 3: ombrage de Phong
On utilise un VS et un PS:
• on utilise le même FVF que dans l’exemple 1.
• le VS utilise les coordonnées de texture pour transmettre les données
géométriques au PS: normale à la surface, direction de la source, et
position.
• les données passées au PS sont interpolées (naturellement) sur la facette.
• on utilise pour l’exemple un modèle de Phong simplifié:
N.L + (R.V)p
/********** Déclaration des paramètres ************/
// position de l'observateur dans le repere de l'objet
D3DXVECTOR4 Opos,Lpos;
// position de la source dans le repere global
D3DXVECTOR3 ObsPos = D3DXVECTOR3( 0.0f,-2.5f, 0.0f );
D3DXVECTOR3 LightPos = D3DXVECTOR3( 0.0f,-2.5f, 0.0f );
// positions dans le repere local de l'objet
D3DXVec3Transform(&Opos,&ObsPos,&IMWorld);
D3DXVec3Transform(&Lpos,&LightPos,&IMWorld);
// caractéristiques de la lampe
// couleur
D3DCOLORVALUE LightCol={1.f, 1.f, 1.f, 1.f};
// exposant spéculaire
D3DXVECTOR3 LightCff = D3DXVECTOR3( 20.f, 0.f, 0.f );
/******************* vertex shader *******************/
// c0-3 = transformation complete
pDEV->SetVertexShaderConstantF(0,(float *)&TMFull,4);
// c4 = position de la lampe
pDEV->SetVertexShaderConstantF(4,(float *)&LightPos,1);
pDEV->SetVertexShader(pVS0);
/******************* pixel shader *******************/
D3DCOLORVALUE LightCol={1.f, 1.f, 1.f, 1.f};
D3DXVECTOR3 LightCff = D3DXVECTOR3( 20.f, 0.f, 0.f );
pDEV->SetPixelShaderConstantF(0,(float *)&Lpos,1);
pDEV->SetPixelShaderConstantF(1,(float *)&LightCol,1);
pDEV->SetPixelShaderConstantF(2,(float *)&LightCff,1);
pDEV->SetPixelShaderConstantF(3,(float *)&Opos,1);
pDEV->SetPixelShader(pPS0);
Pixel Shader
U N IVERSIT É
D E R EIMS
Exemple 3: ombrage de Phong (shaders)
C H AMP AGN E-A RD EN N E
vs_2_0
// paramètres en entrée (depuis le FVF)
dcl_position v0
dcl_normal
v1
dcl_color
v2
dcl_texcoord v3
// constantes fixees avec SetVertexShaderConstant
// c0,c1,c2,c3 = matrice de transformation
// c4 = position de la lampe
// calcul du vecteur L (normalisé)
sub r0, c4,v0 // direction de la source L
nrm r1, r0
// normalisation de L
// transformation du point
m4x4 r0, v0,c0
mov oPos, r0
// recopie en sortie
mov oT0,v3 // coordonneses de texture
mov oT1,v1 // normale
mov oT2,r1 // direction de la source
mov oT3,v0 // position
// recopie la couleur du vertex
mov oD0,v2
ps_2_0
// déclaration
dcl v0
// couleur diffuse interpolée
dcl t0
// premier ensemble de coordonnées
dcl t1
// normale interpolée
dcl t2
// direction de la source interpolée
dcl t3
// position (Pos)
dcl_2d s0
// sampler pour pTX0
// const: c1:couleur, c2:coeff, c3:observateur (Obs)
def c4, 2,0,1,0.5
// code
texld r0, t0,s0 // valeur de la texture
// normalisation des vecteurs interpolés
nrm r1, t1
// r1 = N
nrm r2, t2
// r2 = L
dp3 r9.x, r1,r2
// r9.x = N.L
// calcul de la direction de l'observateur
sub r4, c3,t3 // -- r4 = v non normalisé = Obs-Pos
nrm r3, r4
// r3 = V
// calcul de la direction du vecteur réfléchi
mul r9.y, r9.x,c4.x // r9.y = 2.(N.L)
mul r5, r9.y,r1
// -- r5 = 2.(N.L).N
sub r4, r5,r2
// r4 = R = 2.(N.L).N - L
dp3_sat r9.z, r3,r4 // r9.z = R.V
// calcul de la luminance
// partie diffuse
mul r1, r0,v0
// r1 = Ctexture x Cobjet
mul r0, r1,r9.x
// r0 = Ld
// partie spéculaire
pow r9.w, r9.z,c2.x
// r9.w = (R.V)^ns
add r0, r0,r9.w
// r0 = Ld + Ls
// modulation par la couleur de la source
mul r0, r0,c1
mov oC0, r0
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Profil des
pixels
shaders
disponibles
Profils disponibles:
PS_2_a
PS_2_b
PS_3_0
Dependant Texture Limit
4
No Limit
4
No Limit
Texture Instruction Limit
32
Unlimited
Unlimited
Unlimited
Position Register
-
-
-
D
Instruction Slots
32 + 64
512
512
>= 512
Executed Instructions
32 + 64
512
512
>=65535
Interpolated Registers
2+8
2+8
2+8
10
Instruction Predication
-
D
-
D
Index Input Registers
-
-
-
a
Temp Registers
12
22
32
32
Constant Registers
32
32
32
224
Arbitrary Swizzling
-
D
-
D
Gradient Instructions
-
D
-
D
Loop Count Register
-
-
-
D
Face Register (2-sided Lighting)
-
-
-
D
Dynamic Flow Control
-
-
-
24
Dependant texture limit :
ps_2_0 : Radeon 9500 à 9800.
ps_2_a : GeForce FX.
ps_2_b : Radeon X800
ps_3_0 : GeForce Serie 6.
En terme de fonctionnalité:
2.x = ps_2_a ou ps_2_b
PS_2_0
(cf les caps)
Récupération du profil:
D3DXGetPixelShaderProfile
une dépendance = l’appel à tex* se fait avec une
variable temporaire dont le résultat est lu dans une
texture (cf D3DPS20CAPS_NODEPENDENTREADLIMIT).
Texture Instruction Limit :
nombre maximum d’instruction tex* utilisable dans un
PS.
Instruction Slots :
x + y = texture + arithmétique.
Interpolated Registers:
x + y = 2 couleurs + 8 textures
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Outils de programmation
des VS et des PS
Développement:
Render Monkey : outil de développement de shaders d’ATI.
Cg Toolkit : outil de développement de shaders de nVidia
Shader City
Debugging:
L’extension DirectX de Visual .Net : cf “Shader Debugger” sur MSDN
Le debugging de shader ne fonctionne qu’avec le driver de référence
REF (et donc trèèèèèèèès lentement).
Optimisation:
VTune Performance Analyzer (Intel)
DLL Detective
Optimisations
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
VS et PS
Aspects basiques:
•
•
•
•
Eviter d’utiliser un shader 1.x sur un GPU 2.x ou 3.x car celui-ci est alors
souvent émulé (et donc plus lent).
Plus le code est court, meilleur il est.
Déporter les calculs constants dans un VS ou un PS à l’extérieur de celui-ci.
SetVertexShader est encore plus lent que SetTexture. Optimiser en
conséquence (trier les objets par VS, puis par textures).
Aspects avancés:
•
•
•
Limiter le nombre de registres temporaires est souvent une bonne chose.
Utiliser (à outrance) le Swizzling et les masques d’écriture afin de
maximiser les chance du code d’être optimisé par le driver (exemple:
certains GPU ont des pipelines spécifiques pour le RGB et le alpha).
Eviter les dépendances entre lignes qui empêchent le pipelining potentiel
des instructions (optimisation du PS/VS par le driver).
Conclusion
U N IVERSIT É
D E R EIMS
C H AMP AGN E-A RD EN N E
Conseils de lecture:
Real-time Shader Programming (Fosner)
bonne référence, peu technique (se concentre sur l’assembleur).
DX9 Programmable Graphics Pipeline (Gray)
chapitres 1 à 5
ShaderX2 – Introduction & Tutorial (Engel)
Introduction to the VS3 & PS3 Shader Models
Advanced Lighting and Shading with DX9
Pour aller plus loin:
ShaderX2 – Shader Programming (Engel)
Toutes les techniques pour mettre en place vos effets.
Voir aussi les documents et les codes sur le serveur.