Registers, Spaces & Root Signatures

register(t0, space1) places a resource in a separate namespace so different systems (per-frame, per-material, per-draw) can each start at t0.

15.1 Register types

SRVs: textures, buffers, structured buffers, acceleration structures

15.2 Register spaces (SM 5.1+)

register(t0, space1) places a resource in a separate namespace so different systems (per-frame, per-material, per-draw) can each start at t0. Spaces map naturally to Vulkan descriptor sets.

// space0: per frame, space1: per material, space2: per draw
ConstantBuffer<FrameData>    gFrame      : register(b0, space0);
Texture2D                    gMatTex[4]  : register(t0, space1);   // resource array
SamplerState                 gSamplers[] : register(s0, space1);   // unbounded array (SM 5.1)
ConstantBuffer<DrawData>     gDraw       : register(b0, space2);
Texture2D                    gAllTextures[] : register(t0, space3);   // bindless-style table

float4 SampleMaterial(uint texIndex, float2 uv)
{
    // Indexing with a value that varies across the wave requires NonUniformResourceIndex
    return gAllTextures[NonUniformResourceIndex(texIndex)].Sample(gSamplers[0], uv);
}

15.3 Root signatures

In D3D12, a root signature describes what the shaders can access and how the CPU binds it. Root parameters can be:

Cost (DWORDs of 64) 1 per 32-bit value tiny, frequently changing data (draw ID) Root descriptor (CBV/SRV/UAV) a buffer GPU virtual address (no format; buffers only) a range of descriptors in a heap (textures, many resources) immutable sampler baked into the signature

Root signatures can be authored in HLSL as a string attribute:

The root signature is then embedded in the shader blob, and pso.pRootSignature can be created from the blob (CreateRootSignature with the compiled bytecode), or compiled standalone: dxc -T rootsig_1_1 -E MyRS rs.hlsl -Fo rs.cso.

#define MyRS \
    "RootFlags(ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT), " \
    "RootConstants(num32BitConstants=4, b0), " \
    "CBV(b1), " \
    "DescriptorTable(SRV(t0, numDescriptors=4), visibility=SHADER_VISIBILITY_PIXEL), " \
    "DescriptorTable(UAV(u0, numDescriptors=1)), " \
    "StaticSampler(s0, filter=FILTER_ANISOTROPIC, maxAnisotropy=8, " \
    "              addressU=TEXTURE_ADDRESS_WRAP, addressV=TEXTURE_ADDRESS_WRAP), " \
    "StaticSampler(s1, filter=FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT, " \
    "              comparisonFunc=COMPARISON_LESS_EQUAL, addressU=TEXTURE_ADDRESS_CLAMP, " \
    "              addressV=TEXTURE_ADDRESS_CLAMP)"

cbuffer DrawConstants : register(b0) { uint gDrawId; uint3 gPad; };

[RootSignature(MyRS)]
float4 PSMain(float4 pos : SV_Position) : SV_Target { return float4(gDrawId.xxx / 255.0, 1); }

15.4 Binding strategy

Frequency-based: per-frame data in root CBV, per-draw constants in root constants, material textures in tables.

Keep the root signature small (≤ 64 DWORDs total; smaller is faster on some hardware).

Use SHADER_VISIBILITY_* to limit which stages see a parameter.

Modern engines go bindless (Chapter 33): one big descriptor heap, indices passed via root constants.

D3D11 has no root signatures: bind with VSSetConstantBuffers, PSSetShaderResources, PSSetSamplers, CSSetUnorderedAccessViews; spaces are not supported.