Creating a Root Signature
Root signatures are a complex data structure containing nested structures. These can be defined programmatically using the data structure definition below (which includes methods to help initialize members). Alternatively, they can be authored in High Level Shading Language (HLSL) – giving the advantage that the compiler will validate early that the layout is compatible with the shader.
The API for creating a root signature takes in a serialized (self contained, pointer free) version of the layout description described below. A method is provided for generating this serialized version from the C++ data structure, but another way to obtain a serialized root signature definition is to retrieve it from a shader that has been compiled with a root signature.
If you wish to take advantage of driver optimizations for root signature descriptors and data, refer to Root Signature Version 1.1
- Descriptor Table Bind Types
- Descriptor Range
- Descriptor Table Layout
- Root Constants
- Root Descriptor
- Shader Visibility
- Root Signature Definition
- Root Signature Data Structure Serialization / Deserialization
- Root Signature Creation API
- Root Signature in Pipeline State Objects
- Code for Defining a Version 1.1 Root Signature
- Related topics
Descriptor Table Bind Types
The enum D3D12_DESCRIPTOR_RANGE_TYPE defines the types of descriptors that can be referenced as part of a descriptor table layout definition.
It is a range so that, for example if part of a descriptor table has 100 SRVs, that range can be declared in one entry rather than 100. So a descriptor table definition is a collection of ranges.
typedef enum D3D12_DESCRIPTOR_RANGE_TYPE
{
D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
D3D12_DESCRIPTOR_RANGE_TYPE_UAV,
D3D12_DESCRIPTOR_RANGE_TYPE_CBV,
D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER
} D3D12_DESCRIPTOR_RANGE_TYPE;
Descriptor Range
The D3D12_DESCRIPTOR_RANGE structure defines a range of descriptors of a given type (such as SRVs) within a descriptor table.
The D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND
macro can typically be used for the OffsetInDescriptorsFromTableStart
parameter of D3D12_DESCRIPTOR_RANGE. This means append the descriptor range being defined after the previous one in the descriptor table. If the application wants to alias descriptors or for some reason wants to skip slots, it can set OffsetInDescriptorsFromTableStart
to whatever offset is desired. Defining overlapping ranges of different types is invalid.
The set of shader registers specified by the combination of RangeType
, NumDescriptors
, BaseShaderRegister
, and RegisterSpace
cannot conflict or overlap across any declarations in a root signature that have common D3D12_SHADER_VISIBILITY (refer to the shader visibility section below).
Descriptor Table Layout
The D3D12_ROOT_DESCRIPTOR_TABLE structure declares the layout of a descriptor table as a collection of descriptor ranges that start at a given offset of a descriptor heap. Samplers are not allowed in the same descriptor table as CBV/UAV/SRVs.
This struct is used when the root signature slot type is set to D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE
.
To set a graphics (CBV, SRV, UAV, Sampler) descriptor table, use ID3D12GraphicsCommandList::SetGraphicsRootDescriptorTable.
To set a compute descriptor table, use ID3D12GraphicsCommandList::SetComputeRootDescriptorTable.
Root Constants
The D3D12_ROOT_CONSTANTS structure declares constants inline in the root signature that appear in shaders as one constant buffer.
This struct is used when the root signature slot type is set to D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS
.
Root Descriptor
The D3D12_ROOT_DESCRIPTOR structure declares descriptors (that appear in shaders) inline in the root signature.
This struct is used when the root signature slot type is set to D3D12_ROOT_PARAMETER_TYPE_CBV
, D3D12_ROOT_PARAMETER_TYPE_SRV
or D3D12_ROOT_PARAMETER_TYPE_UAV
.
Shader Visibility
The member of D3D12_SHADER_VISIBILITY enum set into the shader visibility parameter of D3D12_ROOT_PARAMETER determines which shaders see the contents of a given root signature slot. Compute always uses _ALL (since there is only one active stage). Graphics can choose, but if it uses _ALL, all shader stages see whatever is bound at the root signature slot.
One use of shader visibility is to help with shaders that are authored expecting different bindings per shader stage using an overlapping namespace. For example, a vertex shader may declare:
Texture2D foo : register(t0);
and the pixel shader may also declare:
Texture2D bar : register(t0);
If the application makes a root signature binding to t0 VISIBILITY_ALL, both shaders see the same texture. If the shader defines actually wants each shader to see different textures, it can define 2 root signature slots with VISIBILITY_VERTEX and _PIXEL. No matter what the visibility is on a root signature slot, it always has the same cost (cost only depending on what the SlotType is) towards one fixed maximum root signature size.
On low end D3D11 hardware, SHADER_VISIBILITY is also taken into account used when validating the sizes of descriptor tables in a root layout, since some D3D11 hardware can only support a maximum amount of bindings per-stage. These restrictions are only imposed when running on low tier hardware and do not limit more modern hardware at all.
If a root signature has multiple descriptor tables defined that overlap each other in namespace (the register bindings to the shader) and any one of them specifies _ALL for visibility, the layout is invalid (creation will fail).
Root Signature Definition
The D3D12_ROOT_SIGNATURE_DESC structure can contain descriptor tables and inline constants, each slot type defined by the D3D12_ROOT_PARAMETER structure and the enum D3D12_ROOT_PARAMETER_TYPE.
To initiate a root signature slot, refer to the SetComputeRoot*** and SetGraphicsRoot*** methods of ID3D12GraphicsCommandList.
Static samplers are described in the root signature using the D3D12_STATIC_SAMPLER structure.
A number of flags limit the access of certain shaders to the root signature, refer to D3D12_ROOT_SIGNATURE_FLAGS.
Root Signature Data Structure Serialization / Deserialization
The methods described in this section are exported by D3D12Core.dll and provide methods for serializing and deserializing a root signature data structure.
The serialized form is what is passed into the API when creating a root signature. If a shader has been authored with a root signature in it (when that capability is added), then the compiled shader will contain a serialized root signature in it already.
If an application procedurally generates a D3D12_ROOT_SIGNATURE_DESC data structure, it must make the serialized form using D3D12SerializeRootSignature. The output of that can be passed into ID3D12Device::CreateRootSignature.
If an application has a serialized root signature already, or has a compiled shader that contains a root signature and wishes to programmatically discover the layout definition (known as "reflection"), D3D12CreateRootSignatureDeserializer can be called. This generates an ID3D12RootSignatureDeserializer interface, which contains a method to return the deserialized D3D12_ROOT_SIGNATURE_DESC data structure. The interface owns the lifetime of the deserialized data structure.
Root Signature Creation API
The ID3D12Device::CreateRootSignature API takes in a serialized version of a root signature.
Root Signature in Pipeline State Objects
The methods to create pipeline state (ID3D12Device::CreateGraphicsPipelineState and ID3D12Device::CreateComputePipelineState ) take an optional ID3D12RootSignature interface as an input parameter (stored in a D3D12_GRAPHICS_PIPELINE_STATE_DESC structure). This will override any root signature already in the shaders.
If a root signature is passed into one of the create pipeline state methods, this root signature is validated against all the shaders in the PSO for compatibility and given to the driver to use with all the shaders. If any of the shaders has a different root signature in it, it gets replaced by the root signature passed in at the API. If a root signature is not passed in, all shaders passed in must have a root signature and they must match – this will be given to the driver. Setting a PSO on a command list or bundle does not change the root signature. That is accomplished by the methods SetGraphicsRootSignature and SetComputeRootSignature. By the time draw(graphics)/dispatch(compute) is invoked, the application must ensure that the current PSO matches the current root signature; otherwise, the behavior is undefined.
Code for Defining a Version 1.1 Root Signature
The example below shows how to create a root signature with the following format:
RootParameterIndex | Contents | Values |
---|---|---|
[0] | Root constants: { b2 } | (1 CBV) |
[1] | Descriptor table: { t2-t7, u0-u3 } | (6 SRVs + 4 UAVs) |
[2] | Root CBV: { b0 } | (1 CBV, static data) |
[3] | Descriptor table: { s0-s1 } | (2 Samplers) |
[4] | Descriptor table: { t8 - unbounded } | (unbounded # of SRVs, volatile descriptors) |
[5] | Descriptor table: { (t0, space1) - unbounded } | (unbounded # of SRVs, volatile descriptors) |
[6] | Descriptor table: { b1 } | (1 CBV, static data) |
If most parts of the root signature get used most of the time it can be better than having to switch the root signature too frequently. Applications should sort entries in the root signature from most frequently changing to least. When an app changes the bindings to any part of the root signature, the driver may have to make a copy of some or all of root signature state, which can become a nontrivial cost when multiplied across many state changes.
In addition, the root signature will define a static sampler that does anisotropic texture filtering at shader register s3.
After this root signature is bound, descriptor tables, root CBV and constants can be assigned to the [0..6] parameter space. e.g. descriptor tables (ranges in a descriptor heap) can be bound at each of root parameters [1] and [3..6].
CD3DX12_DESCRIPTOR_RANGE1 DescRange[6];
DescRange[0].Init(D3D12_DESCRIPTOR_RANGE_SRV,6,2); // t2-t7
DescRange[1].Init(D3D12_DESCRIPTOR_RANGE_UAV,4,0); // u0-u3
DescRange[2].Init(D3D12_DESCRIPTOR_RANGE_SAMPLER,2,0); // s0-s1
DescRange[3].Init(D3D12_DESCRIPTOR_RANGE_SRV,-1,8, 0,
D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE); // t8-unbounded
DescRange[4].Init(D3D12_DESCRIPTOR_RANGE_SRV,-1,0,1,
D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE);
// (t0,space1)-unbounded
DescRange[5].Init(D3D12_DESCRIPTOR_RANGE_CBV,1,1,
D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC); // b1
CD3DX12_ROOT_PARAMETER1 RP[7];
RP[0].InitAsConstants(3,2); // 3 constants at b2
RP[1].InitAsDescriptorTable(2,&DescRange[0]); // 2 ranges t2-t7 and u0-u3
RP[2].InitAsConstantBufferView(0, 0,
D3D12_ROOT_DESCRIPTOR_FLAG_DATA_STATIC); // b0
RP[3].InitAsDescriptorTable(1,&DescRange[2]); // s0-s1
RP[4].InitAsDescriptorTable(1,&DescRange[3]); // t8-unbounded
RP[5].InitAsDescriptorTable(1,&DescRange[4]); // (t0,space1)-unbounded
RP[6].InitAsDescriptorTable(1,&DescRange[5]); // b1
CD3DX12_STATIC_SAMPLER StaticSamplers[1];
StaticSamplers[0].Init(3, D3D12_FILTER_ANISOTROPIC); // s3
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC RootSig(7,RP,1,StaticSamplers);
ID3DBlob* pSerializedRootSig;
CheckHR(D3D12SerializeVersionedRootSignature(&RootSig,pSerializedRootSig));
ID3D12RootSignature* pRootSignature;
hr = CheckHR(pDevice->CreateRootSignature(
pSerializedRootSig->GetBufferPointer(),pSerializedRootSig->GetBufferSize(),
__uuidof(ID3D12RootSignature),
&pRootSignature));
The following code illustrates how the above root signature might be used on a graphics command list.
InitializeMyDescriptorHeapContentsAheadOfTime(); // for simplicity of the
// example
CreatePipelineStatesAhreadOfTime(pRootSignature); // The root signature is passed into
// shader / pipeline state creation
...
ID3D12DescriptorHeap* pHeaps[2] = {pCommonHeap, pSamplerHeap};
pGraphicsCommandList->SetDescriptorHeaps(2,pHeaps);
pGraphicsCommandList->SetGraphicsRootSignature(pRootSignature);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(
6,heapOffsetForMoreData,DescRange[5].NumDescriptors);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(5,heapOffsetForMisc,5000);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(4,heapOffsetForTerrain,20000);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(
3,heapOffsetForSamplers,DescRange[2].NumDescriptors);
pGraphicsCommandList->SetComputeRootConstantBufferView(2,pDynamicCBHeap,&CBVDesc);
MY_PER_DRAW_STUFF stuff;
InitMyPerDrawStuff(&stuff);
pGraphicsCommandList->SetGraphicsRoot32BitConstants(
0,RTSlot[0].Constants.Num32BitValues,&stuff,0);
SetMyRTVAndOtherMiscBindings();
for(UINT i = 0; i < numObjects; i++)
{
pGraphicsCommandList->SetPipelineState(PSO[i]);
pGraphicsCommandList->SetGraphicsRootDescriptorTable(
1,heapOffsetForFooAndBar[i],DescRange[1].NumDescriptors);
pGraphicsCommandList->SetGraphicsRoot32BitConstant(0,i,drawIDOffset);
SetMyIndexBuffers(i);
pGraphicsCommandList->DrawIndexedInstanced(...);
}
Related topics