Direct3D 12 - Watch out for non-uniform resource index!

15:30
Mon
28
Sep 2015

Direct3D 12 - Watch out for non-uniform resource index!

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 (convergent) 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.

Comments (2) | Tags: directx | Author: Adam Sawicki | Share

Comments

Matias N. Goldberg
2015-09-28 16:49:54
Nice catch!

Somehow, I don't think NonUniformResourceIndex is supported by all tiers, since divergent addressing w/ bindless textures in OpenGL was undefined behavior (basically tip was "don't do it" because some HW would go kapput, others would return garbage, others would do the correct thing, particularly the newer models)

I'm guessing this "NonUniformResourceIndex" qualifier just means "we'll refuse to run if we encounter incompatible hardware". I doubt it will be used to correct rendering even on old HW, but I'll be glad to be mistaken.
Adam Sawicki
2015-09-28 21:18:17
Matias N. Goldberg: No, non-uniform resource indexing is supported even on older hardware and lower tiers, and the flag really is important.

Post comment

Nick *
Your name or nickname
E-mail
Your contact information (optional, will not be shown)
Text *
Content of your comment
Calculate *
(* - required field)
STAT NO AD [Stat] [Admin] [STAT NO AD] [pub] [Mirror] Copyright © 2004-2017 Adam Sawicki
Copyright © 2004-2017 Adam Sawicki