Introduzione alla fase stream-output
Questa sezione descrive come usare uno shader geometry con la fase di output del flusso.
Compilare uno shader geometry
Questo geometry shader (GS) calcola una normale faccia per ogni triangolo e restituisce i dati di posizione, coordinate normali e 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();
}
Tenere presente che il codice considera che uno shader geometry è molto simile a un vertex o pixel shader, ma con le eccezioni seguenti: il tipo restituito dalla funzione, le dichiarazioni dei parametri di input e la funzione intrinseca.
Elemento | Descrizione | |
---|---|---|
Tipo restituito della funzione |
Il tipo restituito dalla funzione esegue un'unica operazione, dichiara il numero massimo di vertici che possono essere restituiti dallo shader. In questo caso
definisce l'output in modo che sia un massimo di 12 vertici. |
|
Dichiarazioni di parametri di input |
Questa funzione accetta due parametri di input:
Il primo parametro è una matrice di vertici (3 in questo caso) definita da una struttura GSPS_INPUT (che definisce i dati per vertice come posizione, una coordinata normale e una trama). Il primo parametro usa anche la parola chiave triangolo, ovvero la fase dell'assembler di input deve restituire i dati al geometry shader come uno dei tipi primitivi di triangolo (elenco di triangoli o strip di triangoli). Il secondo parametro è un flusso di triangoli definito dal tipo TriangleStream<GSPS_INPUT>. Ciò significa che il parametro è una matrice di triangoli, ognuno dei quali è costituito da tre vertici (che contengono i dati dei membri di GSPS_INPUT). Usare le parole chiave triangolo e triangolstream per identificare singoli triangoli o un flusso di triangoli in un gs. |
|
Funzione intrinseca |
Le righe di codice nella funzione shader usano funzioni intrinseche HLSL comuni-shader-core, ad eccezione delle ultime due righe, che chiamano Append e RestartStrip. Queste funzioni sono disponibili solo per uno shader geometry. Append informa il geometry shader per accodare l'output alla striscia corrente; RestartStrip crea una nuova striscia primitiva. Una nuova striscia viene creata in modo implicito in ogni chiamata della fase GS. |
Il resto dello shader è molto simile a un vertice o a un pixel shader. Il geometry shader usa una struttura per dichiarare i parametri di input e contrassegna il membro di posizione con la semantica SV_POSITION per indicare all'hardware che si tratta di dati posizionali. La struttura di input identifica gli altri due parametri di input come coordinate di trama (anche se uno di essi conterrà una normale del viso). Se si preferisce, è possibile usare la propria semantica personalizzata per il viso normale.
Dopo aver progettato lo shader geometry, chiamare D3DCompile per compilare come illustrato nell'esempio di codice seguente.
DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
ID3DBlob** ppShader;
D3DCompile( pSrcData, sizeof( pSrcData ),
"Tutorial13.fx", NULL, NULL, "GS", "gs_4_0",
dwShaderFlags, 0, &ppShader, NULL );
Analogamente ai vertex shader e ai pixel shader, è necessario un flag shader per indicare al compilatore come si vuole compilare lo shader (per il debug, ottimizzato per la velocità e così via), la funzione del punto di ingresso e il modello di shader da convalidare. Questo esempio crea uno shader geometry compilato dal file Tutorial13.fx usando la funzione GS. Lo shader viene compilato per il modello di shader 4.0.
Creare un oggetto geometry-shader con output del flusso
Dopo aver appreso che i dati verranno trasmessi dalla geometria e che lo shader è stato compilato correttamente, il passaggio successivo consiste nel chiamare ID3D11Device::CreateGeometryShaderWithStreamOutput per creare l'oggetto geometry shader.
Prima di tutto, è necessario dichiarare la firma di input dell'output del flusso (SO). Questa firma corrisponde o convalida gli output GS e gli input SO al momento della creazione dell'oggetto. Il codice seguente è un esempio della dichiarazione 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 );
Questa funzione accetta diversi parametri, tra cui:
- Puntatore allo shader geometry compilato (o vertex shader se non sarà presente alcun geometry shader e i dati verranno trasmessi direttamente dal vertex shader). Per informazioni su come ottenere questo puntatore, vedere Recupero di un puntatore a uno shader compilato.
- Puntatore a una matrice di dichiarazioni che descrivono i dati di input per la fase di output del flusso. (Vedere D3D11_SO_DECLARATION_ENTRY.) È possibile fornire fino a 64 dichiarazioni, una per ogni tipo diverso di elemento da restituire dalla fase SO. La matrice di voci di dichiarazione descrive il layout dei dati indipendentemente dal fatto che sia necessario associare un solo buffer o più buffer per l'output del flusso.
- Numero di elementi scritti dalla fase SO.
- Puntatore all'oggetto geometry shader creato (vedere ID3D11GeometryShader).
In questo caso, lo stride del buffer è NULL, l'indice del flusso da inviare al rasterizzatore è 0 e l'interfaccia di collegamento della classe è NULL.
La dichiarazione di output del flusso definisce il modo in cui i dati vengono scritti in una risorsa buffer. È possibile aggiungere tutti i componenti desiderati alla dichiarazione di output. Usare la fase SO per scrivere in una singola risorsa buffer o in molte risorse del buffer. Per un singolo buffer, la fase SO può scrivere molti elementi diversi per vertice. Per più buffer, la fase SO può scrivere solo un singolo elemento di dati per vertice in ogni buffer.
Per usare la fase SO senza usare uno shader geometry, chiamare ID3D11Device::CreateGeometryShaderWithStreamOutput e passare un puntatore a un vertex shader al parametro pShaderBytecode .
Impostare le destinazioni di output
L'ultimo passaggio consiste nell'impostare i buffer di fase SO. I dati possono essere trasmessi in uno o più buffer in memoria per usarli in un secondo momento. Il codice seguente illustra come creare un singolo buffer che può essere usato per i dati dei vertici, nonché per la fase SO in cui trasmettere i dati.
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 );
Creare un buffer chiamando ID3D11Device::CreateBuffer. Questo esempio illustra l'utilizzo predefinito, che è tipico per una risorsa buffer che dovrebbe essere aggiornata abbastanza frequentemente dalla CPU. Il flag di associazione identifica la fase della pipeline a cui è possibile associare la risorsa. Qualsiasi risorsa usata dalla fase SO deve essere creata anche con il flag di associazione D3D10_BIND_STREAM_OUTPUT.
Dopo aver creato il buffer, impostarlo sul dispositivo corrente chiamando ID3D11DeviceContext::SOSetTargets:
UINT offset[1] = 0;
D3D11Device->SOSetTargets( 1, &m_pBuffer, offset );
Questa chiamata accetta il numero di buffer, un puntatore ai buffer e una matrice di offset (un offset in ognuno dei buffer che indica dove iniziare a scrivere i dati). I dati verranno scritti in questi buffer di output di streaming quando viene chiamata una funzione di disegno. Una variabile interna tiene traccia della posizione in cui iniziare a scrivere i dati nei buffer di output di streaming e che le variabili continueranno a incrementare finché non viene chiamato di nuovo SOSetTargets e viene specificato un nuovo valore di offset.
Tutti i dati scritti nei buffer di destinazione saranno valori a 32 bit.