Welcome back! In the previous tutorial we briefly touched on why our Sample Application is looking like it does. In this and the next 3 tutorials we will take a closer look at the various DirectX 12 specific features, like the pipeline state object, command lists and the descriptors. After this, we can start making the cool stuff!
I know it’s a lot of information and many new concepts, but it will all make sense – and when you understand it correctly, it will take you closer to the GPU itself so you can use all of the new performance capabilities DX12 gives you.
Let’s get started. One of the new features of DirectX 12 is the Pipeline State Object (PSO). \m/
Putting it simply, this is a way to set a GPU to a state you need it to be in, when rendering. When the GPU is doing its magic, it needs to set a lot of input and rendering settings before the rendering can happen. It needs to know how to blend colors together if there are overlapping pixels (blending), how to handle depth data (depth stencil state), how to read input data to build primitives, what shaders to use and so on, the list is long.
A PSO is simply an object that describes a given state of the graphics pipeline. You can create as many of these as you want, and switch between them when needed.
These PSOs are usually created during initialization, and then switched during rendering. Doing this right will benefit your application when it comes to performance as you can set a lot of these settings at once, instead of randomly when you need to change a fraction of it.
Think of them as a way to put a lot of different pieces of a puzzle together for the final image, but instead to put together how the GPU will handle stuff.
The two pictures above is the same scene, using the same vertex data, but different PSOs.
Creating a PSO
It’s very simple to create a PSO. To do it we follow the typical Direct3D way of doing things, we fill out a structure called D3D12_GRAPHICS_PIPELINE_STATE and submit it with a call to the function CreateGraphicsPipelineState(…).
Let’s take a look at the PSO from our example application from Tutorial 1:
Wow, that’s a lot of properties! Let’s dive right in. In this case we set the input layout to the layout we specified when creating the Vertex Buffer, and we let it know that we want to use our Root Signature instance (covered in Tutorial 4).
We also need to set what shaders we want to use, so we take the byte code from the VS and PS, and set it accordingly. In out example, we are using one vertex shader to transform our cube, and one pixel shader to give it color.
Next we specify that we want to use the default rasterizer state and blend state, set the sample mask to max, and disable the depth and stencil buffer. We set the primitive topology to triangles, as that is how we set up our vertices in our vertex buffer, we set the number of render targets (RTV) to one as we only need one render target for the example scene, set the format of it and sample desc is used to set multisampling parameters.
Once we got the PSO structure filled out, we can set it using CreateGraphicsPipelineState(…) on the D3D device.
This function creates a PSO instance we can use later in our application. We need to use this when we create a Command List in the next tutorial as a parameter to CreateCommandList(…). Then, when we use that command list for rendering, this PSO will be in use.
Switching between PSOs
Although we won’t need to switch between POSs in our sample, I still want to show you how you can change the current POS bound to a command list.
There is a function called SetPipelineState on a CommandList that will take a PSO created during the initialization of the app, and make it active on the command list.
We still render our cube, but now you know what state the GPU is in, congratulations!