Frédéric Mélantois
WPF : Effets et Pixel Shaders
Le SP1 du Framework .NET 3.5 apporte à WPF un certain nombre d’améliorations dont les effets sur les images. Une « fenêtre » pour l’utilisation des pixel shaders de DirectX a été ouverte.
Par Frédéric Mélantois publié le 12/01/2009 à 09:06, lu 4024 fois, 6 pages
 3 | Développement du Pixel Shader en HLSL
Nous allons aborder une partie de développement qui vous est complètement étrangère si vous n’avez jamais développé sous DirectX. N’ayez crainte, vous allez voir que le langage HLSL (High Level Shader Language) est très abordable aux débutants. Ce langage a été conçu pour simplifier les développements des pixel shaders, ces mini-programmes s’exécutant sur le processeur de la carte graphique.
Il faut bien se rappeler que le Service Pack 1 du Framework 3.5 permet de gérer des effets sur les UIElement, comme nous l’avons vu dans le chapitre d’introduction, en utilisant la version 2.0 pour les pixel shaders afin d’assurer une plus grande prise en charge matérielle dans le parc informatique actuel. Les prochaines versions du Framework, notamment la 4.0, verront la prise en charge de versions postérieures à la 2.0. L’équipe WPF parle déjà pour les futures versions de la prise en charge des pixel shaders dans WPF 3D. S’ajouteront dans une logique d’amélioration de performance et de délégation matérielle vers la carte graphique, la prise en charge des Vertex shaders.
Que faut-il au développeur WPF pour créer ses propres pixel shaders ? Il lui faut un compilateur de shaders qui transformera son code HLSL en code assembleur. Celui-ci est fourni avec le SDK de DirectX. Il s’agit de l’utilitaire fxc.exe. Vous pouvez télécharger le SDK à cette adresse :
Lorsque vous aurez installé le SDK de DirectX, vous disposerez de tous les outils pour fabriquer vos propres pixel shaders. Vous trouverez l’utilitaire fxc dans le dossier « Utilities\Bin\ x86» du répertoire d’installation du SDK.
Dans le projet comportant le code de la classe héritant de ShaderEffect, celle que nous avons créé au chapitre précédent, ajoutez un nouveau fichier nommé « MyEffect.fx ». Dans ce fichier texte, nous allons écrire le code du pixel shader en HLSL.
Il faut préciser au sous-programme où se trouvera l’échantillon : dans le registre des échantillons (Sampler) à l’index 0 :

sampler2D ImplicitInput : register(s0);

Le langage HLSL est proche du c ou du c# concernant la syntaxe. « sampler2D » indique qu’il s’agit d’un échantillon. Celui-ci se trouve dans le registre s0. Il nous faut constituer le corps de notre mini-programme : le point d’entrée sera une méthode que nous nommerons « main » et qui devra renvoyer la couleur d’un pixel dont les composantes sont le rouge, le vert, le bleu et la couche alpha. Il faut savoir que les valeurs des composantes se situent entre 0 et 1 et non pas entre 0 et 255. C’est pourquoi ce qui sera renvoyé sera une structure composée de 4 float. En langage HLSL, ce type peut être noté « float4 » dans sa version prédéfinie. Le type de la valeur de retour de la méthode principale d’un pixel shader est un « float4 » :

float4 main (float2 uv : TEXCOORD) : COLOR

{

    float4 color;

    // traitement   

    return color;

}

La méthode « main » prend en paramètre une variable de type float2 qui correspond aux coordonnées d’un pixel ramenées au repère du plan (0,0) , (1,1). La variable uv contient la position du pixel en x : uv.x et la position du pixel en y : uv.y
C’est à partir de ces coordonnées que l’on peut récupérer la valeur de la couleur. Pour cela, HLSL offre une fonction tex2D permettant de récupérer sur l’échantillon (registre ImplicitInput) la couleur du pixel dont on transmet les coordonnées (variable uv). Pour notre démonstration, essayons d’inverser la couleur. Il s’agit de produire un effet négatif à notre photo. Si nous devions travailler sous GDI, nous appliquerions la formule « 255 – composante » c'est-à-dire (255 – R), (255 – G), (255 – B), la couche alpha n’étant pas modifiée. Nous avons vu précédemment que les valeurs des composantes de couleur se situent en 0 et 1. « 1 » va donc correspondre au « 255 » habituel. Nous avons donc le code suivant :

sampler2D ImplicitInput : register(s0);

 

float4 main (float2 uv : TEXCOORD) : COLOR

{

    float4 color;

    color = 1 - tex2D( ImplicitInput, uv) ;   

    color.a = 1;

    return color;

}

La fonction « text2D » récupère la valeur de la couleur aux coordonnées « uv » de l’échantillon « ImplicitInput ». Le calcul « 1-composante » peut s’effectuer en parallèle pour les quatre composantes : Rouge, vert, bleu, alpha.
Nous aurions pu écrire pour le même résultat :

color.r = 1 - tex2D( ImplicitInput, uv).r ;   

color.g = 1 - tex2D( ImplicitInput, uv).g ;   

color.b = 1 - tex2D( ImplicitInput, uv).b ;

Mais il faut bien comprendre que les instructions se seraient exécutées de manière successive. Donc par la ligne :

color = 1 - tex2D( ImplicitInput, uv) ;

Nous permettons une exécution parallèle sur chaque composante. C’est une notion à bien prendre en compte, quand on sait que la performance est importante pour les pixel shaders puisque les pixels sont traités un à un via le mini-programme. De plus, la version 2.0 de HLSL limite le nombre d’instruction à 64, donc cette réduction de code n’est pas sans importance lorsque notre mini-programme prend de l’importance en taille. Dans notre code, nous devons toutefois rétablir la couche alpha par :

color.a = 1;

Si nous voulions préserver les variations de la couche alpha, il conviendrait sans doute d’écrire plutôt ce code :

color.rgb = 1- tex2D( ImplicitInput, uv).rgb;

color.a = tex2D( ImplicitInput, uv).a;

Vous l’aurez compris le langage HLSL comprend quelques subtilités qui ne sont pas sans importance. Voici donc le code HLSL contenu dans le fichier MyEffect.fx :

sampler2D ImplicitInput : register(s0);

 

float4 main (float2 uv : TEXCOORD) : COLOR

{

    float4 color;

    color = 1 - tex2D( ImplicitInput, uv) ;   

    color.a = 1;

    return color;

}

Nous venons d’écrire le code permettant de réaliser un effet de négatif sur le visuel de n’importe quelle classe héritant de « UIElement ».
Il faut maintenant compiler ce code via l’utilitaire de Shaders de DirectX « fxc.exe ». Plutôt que de le compiler en ligne de commande, intégrons les directives de compilation en pré-compilation de notre projet « MyEffect » :
 
/content/13791ad3-9dcd-44e1-8694-84f713c4a868/image7.jpeg
 

"$(DXSDK_DIR)Utilities\Bin\x86\fxc.exe" /T ps_2_0 /E main

        /Fo "$(SolutionDir)MyEffect/MyEffect.ps" "$(SolutionDir)MyEffect/MyEffect.fx"

Le compilateur offre plusieurs options. « /T » précise le modèle. Avec le Service Pack 1 du Framework 3.5, seule la version 2.0 des shaders est pris en charge : « ps_2_0 ». L’option « /E » permet de préciser le point d’entrée du pixel shader « main ». « /Fo » permet de générer le fichier objet sur lequel pointera l’instance de la classe PixelShader dans le code C# que nous avons produit au précédent chapitre. Ce fichier devra être intégré au projet et mis en ressource.
Il existe une option intéressante : « /Fc », elle permet d’avoir un aperçu des instructions assembleur :

fxc /T ps_2_0 /E main /Fc MyEffect.txt MyEffect.fx

Voici le code généré :

//

// Generated by Microsoft (R) HLSL Shader Compiler 9.24.949.2307

//

//   fxc /T ps_2_0 /E main /Fc MyEffect.txt MyEffect.fx

//

//

// Parameters:

//

//   sampler2D ImplicitInput;

//

//

// Registers:

//

//   Name          Reg   Size

//   ------------- ----- ----

//   ImplicitInput s0       1

//

 

    ps_2_0

    def c0, -1, -1, -1, 1

    def c1, 0, 1, 1, 1

    dcl t0.xy

    dcl_2d s0

    texld r0, t0, s0

    mov r1, c0

    mad r2.xyz, r0, r1, c1.wzyx

    mad r2.w, r0.x, r1.w, c1.x

    mov oC0, r2

 

// approximately 5 instruction slots used (1 texture, 4 arithmetic)

Pour le code HLSL suivant :

sampler2D ImplicitInput : register(s0);

 

float4 main (float2 uv : TEXCOORD) : COLOR

{

    float4 color;

    color = 1 - tex2D( ImplicitInput, uv) ;   

    color.a = tex2D( ImplicitInput, uv) ;

    return color;

}

Comparons les instructions pour le code HLSL où nous indiquions que la syntaxe ne permettait pas de parallélisation de l’instruction de soustraction :

sampler2D ImplicitInput : register(s0);

 

float4 main (float2 uv : TEXCOORD) : COLOR

{

    float4 color;

    color.r = 1 - tex2D( ImplicitInput, uv).r ;   

    color.g = 1 - tex2D( ImplicitInput, uv).g ;   

    color.b = 1 - tex2D( ImplicitInput, uv).b ;   

    color.a = tex2D( ImplicitInput, uv) ;

    return color;

}

Sachez que le compilateur produit exactement le même code assembleur, c’est donc que le compilateur est capable de faire certaines optimisations ! Mais ne vous attendez pas à ce qu’il puisse optimiser toute votre logique dans certains cas. Il convient donc de prendre l’habitude d’optimiser votre code HLSL même si le compilateur « fxc » est doué d’optimisations.
Pour plus de détails sur les directives de compilation, veuillez regarder cette page de la MSDN :
Une fois le fichier « MyEffect.ps » généré, incorporez-le à votre projet. Puis n’oubliez pas de le mettre en ressource :
 
/content/13791ad3-9dcd-44e1-8694-84f713c4a868/image8.jpeg
 
Une fois le projet compilé, vous pouvez maintenant utiliser l’effet sur n’importe quelle classe dérivée de « UIElement ».
 
» Démarrer une discussion
 
Discussion démarée par tanuki le 11/05/2009 à 18:20, 1 commentaire(s).