Pipeline State Object (PSO) is a fundamental concept in modern graphics APIs such as DirectX 12 and Vulkan. It represents an object that stores the state of the graphics pipeline and helps accelerate rendering by minimizing state change costs.
In this article, we will explore what PSO is, how it works, its benefits, and how to use it properly.
What is a Pipeline State Object?
In traditional graphics APIs like DirectX 11 and OpenGL, rendering states were set using multiple individual calls—for shaders, blending, rasterization, and other parameters. This approach introduced significant overhead for state changes.
PSO consolidates all these parameters into a single object that can be pre-created and quickly switched during rendering. This reduces overhead and improves performance.
Components of PSO
Depending on the API, a Pipeline State Object includes:
- Shader programs (vertex, pixel, geometry, compute, etc.).
- Vertex data formats (Vertex Input Layout).
- Rasterization settings (depth, fill mode, culling, etc.).
- Blending settings (color blending modes).
- Depth and stencil testing settings.
- Primitive topology type (such as points, lines, or triangles).
- Formats for render targets and depth buffers.
Benefits of Using PSO
1. Improved Performance
Since PSOs are created in advance, they minimize state changes and reduce CPU overhead.
2. Optimized for Modern Hardware
Unlike older APIs where frequent state changes could slow down the GPU, PSOs allow the graphics processor to work more efficiently by having all states predefined.
3. Simplified State Management
Instead of multiple state-setting calls, we simply switch between predefined PSOs, making the code cleaner and more readable.
Using PSO in DirectX 12
1. Creating a Pipeline State Object
Example of creating a graphics PSO in DirectX 12:
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.VS = { compiledVertexShader->GetBufferPointer(), compiledVertexShader->GetBufferSize() };
psoDesc.PS = { compiledPixelShader->GetBufferPointer(), compiledPixelShader->GetBufferSize() };
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
psoDesc.InputLayout = { inputLayout, _countof(inputLayout) };
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.SampleDesc.Count = 1;
psoDesc.SampleMask = UINT_MAX;
psoDesc.pRootSignature = rootSignature.Get();
ComPtr<ID3D12PipelineState> pipelineState;
device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pipelineState));
2. Applying PSO
After creating a PSO, it can be quickly switched during rendering:
commandList->SetPipelineState(pipelineState.Get());
Using PSO in Vulkan
In Vulkan, the PSO concept is also crucial. Creating a VkPipeline requires defining all rendering parameters in advance, which helps reduce overhead.
Example of PSO creation in Vulkan:
VkGraphicsPipelineCreateInfo gfxPipelineInfo{};
gfxPipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
gfxPipelineInfo.stageCount = static_cast<uint32_t>(shaderStageCount);
gfxPipelineInfo.pStages = shaderStages;
gfxPipelineInfo.pVertexInputState = &vertexInputConfig;
gfxPipelineInfo.pInputAssemblyState = &inputAssemblyState;
gfxPipelineInfo.pRasterizationState = &rasterizationConfig;
gfxPipelineInfo.pMultisampleState = &multisampleConfig;
gfxPipelineInfo.pDepthStencilState = &depthStencilConfig;
gfxPipelineInfo.pColorBlendState = &colorBlendConfig;
gfxPipelineInfo.pDynamicState = &dynamicConfig;
gfxPipelineInfo.layout = pipelineLayout;
gfxPipelineInfo.renderPass = renderPass;
gfxPipelineInfo.subpass = 0;
VkPipeline pipelineHandle;
if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &gfxPipelineInfo, nullptr, &pipelineHandle) != VK_SUCCESS) {
throw std::runtime_error("Failed to create graphics pipeline");
}
Conclusion
PSO serves as a fundamental optimization tool in contemporary graphics APIs such as DirectX 12 and Vulkan. By predefining pipeline configurations, it minimizes computational overhead and enhances rendering efficiency.
Using PSOs requires planning and setup, but in the long run, they provide a substantial efficiency boost, especially in high-performance graphics applications.
If you are developing a game or a graphics engine, effective PSO management will help you achieve maximum rendering performance and flexibility.
Serverspace Knowledge Base
For a deeper understanding of graphics APIs and rendering optimizations, you can explore the ServerSpace Knowledge Base. It offers a wealth of information on PSO, pipeline configurations, and other key technical concepts. You'll also find detailed explanations of rendering techniques, performance tuning, and industry best practices.