Partilhar via


Introdução ao Stream-Output Stage

Esta seção descreve como usar um sombreador de geometria com o estágio de saída de fluxo.

Compilar um sombreador de geometria

Este sombreador de geometria (GS) calcula uma face normal para cada triângulo e produz dados de coordenadas de posição, normal e textura.

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();
}

Tendo esse código em mente, considere que um sombreador de geometria se parece muito com um sombreador de vértice ou pixel, mas com as seguintes exceções: o tipo retornado pela função, as declarações de parâmetro de entrada e a função intrínseca.

Número Descrição
Tipo de retorno de função
O tipo de retorno da função tem um propósito: declarar o número máximo de vértices que podem ser gerados pelo sombreador. Neste caso,
maxvertexcount(12)

define a saída como sendo um máximo de 12 vértices.

Declarações de parâmetros de entrada

Esta função usa dois parâmetros de entrada:

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

O primeiro parâmetro é uma matriz de vértices (3 neste caso) definida por uma estrutura GSPS_INPUT (que define dados por vértice como uma posição, uma coordenada normal e uma textura). O primeiro parâmetro também usa a palavra-chave triângulo, o que significa que o estágio de montagem de entrada deve enviar dados para o sombreador de geometria como um dos tipos primitivos triangulares (lista de triângulos ou strip de triângulos).

O segundo parâmetro é um fluxo de triângulo definido pelo tipo TriangleStream<GSPS_INPUT>. Isto significa que o parâmetro é uma matriz de triângulos, cada um dos quais é composto por três vértices (que contêm os dados dos membros de GSPS_INPUT).

Utilize as palavras-chave triangle e trianglestream para identificar triângulos individuais ou um fluxo de triângulos num GS (Geometry Shader).

Função intrínseca

As linhas de código na função shader utilizam funções intrínsecas HLSL do common-shader-core, exceto as duas últimas linhas, que chamam Append e RestartStrip. Essas funções só estão disponíveis para um sombreador de geometria. Append informa o sombreador de geometria que deve anexar a saída ao segmento atual; RestartStrip cria um novo segmento primitivo. Uma nova tira é implicitamente criada a cada invocação do estágio GS.

O resto do sombreador é muito semelhante a um sombreador de vértice ou pixel. O sombreador de geometria usa uma estrutura para declarar parâmetros de entrada e marca o membro da posição com a semântica SV_POSITION para informar ao hardware que se trata de dados posicionais. A estrutura de entrada identifica os outros dois parâmetros de entrada como coordenadas de textura (mesmo que um deles contenha uma face normal). Você pode usar sua própria semântica personalizada para o rosto normal, se preferir.

Depois de projetar o sombreador de geometria, chame D3DCompile para compilar, conforme mostrado no exemplo de código a seguir.

DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
ID3DBlob** ppShader;

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

Assim como os sombreadores de vértice e pixel, precisas de um flag de sombreador para indicar ao compilador como desejas que o sombreador seja compilado (para depuração, otimizado para desempenho, etc.), a função de ponto de entrada e o modelo de sombreador contra o qual validar. Este exemplo cria um sombreador de geometria criado a partir do arquivo Tutorial13.fx, usando a função GS. O sombreador é compilado para o modelo de sombreador 4.0.

Criar um objeto Geometry-Shader com saída de fluxo

Depois de saber que você estará transmitindo os dados da geometria e tiver compilado com êxito o sombreador, a próxima etapa é chamar ID3D11Device::CreateGeometryShaderWithStreamOutput para criar o objeto de sombreador de geometria.

Mas primeiro, é necessário declarar a assinatura de entrada do estágio de saída do fluxo (SO). Essa assinatura corresponde ou valida as saídas GS e as entradas SO no momento da criação do objeto. O código a seguir é um exemplo da declaração 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 );

Esta função tem vários parâmetros, incluindo:

  • Um ponteiro para o sombreador de geometria compilado (ou sombreador de vértice se nenhum sombreador de geometria estiver presente e os dados forem transmitidos diretamente do sombreador de vértice). Para obter informações sobre como obter esse ponteiro, consulte Obtendo um ponteiro para um sombreador compilado.
  • Um ponteiro para uma matriz de declarações que descrevem os dados de entrada para o estágio de saída do fluxo. (Ver D3D11_SO_DECLARATION_ENTRY.) Você pode fornecer até 64 declarações, uma para cada tipo diferente de elemento a ser produzido do estágio SO. A matriz de entradas de declaração descreve o layout de dados, independentemente de apenas um único buffer ou vários buffers serem vinculados para saída de fluxo.
  • O número de elementos que são escritos pelo estágio SO.
  • Um ponteiro para o objeto de sombreador de geometria que é criado (consulte ID3D11GeometryShader).

Nessa situação, a passada do buffer é NULL, o índice do fluxo a ser enviado para o rasterizador é 0 e a interface de vinculação de classe é NULL.

A declaração de saída de fluxo define a maneira como os dados são gravados em um recurso de buffer. Você pode adicionar quantos componentes quiser à declaração de saída. Use a fase SO para escrever num único recurso de buffer ou em vários recursos de buffer. Para um único buffer, o estágio SO pode gravar muitos elementos diferentes por vértice. Para vários buffers, o estágio SO só pode gravar um único elemento de dados por vértice em cada buffer.

Para usar o estágio SO sem usar um sombreador de geometria, chame ID3D11Device::CreateGeometryShaderWithStreamOutput e passe um ponteiro para um sombreador de vértice para o parâmetro pShaderBytecode.

Definir as metas de saída

A última etapa é definir os buffers de estágio SO. Os dados podem ser transmitidos para um ou mais buffers na memória para uso posterior. O código a seguir mostra como criar um único buffer que pode ser usado para dados de vértice, bem como para o estágio SO para transmitir dados.

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 );

Crie um buffer chamando ID3D11Device::CreateBuffer. Este exemplo ilustra o uso padrão, que é típico para um recurso de buffer que se espera que seja atualizado com bastante frequência pela CPU. O sinalizador de vinculação identifica o estágio do pipeline ao qual o recurso pode ser vinculado. Qualquer recurso usado pelo estágio SO também deve ser criado com o indicador D3D10_BIND_STREAM_OUTPUT.

Depois que o buffer for criado com êxito, defina-o para o dispositivo atual chamando ID3D11DeviceContext::SOSetTargets:

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

Essa chamada usa o número de buffers, um ponteiro para os buffers e uma matriz de deslocamentos (um deslocamento em cada um dos buffers que indica onde começar a gravar dados). Os dados serão gravados nesses buffers de saída de streaming quando uma função de desenho for chamada. Uma variável interna controla a posição de onde começar a gravar dados nos buffers de saída de streaming, e essas variáveis continuarão a aumentar até que SOSetTargets seja chamado novamente e um novo valor de deslocamento seja especificado.

Todos os dados gravados nos buffers de destino serão valores de 32 bits.

Stream-Output Estágio