Partager via


Utilisation d’un effet (Direct3D 9)

Cette page vous montre comment générer et utiliser un effet. Les sujets abordés incluent comment :

Créer un effet

Voici un exemple de création d’un effet à partir de l’exemple BasicHLSL. Le code de création d’effet pour la création d’un nuanceur de débogage provient de OnCreateDevice :

ID3DXEffect* g_pEffect = NULL;
DWORD dwShaderFlags = 0;

    dwShaderFlags |= D3DXSHADER_FORCE_VS_SOFTWARE_NOOPT;
    dwShaderFlags |= D3DXSHADER_FORCE_PS_SOFTWARE_NOOPT;
    dwShaderFlags |= D3DXSHADER_NO_PRESHADER;

    // Read the D3DX effect file
    WCHAR str[MAX_PATH];
    DXUTFindDXSDKMediaFileCch( str, MAX_PATH, L"BasicHLSL.fx" );

    D3DXCreateEffectFromFile( 
        pd3dDevice, 
        str, 
        NULL, // CONST D3DXMACRO* pDefines,
        NULL, // LPD3DXINCLUDE pInclude,
        dwShaderFlags, 
        NULL, // LPD3DXEFFECTPOOL pPool,
        &g_pEffect, 
        NULL );

Cette fonction accepte les arguments suivants :

  • Périphérique.
  • Nom de fichier du fichier d’effet.
  • Pointeur vers une liste de #defines terminée par null, à utiliser lors de l’analyse du nuanceur.
  • Pointeur facultatif vers un gestionnaire include écrit par l’utilisateur. Le gestionnaire est appelé par le processeur chaque fois qu’il a besoin de résoudre un #include.
  • Indicateur de compilation du nuanceur qui donne au compilateur des indications sur la façon dont le nuanceur sera utilisé. Ces options sont les suivantes :
    • Ignorer la validation, si les bons nuanceurs connus sont en cours de compilation.
    • Ignorer l’optimisation (parfois utilisée lorsque les optimisations rendent le débogage plus difficile).
    • Demande d’inclure des informations de débogage dans le nuanceur afin qu’elles puissent être déboguées.
  • Pool d’effets. Si plusieurs effets utilisent le même pointeur de pool de mémoire, les variables globales dans les effets sont partagées entre elles. S’il n’est pas nécessaire de partager des variables d’effet, le pool de mémoire peut être défini sur NULL.
  • Pointeur vers le nouvel effet.
  • Pointeur vers une mémoire tampon vers laquelle des erreurs de validation peuvent être envoyées. Dans cet exemple, le paramètre a été défini sur NULL et n’a pas été utilisé.

Notes

À compter du Kit de développement logiciel (SDK) de décembre 2006, le compilateur HLSL DirectX 10 est désormais le compilateur par défaut dans DirectX 9 et DirectX 10. Pour plus d’informations, consultez Effect-Compiler Tool .

 

Afficher un effet

La séquence d’appels pour appliquer l’état d’effet à un appareil est la suivante :

Le code de rendu d’effet est également plus simple que le code de rendu correspondant sans effet. Voici le code de rendu avec un effet :

// Apply the technique contained in the effect 
g_pEffect->Begin(&cPasses, 0);

for (iPass = 0; iPass < cPasses; iPass++)
{
    g_pEffect->BeginPass(iPass);

    // Only call CommitChanges if any state changes have happened
    // after BeginPass is called
    g_pEffect->CommitChanges();

    // Render the mesh with the applied technique
    g_pMesh->DrawSubset(0);

    g_pEffect->EndPass();
}
g_pEffect->End();

La boucle de rendu consiste à interroger l’effet pour voir le nombre de passes qu’il contient, puis à appeler tous les passes pour une technique. La boucle de rendu peut être développée pour appeler plusieurs techniques, chacune avec plusieurs passes.

Utiliser la sémantique pour rechercher des paramètres d’effet

Une sémantique est un identificateur qui est attaché à un paramètre d’effet pour permettre à une application de rechercher le paramètre. Un paramètre peut avoir au plus une sémantique. La sémantique se trouve à la suite d’un signe deux-points (:) après le nom du paramètre. Exemple :

float4x4 matWorldViewProj : WORLDVIEWPROJ;

Si vous avez déclaré la variable globale d’effet sans utiliser de sémantique, elle ressemblerait à ceci à la place :

float4x4 matWorldViewProj;

L’interface d’effet peut utiliser une sémantique pour obtenir un handle pour un paramètre d’effet particulier. Pour instance, la commande suivante retourne le handle de la matrice :

D3DHANDLE handle = 
    m_pEffect->GetParameterBySemantic(NULL, "WORLDVIEWPROJ");

En plus de la recherche par nom sémantique, l’interface d’effet a de nombreuses autres méthodes pour rechercher des paramètres.

Utiliser des descripteurs pour obtenir et définir des paramètres efficacement

Les handles fournissent un moyen efficace de référencer des paramètres d’effet, des techniques, des passes et des annotations avec un effet. Les handles (de type D3DXHANDLE) sont des pointeurs de chaîne. Les descripteurs passés dans des fonctions telles que GetParameterxxx ou GetAnnotationxxx peuvent être sous l’une des trois formes suivantes :

  • Handle retourné par une fonction telle que GetParameterxxx.
  • Chaîne contenant le nom du paramètre, de la technique, de la passe ou de l’annotation.
  • Handle défini sur NULL.

Cet exemple retourne un handle au paramètre auquel la sémantique WORLDVIEWPROJ est attachée :

D3DHANDLE handle = 
    m_pEffect->GetParameterBySemantic(NULL, "WORLDVIEWPROJ");

Ajouter des informations de paramètre avec des annotations

Les annotations sont des données spécifiques à l’utilisateur qui peuvent être attachées à n’importe quelle technique, passe ou paramètre. Une annotation est un moyen flexible d’ajouter des informations à des paramètres individuels. Les informations peuvent être lues et utilisées de la manière choisie par l’application. Une annotation peut être de n’importe quel type de données et peut être ajoutée dynamiquement. Les déclarations d’annotation sont délimitées par des crochets. Une annotation contient :

  • Type de données.
  • Nom de variable.
  • Signe égal (=).
  • Valeur de données.
  • Point-virgule de fin (;).

Par instance, les deux exemples précédents de ce document contiennent cette annotation :

texture Tex0 < string name = "tiger.bmp"; >;

L’annotation est attachée à l’objet de texture et spécifie le fichier de texture qui doit être utilisé pour initialiser l’objet de texture. L’annotation n’initialise pas l’objet de texture, il s’agit simplement d’un élément d’informations utilisateur qui est attaché à la variable. Une application peut lire l’annotation avec ID3DXBaseEffect::GetAnnotation ou ID3DXBaseEffect::GetAnnotationByName pour retourner la chaîne. Les annotations peuvent également être ajoutées par l’application.

Chaque annotation :

Paramètres d’effet de partage

Les paramètres d’effet sont toutes les variables non statiques déclarées dans un effet. Cela peut inclure des variables globales et des annotations. Les paramètres d’effet peuvent être partagés entre différents effets en déclarant des paramètres avec le mot clé « partagé », puis en créant l’effet avec un pool d’effets.

Un pool d’effets contient des paramètres d’effet partagés. Le pool est créé en appelant D3DXCreateEffectPool, qui retourne une interface ID3DXEffectPool . L’interface peut être fournie en tant qu’entrée à l’une des fonctions D3DXCreateEffectxxx lorsqu’un effet est créé. Pour qu’un paramètre soit partagé entre plusieurs effets, le paramètre doit avoir le même nom, le même type et la même sémantique dans chacun des effets partagés.

ID3DXEffectPool* g_pEffectPool = NULL;   // Effect pool for sharing parameters

    D3DXCreateEffectPool( &g_pEffectPool );

Les effets qui partagent des paramètres doivent utiliser le même appareil. Cela est appliqué pour empêcher le partage de paramètres dépendants de l’appareil (tels que des nuanceurs ou des textures) entre différents appareils. Les paramètres sont supprimés du pool chaque fois que les effets qui contiennent les paramètres partagés sont libérés. Si le partage de paramètres n’est pas nécessaire, indiquez NULL pour le pool d’effets lors de la création d’un effet.

Les effets clonés utilisent le même pool d’effets que l’effet à partir duquel ils sont clonés. Le clonage d’un effet fait une copie exacte d’un effet, y compris les variables globales, les techniques, les passes et les annotations.

Compiler un effet hors connexion

Vous pouvez compiler un effet au moment de l’exécution avec D3DXCreateEffect, ou vous pouvez compiler un effet hors connexion à l’aide de l’outil de compilateur en ligne de commande fxc.exe. L’effet dans l’exemple CompiledEffect contient un nuanceur de vertex, un nuanceur de pixels et une technique :

// File: CompiledEffect.fx

// Global variables
float4 g_MaterialAmbientColor;    // Material's ambient color
...

// Texture samplers
sampler RenderTargetSampler = 
   ...

// Type: Vertex shader                                      
VS_OUTPUT RenderSceneVS( float4 vPos : POSITION, 
                         float3 vNormal : NORMAL,
                         float2 vTexCoord0 : TEXCOORD0 )
{
   ...
};
// Type: Pixel shader
PS_OUTPUT RenderScenePS( VS_OUTPUT In ) 
{ 
   ...
}

// Type: Technique                                     
technique RenderScene
{
    pass P0
    {          
        ZENABLE = true;
        VertexShader = compile vs_1_1 RenderSceneVS();
        PixelShader  = compile ps_1_1 RenderScenePS();
    }
}

L’utilisation de l’outil Effect-Compiler Tool pour compiler le nuanceur pour vs_1_1 généré les instructions suivantes du nuanceur d’assembly :

//
// Generated by Microsoft (R) D3DX9 Shader Compiler 4.09.02.1188
//
//   fxc /T vs_1_1 /E RenderSceneVS /Fc CompiledEffect.txt CompiledEffect.fx
//
//
// Parameters:
//
//   float4 g_LightAmbient;
//   float4 g_LightDiffuse;
//   float3 g_LightDir;
//   float4 g_MaterialAmbientColor;
//   float4 g_MaterialDiffuseColor;
//   float g_fTime;
//   float4x4 g_mWorld;
//   float4x4 g_mWorldViewProjection;
//
//
// Registers:
//
//   Name                   Reg   Size
//   ---------------------- ----- ----
//   g_mWorldViewProjection c0       4
//   g_mWorld               c4       3
//   g_MaterialAmbientColor c7       1
//   g_MaterialDiffuseColor c8       1
//   g_LightDir             c9       1
//   g_LightAmbient         c10      1
//   g_LightDiffuse         c11      1
//   g_fTime                c12      1
//
//
// Default values:
//
//   g_LightDir
//     c9   = { 0.57735, 0.57735, 0.57735, 0 };
//
//   g_LightAmbient
//     c10  = { 1, 1, 1, 1 };
//
//   g_LightDiffuse
//     c11  = { 1, 1, 1, 1 };
//

    vs_1_1
    def c13, 0.159154937, 0.25, 6.28318548, -3.14159274
    def c14, -2.52398507e-007, 2.47609005e-005, -0.00138883968, 0.0416666418
    def c15, -0.5, 1, 0.5, 0
    dcl_position v0
    dcl_normal v1
    dcl_texcoord v2
    mov r0.w, c12.x
    mad r0.w, r0.w, c13.x, c13.y
    expp r3.y, r0.w
    mov r0.w, r3.y
    mad r0.w, r0.w, c13.z, c13.w
    mul r0.w, r0.w, r0.w
    mad r1.w, r0.w, c14.x, c14.y
    mad r1.w, r0.w, r1.w, c14.z
    mad r1.w, r0.w, r1.w, c14.w
    mad r1.w, r0.w, r1.w, c15.x
    mad r0.w, r0.w, r1.w, c15.y
    mul r0.w, r0.w, v0.x
    mul r0.x, r0.w, c15.z
    dp3 r1.x, v1, c4
    dp3 r1.y, v1, c5
    dp3 r1.z, v1, c6
    mov r0.yzw, c15.w
    dp3 r2.x, r1, r1
    add r0, r0, v0
    rsq r1.w, r2.x
    dp4 oPos.x, r0, c0
    mul r1.xyz, r1, r1.w
    dp4 oPos.y, r0, c1
    dp3 r1.x, r1, c9
    dp4 oPos.z, r0, c2
    max r1.w, r1.x, c15.w
    mov r1.xyz, c8
    mul r1.xyz, r1, c11
    mov r2.xyz, c7
    mul r2.xyz, r2, c10
    dp4 oPos.w, r0, c3
    mad oD0.xyz, r1, r1.w, r2
    mov oD0.w, c15.y
    mov oT0.xy, v2

// approximately 34 instruction slots used

Améliorer les performances avec les preshaders

Un préformateur est une technique permettant d’augmenter l’efficacité du nuanceur en précalquant des expressions de nuanceur constantes. Le compilateur d’effets extrait automatiquement les calculs du nuanceur du corps d’un nuanceur et les exécute sur le processeur avant l’exécution du nuanceur. Par conséquent, les preshaders ne fonctionnent qu’avec des effets. Par instance, ces deux expressions peuvent être évaluées en dehors du nuanceur avant son exécution.

mul(World,mul(View, Projection));
sin(time)

Les calculs de nuanceur qui peuvent être déplacés sont ceux associés à des paramètres uniformes ; autrement dit, les calculs ne changent pas pour chaque sommet ou pixel. Si vous utilisez des effets, le compilateur d’effets génère et exécute automatiquement un préformeur pour vous ; il n’y a aucun indicateur à activer. Les preshaders peuvent réduire le nombre d’instructions par nuanceur et peuvent également réduire le nombre de registres constants consommés par un nuanceur.

Considérez le compilateur d’effet comme une sorte de compilateur multiprocesseur, car il compile le code du nuanceur pour deux types de processeurs : un processeur et un GPU. En outre, le compilateur d’effets est conçu pour déplacer le code du GPU vers le processeur et ainsi améliorer les performances du nuanceur. Cela est très similaire à l’extraction d’une expression statique hors d’une boucle. Un nuanceur qui transforme la position de l’espace du monde en espace de projection et copie les coordonnées de texture ressemble à ceci dans HLSL :

float4x4 g_mWorldViewProjection;    // World * View * Projection matrix
float4x4 g_mWorldInverse;           // Inverse World matrix
float3 g_LightDir;                  // Light direction in world space
float4 g_LightDiffuse;              // Diffuse color of the light

struct VS_OUTPUT
{
    float4 Position   : POSITION;   // vertex position 
    float2 TextureUV  : TEXCOORD0;  // vertex texture coords 
    float4 Diffuse    : COLOR0;     // vertex diffuse color
};

VS_OUTPUT RenderSceneVS( float4 vPos : POSITION, 
                         float3 vNormal : NORMAL,
                         float2 vTexCoord0 : TEXCOORD0)
{
    VS_OUTPUT Output;
    
    // Transform the position from object space to projection space
    Output.Position = mul(vPos, g_mWorldViewProjection);

    // Transform the light from world space to object space    
    float3 vLightObjectSpace = normalize(mul(g_LightDir, (float3x3)g_mWorldInverse)); 

    // N dot L lighting
    Output.Diffuse = max(0,dot(vNormal, vLightObjectSpace));
    
    // Copy the texture coordinate
    Output.TextureUV = vTexCoord0; 
    
    return Output;    
}
technique RenderVS
{
    pass P0
    {          
        VertexShader = compile vs_1_1 RenderSceneVS();
    }
}

L’utilisation de l’outil Effect-Compiler tool pour compiler le nuanceur pour vs_1_1 génère les instructions d’assembly suivantes :

technique RenderVS
{
    pass P0
    {
        vertexshader = 
            asm {
            //
            // Generated by Microsoft (R) D3DX9 Shader Compiler 9.15.779.0000
            //
            // Parameters:
            //
            //   float3 g_LightDir;
            //   float4x4 g_mWorldInverse;
            //   float4x4 g_mWorldViewProjection;
            //
            //
            // Registers:
            //
            //   Name                   Reg   Size
            //   ---------------------- ----- ----
            //   g_mWorldViewProjection c0       4
            //   g_mWorldInverse        c4       3
            //   g_LightDir             c7       1
            //
            
                vs_1_1
                def c8, 0, 0, 0, 0
                dcl_position v0
                dcl_normal v1
                dcl_texcoord v2
                mov r1.xyz, c7
                dp3 r0.x, r1, c4
                dp3 r0.y, r1, c5
                dp3 r0.z, r1, c6
                dp4 oPos.x, v0, c0
                dp3 r1.x, r0, r0
                dp4 oPos.y, v0, c1
                rsq r0.w, r1.x
                dp4 oPos.z, v0, c2
                mul r0.xyz, r0, r0.w
                dp4 oPos.w, v0, c3
                dp3 r0.x, v1, r0
                max oD0, r0.x, c8.x
                mov oT0.xy, v2
            
            // approximately 14 instruction slots used
            };

        //No embedded pixel shader
    }
}

Cela utilise jusqu’à environ 14 emplacements et consomme 9 registres constants. Avec un préformeur, le compilateur déplace les expressions statiques hors du nuanceur et dans le préformeur. Le même nuanceur ressemblerait à ceci avec un préformateur :

technique RenderVS
{
    pass P0
    {
        vertexshader = 
            asm {
            //
            // Generated by Microsoft (R) D3DX9 Shader Compiler 9.15.779.0000
            //
            // Parameters:
            //
            //   float3 g_LightDir;
            //   float4x4 g_mWorldInverse;
            //
            //
            // Registers:
            //
            //   Name            Reg   Size
            //   --------------- ----- ----
            //   g_mWorldInverse c0       3
            //   g_LightDir      c3       1
            //
            
                preshader
                dot r0.x, c3.xyz, c0.xyz
                dot r0.y, c3.xyz, c1.xyz
                dot r0.z, c3.xyz, c2.xyz
                dot r1.w, r0.xyz, r0.xyz
                rsq r0.w, r1.w
                mul c4.xyz, r0.w, r0.xyz
            
            // approximately 6 instructions used
            //
            // Generated by Microsoft (R) D3DX9 Shader Compiler 9.15.779.0000
            //
            // Parameters:
            //
            //   float4x4 g_mWorldViewProjection;
            //
            //
            // Registers:
            //
            //   Name                   Reg   Size
            //   ---------------------- ----- ----
            //   g_mWorldViewProjection c0       4
            //
            
                vs_1_1
                def c5, 0, 0, 0, 0
                dcl_position v0
                dcl_normal v1
                dcl_texcoord v2
                dp4 oPos.x, v0, c0
                dp4 oPos.y, v0, c1
                dp4 oPos.z, v0, c2
                dp4 oPos.w, v0, c3
                dp3 r0.x, v1, c4
                max oD0, r0.x, c5.x
                mov oT0.xy, v2
            
            // approximately 7 instruction slots used
            };

        //No embedded pixel shader
    }
}

Un effet exécute un préformateur juste avant l’exécution d’un nuanceur. Le résultat est la même fonctionnalité avec des performances accrues du nuanceur, car le nombre d’instructions qui doivent être exécutées (pour chaque sommet ou pixel selon le type de nuanceur) a été réduit. En outre, moins de registres de constantes sont consommés par le nuanceur en raison du déplacement des expressions statiques vers le préformateur. Cela signifie que les nuanceurs précédemment limités par le nombre de registres de constantes dont ils avaient besoin peuvent désormais être compilés, car ils nécessitent moins de registres de constantes. Il est raisonnable de s’attendre à une amélioration des performances de 5 % et de 20 % par rapport aux préados.

Gardez à l’esprit que les constantes d’entrée sont différentes des constantes de sortie dans un préformeur. La sortie c1 n’est pas identique au registre c1 d’entrée. L’écriture dans un registre de constantes dans un préformateur écrit en fait dans l’emplacement d’entrée (constante) du nuanceur correspondant.

// BaseDelta c0 1
// Refinements c1 1
preshader
mul c1.x, c0.x, (-2)
add c0.x, c0.x, c0.x
cmp c5.x, c1.x, (1), (0)

L’instruction cmp ci-dessus lit la valeur c1 préformeuse, tandis que l’instruction mul écrit dans les registres du nuanceur matériel à utiliser par le nuanceur de vertex.

Utiliser des blocs de paramètres pour gérer les paramètres d’effet

Les blocs de paramètres sont des blocs de modifications d’état d’effet. Un bloc de paramètres peut enregistrer les modifications d’état, afin qu’elles puissent être appliquées ultérieurement avec un seul appel. Créez un bloc de paramètres en appelant ID3DXEffect::BeginParameterBlock :

    m_pEffect->SetTechnique( "RenderScene" );

    m_pEffect->BeginParameterBlock();
    D3DXVECTOR4 v4( Diffuse.r, Diffuse.g, Diffuse.b, Diffuse.a );
    m_pEffect->SetVector( "g_vDiffuse", &v4 );
    m_pEffect->SetFloat( "g_fReflectivity", fReflectivity );
    m_pEffect->SetFloat( "g_fAnimSpeed", fAnimSpeed );
    m_pEffect->SetFloat( "g_fSizeMul", fSize );
    m_hParameters = m_pEffect->EndParameterBlock();

Le bloc de paramètres enregistre quatre modifications appliquées par les appels d’API. L’appel de ID3DXEffect::BeginParameterBlock commence à enregistrer les modifications d’état. ID3DXEffect::EndParameterBlock cesse d’ajouter les modifications au bloc de paramètres et retourne un handle. Le handle sera utilisé lors de l’appel de ID3DXEffect::ApplyParameterBlock.

Dans l’exemple EffectParam, le bloc de paramètres est appliqué dans la séquence de rendu :

CObj g_aObj[NUM_OBJS];       // Object instances

    if( SUCCEEDED( pd3dDevice->BeginScene() ) )
    {
        // Set the shared parameters using the first mesh's effect.

        // Render the mesh objects
        for( int i = 0; i < NUM_OBJS; ++i )
        {
            ID3DXEffect *pEffect = g_aObj[i].m_pEffect;

            // Apply the parameters
            pEffect->ApplyParameterBlock( g_aObj[i].m_hParameters );

            ...

            pEffect->Begin( &cPasses, 0 );
            for( iPass = 0; iPass < cPasses; iPass++ )
            {
              ...
            }
            pEffect->End();
        }

        ...
        pd3dDevice->EndScene();
    }

Le bloc de paramètres définit la valeur des quatre changements d’état juste avant l’appel d’ID3DXEffect::Begin . Les blocs de paramètres sont un moyen pratique de définir plusieurs changements d’état avec un seul appel d’API.

Effets