Partager via


Prise en main de l’étape de sortie de flux

Cette section explique comment utiliser un nuanceur de géométrie avec l’étape de sortie de flux.

Compiler un nuanceur de géométrie

Ce nuanceur de géométrie (GS) calcule une face normale pour chaque triangle et retourne les données de coordonnées de position, normales et de texture.

struct GSPS_INPUT
{
    float4 Pos : SV_POSITION;
    float3 Norm : TEXCOORD0;
    float2 Tex : TEXCOORD1;
};

[maxvertexcount(12)]
void GS( triangle GSPS_INPUT input[3], inout TriangleStream<GSPS_INPUT> TriStream )
{
    GSPS_INPUT output;
    
    //
    // Calculate the face normal
    //
    float3 faceEdgeA = input[1].Pos - input[0].Pos;
    float3 faceEdgeB = input[2].Pos - input[0].Pos;
    float3 faceNormal = normalize( cross(faceEdgeA, faceEdgeB) );
    float3 ExplodeAmt = faceNormal*Explode;
    
    //
    // Calculate the face center
    //
    float3 centerPos = (input[0].Pos.xyz + input[1].Pos.xyz + input[2].Pos.xyz)/3.0;
    float2 centerTex = (input[0].Tex + input[1].Tex + input[2].Tex)/3.0;
    centerPos += faceNormal*Explode;
    
    //
    // Output the pyramid
    //
    for( int i=0; i<3; i++ )
    {
        output.Pos = input[i].Pos + float4(ExplodeAmt,0);
        output.Pos = mul( output.Pos, View );
        output.Pos = mul( output.Pos, Projection );
        output.Norm = input[i].Norm;
        output.Tex = input[i].Tex;
        TriStream.Append( output );
        
        int iNext = (i+1)%3;
        output.Pos = input[iNext].Pos + float4(ExplodeAmt,0);
        output.Pos = mul( output.Pos, View );
        output.Pos = mul( output.Pos, Projection );
        output.Norm = input[iNext].Norm;
        output.Tex = input[iNext].Tex;
        TriStream.Append( output );
        
        output.Pos = float4(centerPos,1) + float4(ExplodeAmt,0);
        output.Pos = mul( output.Pos, View );
        output.Pos = mul( output.Pos, Projection );
        output.Norm = faceNormal;
        output.Tex = centerTex;
        TriStream.Append( output );
        
        TriStream.RestartStrip();
    }
    
    for( int i=2; i>=0; i-- )
    {
        output.Pos = input[i].Pos + float4(ExplodeAmt,0);
        output.Pos = mul( output.Pos, View );
        output.Pos = mul( output.Pos, Projection );
        output.Norm = -input[i].Norm;
        output.Tex = input[i].Tex;
        TriStream.Append( output );
    }
    TriStream.RestartStrip();
}

Gardez à l’esprit ce code et considérez qu’un nuanceur de géométrie ressemble beaucoup à un nuanceur de sommets ou de pixels, mais avec les exceptions suivantes : le type retourné par la fonction, les déclarations de paramètres d’entrée et la fonction intrinsèque.

Élément Description
Type de retour de la fonction
Le type de retour de la fonction fait une chose, il déclare le nombre maximal de sommets pouvant être générés par le nuanceur. Dans ce cas,
maxvertexcount(12)

définit la sortie à un maximum de 12 sommets.

Déclarations de paramètres d’entrée

Cette fonction prend deux paramètres d’entrée :

triangle GSPS_INPUT input[3] , inout TriangleStream<GSPS_INPUT> TriStream

Le premier paramètre est un tableau de sommets (3 dans ce cas) défini par une structure GSPS_INPUT (qui définit les données par sommet comme coordonnées de position, normales et de texture). Le premier paramètre utilise également le mot clé triangle, ce qui signifie que l’étape de l’assembleur d’entrée doit générer des données vers le nuanceur de géométrie comme l’un des types primitifs de triangle (liste de triangles ou bande de triangles).

Le deuxième paramètre est un flux de triangles défini par le type TriangleStream<GSPS_INPUT>. Cela signifie que le paramètre est un tableau de triangles, chacun composé de trois sommets (qui contiennent les données des membres de GSPS_INPUT).

Utilisez les mots clés triangle et trianglestream pour identifier des triangles individuels ou un flux de triangles dans un GS.

Fonction intrinsèque

Les lignes de code de la fonction de nuanceur utilisent des fonctions intrinsèques HLSL common-shader-core, à l’exception des deux dernières lignes, qui appellent Append et RestartStrip. Ces fonctions sont disponibles uniquement pour un nuanceur de géométrie. Append informe le nuanceur de géométrie d’ajouter la sortie à la bande actuelle ; RestartStrip crée une nouvelle bande primitive. Une nouvelle bande est implicitement créée dans chaque appel de la phase GS.

Le reste du nuanceur ressemble à un nuanceur de sommet ou de pixels. Le nuanceur de géométrie utilise une structure pour déclarer des paramètres d’entrée et marque le membre de position avec la sémantique SV_POSITION pour indiquer au matériel qu’il s’agit de données positionnelles. La structure d’entrée identifie les deux autres paramètres d’entrée en tant que coordonnées de texture (même si l’un d’entre eux contiendra une face normale). Vous pouvez utiliser votre propre sémantique personnalisée pour la face normale si vous préférez.

Après avoir conçu le nuanceur de géométrie, appelez D3DCompile pour compiler, comme illustré dans l’exemple de code suivant.

DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
ID3DBlob** ppShader;

D3DCompile( pSrcData, sizeof( pSrcData ), 
  "Tutorial13.fx", NULL, NULL, "GS", "gs_4_0", 
  dwShaderFlags, 0, &ppShader, NULL );

Tout comme les nuanceurs de sommet et de pixels, vous avez besoin d’un indicateur de nuanceur pour indiquer au compilateur comment vous souhaitez que le nuanceur soit compilé (pour le débogage, optimisé pour la vitesse, et ainsi de suite), la fonction de point d’entrée et le modèle de nuanceur à valider. Cet exemple crée un nuanceur de géométrie généré à partir du fichier Tutorial13.fx à l’aide de la fonction GS. Le nuanceur est compilé pour le modèle de nuanceur 4.0.

Créer un objet Geometry-Shader avec une sortie de flux

Une fois que vous savez que vous diffuserez en continu les données de la géométrie et que vous avez correctement compilé le nuanceur, l’étape suivante consiste à appeler ID3D11Device::CreateGeometryShaderWithStreamOutput pour créer l’objet de nuanceur de géométrie.

Mais tout d’abord, vous devez déclarer la signature d’entrée de l’étape de sortie de flux (SO). Cette signature correspond ou valide les sorties GS et les entrées SO au moment de la création de l’objet. Le code suivant est un exemple de déclaration SO.

D3D11_SO_DECLARATION_ENTRY pDecl[] =
{
    // semantic name, semantic index, start component, component count, output slot
    { "SV_POSITION", 0, 0, 4, 0 },   // output all components of position
    { "TEXCOORD0", 0, 0, 3, 0 },     // output the first 3 of the normal
    { "TEXCOORD1", 0, 0, 2, 0 },     // output the first 2 texture coordinates
};

D3D11Device->CreateGeometryShaderWithStreamOut( pShaderBytecode, ShaderBytecodesize, pDecl, 
    sizeof(pDecl), NULL, 0, 0, NULL, &pStreamOutGS );

Cette fonction prend plusieurs paramètres, notamment :

  • Un pointeur vers le nuanceur de géométrie compilé (ou le nuanceur de sommet si aucun nuanceur de géométrie ne sera présent et que les données seront diffusées directement à partir du nuanceur de sommet). Pour plus d’informations sur l’obtention de ce pointeur, consultez Obtention d’un pointeur vers un nuanceur compilé.
  • Un pointeur vers un tableau de déclarations qui décrivent les données d’entrée pour l’étape de sortie du flux. (Consultez D3D11_SO_DECLARATION_ENTRY.) Vous pouvez fournir jusqu’à 64 déclarations, une pour chaque type d’élément différent à générer à partir de l’étape SO. Le tableau d’entrées de déclaration décrit la disposition des données, que seule une mémoire tampon unique ou plusieurs mémoires tampons soient liées pour la sortie de flux.
  • Le nombre d’éléments écrits par l’étape SO.
  • Un pointeur vers l’objet nuanceur de géométrie créé (voir ID3D11GeometryShader).

Dans ce cas, la progression de la mémoire tampon est NULL, l’index du flux à envoyer au rastériseur est 0 et l’interface de liaison de classe a la valeur NULL.

La déclaration de sortie de flux définit la façon dont les données sont écrites dans une ressource de mémoire tampon. Vous pouvez ajouter autant de composants que vous souhaitez à la déclaration de sortie. Utilisez l’étape SO pour écrire dans une ressource de mémoire tampon unique ou dans de nombreuses ressources de mémoire tampon. Pour une mémoire tampon unique, l’étape SO peut écrire de nombreux éléments différents par sommet. Pour plusieurs mémoires tampons, l’étape SO ne peut écrire qu’un seul élément de données par sommet dans chaque mémoire tampon.

Pour utiliser l’étape SO sans utiliser de nuanceur de géométrie, appelez ID3D11Device::CreateGeometryShaderWithStreamOutput et passez un pointeur vers un nuanceur de sommet au paramètre pShaderBytecode.

Définir les cibles de sortie

La dernière étape consiste à définir les mémoires tampons de l’étape SO. Les données peuvent être diffusées en continu dans une ou plusieurs mémoires tampons en mémoire pour une utilisation ultérieure. Le code suivant montre comment créer une mémoire tampon unique qui peut être utilisée pour les données de sommet, ainsi que pour l’étape SO dans laquelle diffuser des données.

ID3D11Buffer *m_pBuffer;
int m_nBufferSize = 1000000;

D3D11_BUFFER_DESC bufferDesc =
{
    m_nBufferSize,
    D3D11_USAGE_DEFAULT,
    D3D11_BIND_STREAM_OUTPUT,
    0,
    0,
    0
};
D3D11Device->CreateBuffer( &bufferDesc, NULL, &m_pBuffer );

Créez une mémoire tampon en appelant ID3D11Device::CreateBuffer. Cet exemple illustre l’utilisation par défaut, qui est typique d’une ressource de mémoire tampon qui est censée être mise à jour assez fréquemment par l’UC. L’indicateur de liaison identifie l’étape de pipeline à laquelle la ressource peut être liée. Toute ressource utilisée par l’étape SO doit également être créée avec l’indicateur de liaison D3D10_BIND_STREAM_OUTPUT.

Une fois la mémoire tampon créée, définissez-la sur l’appareil actuel en appelant ID3D11DeviceContext::SOSetTargets :

UINT offset[1] = 0;
D3D11Device->SOSetTargets( 1, &m_pBuffer, offset );

Cet appel prend le nombre de mémoires tampons, un pointeur vers les mémoires tampons et un tableau de décalages (un décalage dans chacune des mémoires tampons qui indique où commencer l’écriture de données). Les données sont écrites dans ces mémoires tampons de sortie de diffusion en continu lorsqu’une fonction de dessin est appelée. Une variable interne effectue le suivi de la position de l’emplacement où commencer l’écriture de données dans les mémoires tampons de sortie de diffusion, et que les variables continuent d’incrémenter jusqu’à ce que SOSetTargets soit appelé à nouveau et qu’une nouvelle valeur de décalage soit spécifiée.

Toutes les données écrites dans les mémoires tampons cibles sont des valeurs 32 bits.

Étape de sortie de flux