Pipeline State Object 3151 Visite) DirectX 12
La pipeline DirectX gestisce ed elabora principalmente triangoli. Ogni modello 3D sia esso realizzato a mano o con un editor, viene salvato come array di punti e indici (l'ordine con cui questi vanno presi) per formare triangoli. Ogni punto viene trasformato dalla posizione con cui è stato creato (esempio in un editor 3D) in quella finale a video, quindi per ogni pixel che compone i vari triangoli viene eseguito un calcolo per avere il colore finale. Queste trasformazioni sono create tramite gli shader, script che carichiamo ed applichiamo al modello.
Introdotti nella versione 8 di DirectX in pseudo Assembly, si sono evoluti diventando il cuore delle Direct3D. La versione 12 non ha introdotto modifiche agli shader ma piuttosto al loro utilizzo. Fino alla versione 11 i vari shader erano indipendenti ed applicati singolarmente. Ora invece tutto risiede in un unico oggetto: Il Pipeline State Object. Questo definisce l'intero processo di elaborazione del modello e comprende sia gli Shaders sia le proprietà della pipeline. Vediamo come creare il primo pso.
Per prima cosa occorre creare il byte code dei nostri Shader, compilandoli tramite il metodo CompileFromFile e creando l'oggetto ShaderByteCode
var vertexShader = new ShaderBytecode(SharpDX.D3DCompiler.ShaderBytecode.CompileFromFile("shaders.hlsl", "VSMain", "vs_5_0"));
Al metodo Compile va passato il path del file, la funzione da compilare (lo Shader potrebbe contenere molte funzioni gli va indicato quale è quella che ci interessa) e la versione da usare (vs_5_0 significa Vertex Shader versione 5.0).
Per il funzionamento degli Shader leggete il De Rerum Shader pubblicato nel primo tutorial.
Creati quelli che ci servono (in genere almeno un vertex ed un pixel shader) dovremo definire l'input del pso. A differenza del passato i parametri che passiamo devono essere indicati esplicitamente. Questo, che è l'elemento più complesso delle Direct3D12, verrà spiegato nel prossimo tutorial. Per ora creiamo una pipeline senza parametri
RootSignatureDescription rootSignatureDesc = new RootSignatureDescription(RootSignatureFlags.AllowInputAssemblerInputLayout);
rootSignature = device.CreateRootSignature(rootSignatureDesc.Serialize());
Ora definiamo la struttura del vertice che andremo ad utilizzare.
Immaginiamo che vogliamo 3 valori per la posizione che chiamiamo POSITION e 4 per un colore che chiamiamo COLOR.
InputElement[] inputElementDescs = new InputElement[]
{
new InputElement("POSITION",0,Format.R32G32B32_Float,0,0),
new InputElement("COLOR",0,Format.R32G32B32A32_Float,12,0)
};
Ecco l'array che lo descrive. Ogni elemento indicherà il nome che gli vogliamo assegnare nello shader, il buffer da usare (in genere il primo quindi 0), il formato (R32G32B32 indicano 3 float) e fondamentale, l'offset. Se il precedente occupa 12byte (3 float) il secondo partirà da 12. Se volessimo aggiungere un altro elemento partirà da 28 (12 + 16) e così via.
Ora che abbiamo shaders, input element e root signature descriveremo la signature
GraphicsPipelineStateDescription psoDesc = new GraphicsPipelineStateDescription()
{
InputLayout = new InputLayoutDescription(inputElementDescs),
RootSignature = rootSignature,
VertexShader = vertexShader,
PixelShader = pixelShader,
RasterizerState = RasterizerStateDescription.Default(),
BlendState = BlendStateDescription.Default(),
DepthStencilFormat = SharpDX.DXGI.Format.D32_Float,
DepthStencilState = new DepthStencilStateDescription() { IsDepthEnabled = false, IsStencilEnabled = false },
SampleMask = int.MaxValue,
PrimitiveTopologyType = PrimitiveTopologyType.Triangle,
RenderTargetCount = 1,
Flags = PipelineStateFlags.None,
SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0),
StreamOutput = new StreamOutputDescription()
};
psoDesc.RenderTargetFormats[0] = SharpDX.DXGI.Format.R8G8B8A8_UNorm;
In questo esempio abbiamo creato una pipeline passandogli gli elementi che abbiamo creato.
Gli altri elementi, per ora messi a default, ci permettono di definire il comportamento di Direct3D in fase di rendering.
Per creare la Pipeline
PipelineState pipelineState = device.CreateGraphicsPipelineState(psoDesc);
Ora la pipeline è pronta all'uso. Potete usarla in due modi:
Passarla al commandList in fase di reset
commandList.Reset(commandAllocator, pipelineState);
O con la proprietà PipelineState, sempre della commandList.
In una sola istruzione avete così impostato tutte le proprietà della pipeline. Dato che è un unico blocco non potrete cambiare i singoli elementi ma dovrete creare più pipeline e cambiarle interamente. Considerando che gli shader una volta compilati occupano pochi byte non è un problema crearne anche migliaia.
Vi rimando ai successivi tutorial.