David Catuhe
Conception avancée de shaders DirectX 10.0
Mise en place d’un mécanisme maintenable et performant pour la gestion des shaders générique avec DirectX 10.0
Par David Catuhe publié le 28/10/2009 à 20:22, lu 2291 fois, 6 pages
 5 | Injection de code
Ce qui nous amène à notre dernière technique qui va allier maintenabilité et performances. Dans cette solution, nous allons modifier le code du shader à la volée pour qu’il ne contienne que le strict nécessaire en termes de HLSL.
Pour ce faire nous allons créer notre propre système de balisage qui définira dans le code les parties du shader qui dépendront ou non d’une variable.
Ce qui peut donner dans notre cas :

cbuffer cbGlobals

{

    float4 emissive;

};

 

struct VS_IN

{

    float4 pos : POSITION;

    float4 col : COLOR;

};

 

struct PS_IN

{

    float4 pos : SV_POSITION;

    float4 col : COLOR;

};

 

PS_IN VS( VS_IN input)

{

    PS_IN output = (PS_IN)0;

 

    output.pos = input.pos;

 

//<VertexColor>

        output.col += input.col;

//</VertexColor>

//<NotVertexColor>

        output.col += 0.1;

//</NotVertexColor>

 

//<EmissiveColor>

        output.col += emissive;

//</EmissiveColor>

 

    return output;

}

 

float4 PS( PS_IN input ) : SV_Target

{

    return input.col;

}

 

technique10 Render

{

    pass P0

    {

        SetGeometryShader( 0 );

        SetVertexShader( CompileShader( vs_4_0, VS() ) );

        SetPixelShader( CompileShader( ps_4_0, PS() ) );

    }

}

Comme nous pouvons le voir, la gestion du booléen VertexColor est prise en compte par une balise qui donne le comportement si VertexColor = true et par une autre balise qui donne le comportement si VertexColor = false.
Pour EmissiveColor, nous n’avons un comportement que si la variable est vraie.
Au niveau du code .Net, cela se traduit par une phase de pré-traitement de notre code HLSL pour remplacer les zones par du vide si nécessaire.
Pour réaliser cette tâche il suffit de rajouter la méthode suivante :

static string ProcessCode(string code, string var, bool value)

{

    if (!value)

    {

        Regex exp = new Regex(string.Format(@"(//\<{0}\>(.(?!//</{0}>))+.//\</{0}\>)", var),

            RegexOptions.Singleline);

 

        code = exp.Replace(code, "");

    }

    else

    {

        Regex exp = new Regex(string.Format(@"(//\<Not{0}\>(.(?!//</Not{0}>))+.//\</Not{0}\>)", var),

            RegexOptions.Singleline);

 

        code = exp.Replace(code, "");

    }

    return code;

}

Cette méthode utilise une expression régulière pour faire les remplacements.
Au final, la compilation du code donne donc :

string code = File.ReadAllText("MiniTri.fx");

code = ProcessCode(code, "VertexColor", false);

code = ProcessCode(code, "EmissiveColor", true);

 

var effect = Effect.FromString(device, code, "fx_4_0", ShaderFlags.None,

    EffectFlags.None, null, null);

Grâce à cette solution, il est possible de recompiler à la volée le shader pour l’adapter aux valeurs prises par nos booléens (en prenant soin de gérer un cache des versions déjà compilées).
De plus, dans le cadre ou la structure transitant entre le vertex et le pixel shader peut dépendre des options, cette solution permet de tailler parfaitement le contenu pour n’embarquer que le strict nécessaire :

struct PS_IN

{

    float4 pos : SV_POSITION;

    float4 col : COLOR;

//<Texture>

    float3 tex : TEXCOORD0;

//</Texture>       

};

 
» Démarrer une discussion