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,
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:
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.