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.
Documents pareils
Pipeline Graphique Programmable: Vertex et Pixel Shaders
La fonction D3DXCompileShaderFromFile permet de charger et compiler un code HLSL:
D3DXCompileShaderFromFile(fname, NULL, NULL, entry, profil, flags, &pShaderBuf, &pErrMsg , &pCstTable)
fname : nom ...