Riccardo Loggini

The D3D12 Pipeline State Object

Table of Contents

Why talking about the PSO

The Pipeline State Object (PSO) is used to describe how the graphics/compute pipeline will behave in every Pipeline Stage when we are going to render/dispatch something.

We can define behavior of both programmable and fixed pipeline stages, by linking compiler shader code and setting a multitude of variable values along the way. Other settings will be available to change behavior of in-between stages, such as viewport and scissor rect, from a command list instead. The PSO definition will also incorporate the definition of a Root Signature, which is used to define the resources types (and their details) available during execution of the pipeline (e.g. textures exposed to be sampled in pixel shader code).

What is The PSO

The PSO is an object that represents the settings for our graphics device (GPU) in order to draw or dispatch something.

Brief description of its components purposes follows:

How To Create the PSO in code

All the different structs describing the various parts of the PSO will serve to create the so called Pipeline State Description object of type D3D12_GRAPHICS_PIPELINE_STATE_DESC. From that, the PSO object is obtained by calling device->CreateGraphicsPipelineState(...) method.
Thankfully, the helper library D3DX12 (available only on GitHub) will provide some helper structs to facilitate this whole process and make it less verbose, by providing another way to create the PSO.

This alternative way will use a D3D12_PIPELINE_STATE_STREAM_DESC type instead of the default PSO description object.
We are also going to need to use an ID3D12Device2 interface type of device.
We start by defining an object type called Pipeline State Stream, a struct made out of Tokens:

struct PipelineStateStream{
CD3DX12_PIPELINE_STATE_STREAM_ROOT_SIGNATURE pRootSignature;
CD3DX12_PIPELINE_STATE_STREAM_INPUT_LAYOUT InputLayout;
CD3DX12_PIPELINE_STATE_STREAM_PRIMITIVE_TOPOLOGY PrimitiveTopologyType;
CD3DX12_PIPELINE_STATE_STREAM_VS VS;
CD3DX12_PIPELINE_STATE_STREAM_PS PS;
CD3DX12_PIPELINE_STATE_STREAM_DEPTH_STENCIL_FORMAT DSVFormat;
CD3DX12_PIPELINE_STATE_STREAM_RENDER_TARGET_FORMATS RTVFormats;
} pipelineStateStream;

Each of those tokens represents an input data for the PSO description, from those described before. There are actually more tokens available, and the number will grow with the expansion of D3D12 features. A full list of tokens is available in the microsoft documentation website.

To define a pipeline state stream object, only a subset of tokens is required: for all the parameters that we don’t specify, the pipeline state stream will assign a default value. That is why we can select any number of tokens for our state stream, still the default PSO state is not enough to make the system work by itself: some components such as the vertex shader, are always needed. The order in which we declare tokens is not important.
After our pipeline state stream object type is defined, we create an object of that type and we fill all the fields with our data:

pipelineStateStream.pRootSignature = m_RootSignature.Get();
pipelineStateStream.InputLayout = { inputLayout, _countof(inputLayout) };
pipelineStateStream.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
pipelineStateStream.VS = CD3DX12_SHADER_BYTECODE(vertexShaderBlob.Get());
pipelineStateStream.PS = CD3DX12_SHADER_BYTECODE(pixelShaderBlob.Get());
pipelineStateStream.DSVFormat = DXGI_FORMAT_D32_FLOAT;
pipelineStateStream.RTVFormats = rtvFormats;

With the pipeline state stream description complete, we can now create the PSO object with it, of type ID3D12PipelineState, from the device with CreatePipelineState method:

D3D12_PIPELINE_STATE_STREAM_DESC pipelineStateStreamDesc = {
sizeof(PipelineStateStream), &pipelineStateStream
};
ThrowIfFailed(device->CreatePipelineState(&pipelineStateStreamDesc, IID_PPV_ARGS(&m_PipelineState)));

Note: When we need to change one of the attributes of the PSO, for example changing pixel shader the next frame, we are going to need to recreate the PSO object.

Shaders Bytecode Generation

The PSO description contains D3D12_SHADER_BYTECODE fields for vertex, hull, domain, geometry and pixel shaders. This compiled shader data in binary form is obtained with the following steps:

Note: A real case scenario includes a system that stores the compiled shader’s binary blob in permanent memory, so that we just need to load it and link it to the PSO.

In-Command List Settings

These additional settings are controlled by calling methods on the Command List, from a ID3D12GraphicsCommandList object (as described in the official documentation), rather than the PSO. This is because we can modify them on the fly without recreate the PSO, and use this last one for multiple command lists that do different tasks. For each of these settings that we do not specify, a default value will be assigned.

~Sources