Introdução à Fase de Saída de Fluxo

Esta seção descreve como usar um sombreador de geometria com a fase de saída de fluxo.

Compilar um Sombreador de Geometria

Esse sombreador de geometria (GS) calcula uma face normal para cada triângulo e emite dados de posição, normal e de coordenada de 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 de pixel, mas com as seguintes exceções: o tipo retornado pela função, as declarações do parâmetro de entrada e a função intrínseca.

Item Descrição
Tipo de retorno da função
O tipo de retorno da função faz uma coisa: declara o número máximo de vértices que podem ser emitidos pelo sombreador. Nesse caso,
maxvertexcount(12)

define que a saída será de no máximo 12 vértices.

Declarações de parâmetros de entrada

Essa função recebe 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, nesse caso) definida por uma estrutura GSPS_INPUT (que define dados por vértice como uma posição, uma normal e uma coordenada de textura). O primeiro parâmetro também utiliza a palavra-chave triângulo, o que significa que a fase do assembler de entrada deve emitir dados para o sombreador de geometria como um dos tipos primitivos de triângulo (lista de triângulos ou faixa de triângulos).

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

Use as palavras-chave triângulo e trianglestream para identificar triângulos individuais ou um fluxo de triângulos em um GS.

Função intrínseca

As linhas de código na função do sombreador utilizam funções intrínsecas de HLSL do núcleo do sombreador comum, exceto as duas últimas linhas, que chamam Append e RestartStrip. Essas funções estão disponíveis apenas para um sombreador de geometria. Acrescentar informa ao sombreador de geometria para acrescentar a saída à faixa atual; RestartStrip cria uma nova faixa primitiva. Uma nova faixa é criada implicitamente em cada invocação da fase GS.

O restante do sombreador é muito semelhante a um sombreador de vértice ou de pixel. O sombreador de geometria utiliza uma estrutura para declarar os parâmetros de entrada e marca o membro 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 utilizar sua própria semântica personalizada para a face 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 de pixel, você precisa de um sinalizador de sombreador para informar ao compilador como deseja que o sombreador seja compilado (para depuração, otimizado para velocidade etc.), a função do ponto de entrada e o modelo de sombreador a ser validado. Este exemplo cria um sombreador de geometria criado a partir do arquivo Tutorial13.fx, utilizando a função GS. O sombreador é compilado para o modelo de sombreador 4.0.

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

Uma vez que você saiba que transmitirá os dados da geometria e que tenha compilado o sombreador com êxito, a próxima etapa é chamar ID3D11Device::CreateGeometryShaderWithStreamOutput para criar o objeto do sombreador de geometria.

Mas, primeiro, você precisa declarar a assinatura de entrada da fase de saída de 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 );

Essa função recebe 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, confira Obtendo um Ponteiro para um Sombreador compilado.
  • Um ponteiro para uma matriz de declarações que descrevem os dados de entrada para a fase de saída do fluxo. (Confira D3D11_SO_DECLARATION_ENTRY.) Você pode fornecer até 64 declarações, uma para cada tipo diferente de elemento que será a saída da fase SO. A matriz de entradas de declaração descreve o layout dos dados, independentemente do fato de que será associado a um único buffer ou a vários buffers para a saída do fluxo.
  • O número de elementos que são gravados pela fase SO.
  • Um ponteiro para o objeto do sombreador de geometria criado (consulte ID3D11GeometryShader).

Nessa situação, o stride do buffer é NULO, o índice do fluxo que será enviado ao rasterizador é 0 e a interface de vinculação da classes é NULA.

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

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

Definir os Destinos de Saída

A última etapa é definir os buffers da fase SO. Os dados podem ser transmitidos em um ou mais buffers na memória para serem utilizados posteriormente. O código a seguir mostra como criar um único buffer que pode ser utilizado para dados de vértice, bem como para o fluxo de dados da fase SO.

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

Criar um buffer chamando ID3D11Device::CreateBuffer. Este exemplo ilustra o uso padrão, típico de um recurso de buffer que será atualizado com bastante frequência pela CPU. O sinalizador de associação identifica a fase do pipeline à qual o recurso pode ser associado. Qualquer recurso utilizado pela fase SO também deve ser criado com o sinalizador de associação D3D10_BIND_STREAM_OUTPUT.

Uma vez que o buffer tenha sido criado com êxito, defina-o como o dispositivo atual chamando ID3D11DeviceContext::SOSetTargets:

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

Essa chamada recebe o número de buffers, um ponteiro para os buffers e uma matriz de deslocamentos (um deslocamento em cada um dos buffers que indica em que ponto começar a gravar os 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 mantém o controle da posição em que os dados começarão a ser gravados nos buffers de saída do streaming, e essa variável continuará a ser incrementada até que SOSetTargets seja chamada novamente e um novo valor deslocado seja especificado.

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

Fase de Saída do Fluxo