Shader Data
If we have data, let’s look at data. If all we have are opinions, let’s go with mine.
– J. Barksdale
So far our shaders are very simple functions with constant variables that can not be changed at runtime. In the previous tutorial, Among Colourful Stars, we used dynamic buffers to update the starfield. In this tutorial we will have a detailed look at constant buffers, used to pass data between the CPU and the GPU.
Constant Buffers
To set up a constant buffer, the following four steps must be completed:
- The desired variables have to be added to the shader.
- A C++-structure matching the shader variables has to be defined.
- A constant buffer object has to be created.
- The constant buffer must be set as active.
In a first example we will change the vertex colours over time.
Add Variables to Shaders
Adding variables to a shader is straightforward. They are basically like structs, but they are called cbuffer.
Note that the variables can be used without having to refer to the name of the constant buffer.
Matching C++-structure
This is even simpler than the first step, we simply create a struct with the exact same data:
Note that the names of the variables are arbitrary, but the order and size of the variables must fit those of the constant buffer of the shader.
Creating Constant Buffer Objects
Now comes the difficult part: The creation of the actual constant buffer, but thankfully it is similar to the creation of a vertex buffer. First, a D3D11_BUFFER_DESC structure must be filled out and created with the CreateBuffer method:
The following flags are important for constant buffers:
UINT ByteWidth
This unsigned int specifies the size of the buffer in bytes. Unfortunately this is a bit tricky, as constant buffers must always be a multiple of 16 bytes. It is often easier to set this size manually to the appropriate size instead of using the sizeof() operator.
In our example, the ColourLevels structure is 12 bytes big (3 floats), thus 16 bytes will be enough. The shader will ignore the last 4 bytes.
D3D11_USAGE Usage
This variable identifies how the buffer is expected to be read from and written to. We will set this to D3D11_USAGE_DEFAULT and then use special functions to let the CPU access the buffer as well, as normally the DEFAULT type implies that only the GPU can access the buffer.
UINT BindFlags
Those flags define how the buffer will be bound to the pipeline. Obviously, we will use the D3D11_BIND_CONSTANT_BUFFER flag this time.
After the buffer is created, it must be activated. This is rather straightforward again as there are only two functions to use, either VSSetConstantBuffers() or PSSetConstantBuffers() depending on what shader the constant buffers are being used for (VS: Vertex Shader ; PS: Pixel Shader).
We used the Vertex Shader version:
UINT StartSlot
This is advanced, and we do not need this at the moment, and thus we can safely set this to 0.
UINT NumBuffers
This flag defines the number of buffers to activate.
ID3D11Buffer *const *ppConstantBuffers
This is an array of constant pointers to constant buffer objects. We simply passed the name of our constant buffer.
Easy! We now have a constant buffer set up to manipulate the colour data of each vertex!
Updating Constant Buffers
To efficiently update the data of constant buffers every frame, DirectX offers us the UpdateSubresource method, which copies data from memory to a subresource created in non-mappable memory.
Thankfully, we only need two of those parameters:
ID3D11Resource *pDstResource
This pointer to an ID3D11Resource defines the buffer to be updated.
const void *pSrcData
This pointer defines where the new data is coming from.
Here is an example:
As seen above, the data in the constant buffer is updated by the data in the CoulourLevels structure, effectively undermining the restriction that the CPU can’t write to the constant buffer. In this example, the red colour is slowly being drained from each vertex.
Moving Triangle
As a last example, we will add an offset to the position of the triangle. Due to a rift in the space-time continuum, the triangle is pushed towards the upper-right corner of the screen.
Vertex Shader
Changing the vertex shader constant buffer to accept both the position and the colour is rather easy:
C++-Structure
Well now things become a bit more tricky. Remember that the GPU wants the constant buffer data to be packed in sizes of 16 bytes. Let us have a look at the C++-structure:
Clearly, the data on the CPU is not arranged in the same way as the data on the GPU? We need to add some spacing between the positions and the colours:
Creating the Buffer
All we have to change is to tell DirectX to allocate 32 bytes of memory:
Now we let the triangle drift off towards the sun in the upper-right corner of the map, and as it approaches the sun, it gets hotter and hotter:
All in all, this High-Level Shader Language isn’t as bad as its reputation!
References
- Microsoft Developer Network (MSDN)
- Wikipedia