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.