Bindless & Dynamic Resources

33.1 From slots to bindless

fixed slots: 128 SRVs, 16 samplers, 14 CBs per stage, 8–64 UAVs descriptor tables + unbounded arrays (Texture2D gTex[]: register(t0, space1))

dynamic resources: ResourceDescriptorHeap[i] and SamplerDescriptorHeap[i] — no tables or arrays needed

33.2 SM 6.6 ResourceDescriptorHeap

Requires SM 6.6 and Resource Binding Tier 3 (all D3D12 Ultimate hardware).

If the index can differ across lanes in a wave (e.g. per-pixel material IDs in a deferred pass), wrap it: ResourceDescriptorHeap[NonUniformResourceIndex(idx)]. Omitting it is undefined behavior that often "works" on one vendor and breaks on another.

Bindless pairs naturally with GPU-driven rendering: one ExecuteIndirect or mesh-shader dispatch draws everything, with indices in per-draw data.

Vulkan equivalent: descriptor indexing (VK_EXT_descriptor_indexing), mutable descriptors, or descriptor buffers; DXC can compile ResourceDescriptorHeap to SPIR-V by mapping it onto runtime descriptor arrays (requires descriptor indexing support); see DXC's SPIR-V documentation for the exact set/binding mapping.

// Root signature: only root constants; shaders index the heaps directly
#define BindlessRS \
    "RootFlags(CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED | SAMPLER_HEAP_DIRECTLY_INDEXED), " \
    "RootConstants(num32BitConstants=4, b0)"

struct DrawConstants
{
    uint vertexBufferIndex;      // descriptor heap index of a ByteAddressBuffer
    uint materialBufferIndex;    // StructuredBuffer<MaterialGPU>
    uint materialIndex;
    uint frameCBIndex;           // ConstantBuffer<FrameCB>
};
ConstantBuffer<DrawConstants> gDC : register(b0);

struct FrameCB     { float4x4 viewProj; float3 camPos; float time; };
struct MaterialGPU { uint albedoTex; uint normalTex; uint samplerIdx; float roughness; };
struct BLVOut      { float4 pos : SV_Position; float2 uv : TEXCOORD0; };

[RootSignature(BindlessRS)]
BLVOut BindlessVS(uint vid : SV_VertexID)
{
    ConstantBuffer<FrameCB> frame = ResourceDescriptorHeap[gDC.frameCBIndex];
    ByteAddressBuffer vb = ResourceDescriptorHeap[gDC.vertexBufferIndex];
    // Vertex pulling: position (12 bytes) + uv (8 bytes) = 20-byte stride
    float3 pos = asfloat(vb.Load3(vid * 20));
    float2 uv  = asfloat(vb.Load2(vid * 20 + 12));
    BLVOut o;
    o.pos = mul(frame
…