Mon
28
Sep 2015
DirectX 12 allows us to use arrays of resources (descriptor tables) and refer to them from a shader code by an index. The index can be constant or variable. But there is a big trap waiting for shader developers doing that! Microsoft just updated their public documentation in this regard (see Resource Binding in HLSL), so now I can talk about it.
In D3D12, resource index is expected to be uniform (scalar) by default. It means the index, even if dynamic, should be the same across whole draw call (all vertices/pixels/etc.). For example, it can be a result of some calculations, which depend on parameters coming from a constant buffer. Example code:
Texture2D<float4> textures[16] : register(t0);
SamplerState samp : register(s0);
struct SConstantBuffer
{
uint MaterialId;
};
ConstantBuffer<SConstantBuffer> cb : register(b0);
float4 PS(
in float2 texCoords : TEXCOORD0 ) : SV_Target
{
uint materialId = cb.MaterialId;
return textures[materialId].Sample(samp, texCoords);
}
Why is it this way? That is because on low level, GPU-s are made of SIMD processors. Each of its small processors executes shader instructions over multiple "SIMD threads" (or "warps", or "wavefronts", however you call them), not a single (scalar) value. Because of that, knowing that the resource index will always be the same on all SIMD threads can result in some optimizations and more efficient execution.
Resource indices in Direct3D 12 actually can be non-uniform (divergent) - different in every vertex/pixel/etc., like when they are result of some calculations based on vertex attributes or pixel position. But they must be then surrounded by a special, "pseudo"-function in HLSL, called NonUniformResourceIndex. For example:
Texture2D<float4> textures[16] : register(t0);
SamplerState samp : register(s0);
float4 PS(
in float2 texCoords : TEXCOORD0,
in uint materialId : MATERIAL_ID ) : SV_Target
{
return textures[NonUniformResourceIndex(materialId)].Sample(samp, texCoords);
}
This may look a bit unintuitive, so I expect many developers will make the mistake and omit this function. If you use a non-uniform value as resource index, but forget to mark it with NonUniformResourceIndex, HLSL compiler probably won't warn you about it. It may even work in some cases and on some GPU-s, while it will give invalid (undefined) results on others. So similarly to the issue with reduced precision in normalize/length operations in GLSL, this is a thing to be careful with when coding your shaders in HLSL using new Shader Model 5.1.
Update 2023-10-05: I recommend a follow-up article I've written 5 years later: "Which Values Are Scalar in a Shader?". It explains the topic in more details and also introduces differentiation between values that are uniform across the entire draw call or compute dispatch (constant) versus uniform in a single wave (scalar).