Dynamic Resource Indexing in DirectX 12
DirectX 12 eliminates the overhead of manual descriptor table switching through dynamic resource indexing, commonly known as bindless rendering. By leveraging shader-visible descriptor heaps, unbounded resource arrays in HLSL, and modern Resource Binding Tiers, the GPU can resolve resource locations dynamically at runtime using simple integer indices. This architecture allows shaders to access thousands of unique textures, buffers, and samplers within a single draw call without requiring the CPU to re-bind descriptor tables on the command list.
Shader-Visible Descriptor Heaps
Traditional rendering pipelines require developers to group related
descriptors into tables and explicitly bind each table to the pipeline
using CPU-side API calls like
SetGraphicsRootDescriptorTable.
In DirectX 12, dynamic indexing relies on persistent, shader-visible
descriptor heaps
(D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE). A developer
allocates a large heap capable of holding thousands of Constant Buffer
Views (CBV), Shader Resource Views (SRV), and Unordered Access Views
(UAV). Instead of binding small descriptor tables per material or mesh,
the entire heap (or a massive contiguous range of it) is mapped to the
root signature once per frame or pass using
SetDescriptorHeaps.
Unbounded Arrays and Shader Model 5.1+
HLSL introduced unbounded arrays starting with Shader Model 5.1, forming the core language feature required for dynamic indexing. Shaders declare an array of resources without a fixed size, mapping it to a single register space:
Texture2D g_Textures[] : register(t0, space0);
SamplerState g_Sampler : register(s0);
Under this model, the root signature does not define a fixed-size
table for distinct textures. Instead, it exposes a single descriptor
range with UINT_MAX descriptors, effectively mapping the
shader array directly over the descriptor heap.
Runtime Index Resolution
Resources are selected at runtime by passing an integer index directly into the shader. This index can originate from:
- A per-instance vertex buffer (e.g., material IDs for instanced rendering).
- A flat root constant set via
SetGraphicsRoot32BitConstants. - Structured or byte-address buffers storing scene or draw metadata.
The shader executes a dynamic read from the array using the supplied index:
float4 color = g_Textures[materialIndex].Sample(g_Sampler, input.UV);
Uniform vs. Non-Uniform Indexing
GPU hardware executes threads in lockstep (warps or wavefronts). When threads within the same wave dynamically index different resources, standard divergence rules apply:
- Uniform Indexing: If every thread across a draw call or wave accesses the same resource index, the GPU executes a uniform load.
- Divergent (Non-Uniform) Indexing: If adjacent pixels or vertices access different descriptors simultaneously (such as different materials assigned to different polygons in an instanced draw), the compiler must handle hardware-level divergence.
To safely permit divergent access, HLSL provides the
NonUniformResourceIndex intrinsic:
uint matIndex = NonUniformResourceIndex(input.MaterialID);
float4 color = g_Textures[matIndex].Sample(g_Sampler, input.UV);
This intrinsic signals the shader compiler to emit instructions that preserve divergence safety, such as scalarizing the descriptor load across the wave or executing lane masks depending on hardware capabilities.
Hardware Tiers and Capabilities
Dynamic indexing is governed by DirectX 12 Resource Binding Tiers:
- Tier 1: Highly restricted. Descriptor heaps must be indexed statically or uniformly.
- Tier 2: Permits dynamic indexing across a broad range of resources, though limits exist regarding heap sizes and concurrent resource declarations.
- Tier 3: Removes nearly all restrictions. Shaders can dynamically and non-uniformly index across the entire capacity of a descriptor heap (up to hundreds of thousands of descriptors) without limitation. Modern desktop GPUs (NVIDIA Maxwell+, AMD GCN+, Intel Gen9+) natively support Tier 3.
By shifting resource identification from CPU-bound descriptor table bindings to GPU-driven array indices, DirectX 12 streamlines pipeline execution, minimizes API call overhead, and maximizes batching efficiency.