HLSL: Buffer vs. StructuredBuffer
November 2022 (591 Words, 4 Minutes)
Ever wondered why the DirectX offers two seemingly interchangeable buffer types? In this post we’ll go over the subtleties of the two. The MSDN does contain this information, but it is scattered across several pages that only vaguely link each other.
Buffer
The main purpose of a Buffer
is to be used with a format. The template variable must fit in 4 32-bit quantities, but that template specifies the result after conversion not the data as it is laid out in memory. The place that specifies the layout in memory is the Format
field of the SRV of the buffer. That’s right, even though a buffer resource must be created with DXGI_FORMAT_UNKNOWN
, an SRV meant to be bound to an HLSL Buffer
has a format. That format also defines the stride of the buffer read, and therefore the StructureByteStride
field of D3D12_BUFFER_SRV
must be 0. Currently, specifying a format and a non-0 stride can result in device removed by the D3D12 runtime.
Here is a common example of where all of this comes in handy. You can store normals and tangents in a packed buffer, using 8 bit for the X and Y component of each vector (Z is implicit since they are normalized) and use an SRV with DXGI_FORMAT_R8G8B8A8_SNORM
as the format. In the shader, you can bind this to a Buffer<float4>
descriptor and when you load from the buffer the HW will convert the formats for you for free! It also works in the other direction as well. Declare a RWBuffer<float4>
in a shader, and stores will auto-magically be converted from float4
to R8G8B8A8_SNORM
.
Code snippet:
Buffer<float4> NormalsAndTangents; // stored as RGBA8_snorm in memory
RWBuffer<float4> RW_NormalsAndTangents;
...
void main()
{
float2 normal = NormalsAndTangents[idx].xy;
float2 tangent = NormalsAndTangents[idx].zw;
// do some math with the normals
RW_NormalsAndTangents[idx] = float4(normal, tangent); // will be converted by the HW to RGBA8_snorm
}
StructuredBuffer
A StructuredBuffer
is templated by a struct. Unlike Buffer
, the template defines the layout in memory and conversions do not occur. You can still use simple types such as float4
or int2
but the data needs to be laid out in a matched way in memory(support for the 16-bit types however is shakier, a topic for another day). A StructuredBuffer
cannot have a format, the SRV format needs to be DXGI_FORMAT_UNKNOWN
. The StructureByteStride
field needs to be sizeof(struct)
.
This buffer is best used, well, for structs. Light
, DrawPacket
, BoneMatrix
, Particle
etc. There isn’t really a limit on the size of the struct, but remember that everything in graphics tends to be multiplied by the screen resolution. If every pixel needs to read even just 8 bytes, that is 64MB for 4K target. Aligning the structs (and their members) to 16 byte boundaries will help performance as well. If you need a non-16 byte align type (a float3
for example) a better approach might be to declare it as Buffer<float>
and access triplets in a shader. A bit ugly but will avoid some of the nasty edge cases of non-16 byte aligned types.
Code snippet:
struct Particle
{
float4 pos;
float4 vel;
}
StructuredBuffer<Particle> ParticleArray; // stored as 2 float4 in memory
RWStructuredBuffer<Particle> RW_ParticleArray;
...
void main()
{
Particle p = ParticleArray[idx];
// do some math with the Particle
RW_ParticleArray[idx] = p ; // no conversion will be stored as 2 float4
}
Summary
Here is a handy table to summarize the info:
Buffer |
StructuredBuffer |
|
---|---|---|
Format for resource creation | DXGI_FORMAT_UNKNOWN |
DXGI_FORMAT_UNKNOWN |
Format for SRV\UAV | As laid out in memory | DXGI_FORMAT_UNKNOWN |
StructureByteStride field |
0 | sizeof(struct) |
Can convert data? | Yes | No |
If you found all of this confusing, you are not alone. We recently spent an hour debugging an issue with Buffer
and incorrect format. And like most dark corners, the behavior between AMD and NV hardware is different…
Some useful (?) MSDN pages:
https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_unordered_access_view_desc
https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_buffer_uav
https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_shader_resource_view_desc
https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_buffer_srv
https://learn.microsoft.com/en-us/windows/win32/direct3d12/typed-unordered-access-view-loads
https://learn.microsoft.com/en-us/windows/uwp/graphics-concepts/shader-resource-view–srv-