home..

HLSL: Buffer vs. StructuredBuffer

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-

© 2022 Nadav Geva   •  Powered by Soopr   •  Theme  Moonwalk