[0002] - Root Signatures in Clang
Status | Accepted |
---|---|
Authors |
Introduction
Root Signatures can be specified in HLSL and included in the generated DXContainer in a binary serialized format. Support for this functionality needs to be added to Clang.
This change proposes adding:
- New AST nodes to represent the root signature
- A metadata representation of the root signature so it can be stored in LLVM IR
- Validation and diagnostic generation for root signatures during semantic analysis
- Conversion of the metadata representation to the binary serialized format.
Motivation
What are Root Signatures?
In DirectX HLSL, resources can be associated with registers. For example:
StructuredBuffer<float4> b1 : register(t0);
StructuredBuffer<float4> b2 : register(t1);
StructuredBuffer<float4> bn[] : register(t2);
In Direct3D 12, resources can be assigned to these registers. Root Signatures describe how these resources are set using the Direct3D API. A Root Signature describes a list of root parameters and how they map onto registers. These Root Signatures are all compatible with the HLSL shown above:
Three parameters - two root descriptors and a descriptor table:
"SRV(t0),"
"SRV(t1),"
"DescriptorTable(SRV(t1, numDescriptors = unbounded))"
This would be set with C++ code that looks like this:
cl->SetGraphicsRootShaderResourceView(0, buffer1);
cl->SetGraphicsRootShaderResourceView(1, buffer2);
cl->SetGraphicsRootDescriptorTable(2, baseDescriptor);
A single parameter that’s a descriptor table:
"DescriptorTable(SRV(t0, numDescriptors = unbounded))"
This would be set with C++ code that looks like this:
cl->SetGraphicsRootDescriptorTable(0, baseDescriptor);
The application creates a root signature by passing a serialized root signature
blob to the
CreateRootSignature
method. This root signature must then be used when creating the Pipeline State
Object and also set on the command list before setting any of the root
parameters.
Specifying Root Signatures
A serialized root signature blob can be built in an application by using the
D3D12SerializeRootSignature
function. However, it is also helpful to be able to provide the shader compiler
with a root signature so that it can perform validation against it and the
shader being compiled. Also, the syntax for specifying a root signature in HLSL
can be more convenient than setting up the various structures required to do so
in C++. A compiled shader that contains a root signature can be passed to
CreateRootSignature
.
In HLSL, Root Signatures are specified using a domain specific language as documented here.
See below for the grammar of this DSL.
An example root signature string (see the documentation for some more extensive samples):
"RootFlags(ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT), CBV(b0)"
A root signature can be associated with an entry point using the RootSignature
attribute. eg:
[RootSignature("RootFlags(ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT), CBV(b0)")]
float4 main(float4 coord : COORD) : SV_TARGET {
// ...
}
The compiler can then verify that any resources used from this entry point are compatible with this root signature.
In addition, when using HLSL State
Objects
root signatures can also be specified using GlobalRootSignature
and
LocalRootSignature
, where the same string format is used with the state object. eg:
GlobalRootSignature my_global_root_signature = { "CBV(b0), SRV(t0)" };
LocalRootSignature my_local_root_signature = { "SRV(t1)" };
These root signatures (along with other subobjects) can be associated with exports from a shader libary like so:
SubobjectToExportsAssociation my_association = {
"my_global_root_signature",
"MyMissShader"
};
Note: HLSL State Objects are out of scope for this proposal, and so support for LocalRootSignature and GlobalRootSignature is not covered in this document.
Note on the root signature domain specific language
We have received feedback that the DSL for root signatures is not something that every language that targets DirectX would want to adopt. For this reason we need to ensure that our solution doesn’t unnecessarily tie the non-HLSL parts to it.
Root Signature Grammar
The root signature DSL is defined using a slightly modified version of Extended Backus-Naur form. Where we assume there is arbitrary whitespace between any subsequent tokens. Additionally, all keywords and enums are case-insensitive.
RootSignature = [ RootElement { ',' RootElement } ] ;
RootElement = RootFlags | RootConstants | RootCBV | RootSRV | RootUAV |
DescriptorTable | StaticSampler ;
RootFlags = 'RootFlags' '(' [ ROOT_FLAG { '|' ROOT_FLAG } ] ')' ;
ROOT_FLAG = 0 | 'ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT' |
'DENY_VERTEX_SHADER_ROOT_ACCESS' |
'DENY_HULL_SHADER_ROOT_ACCESS' |
'DENY_DOMAIN_SHADER_ROOT_ACCESS' |
'DENY_GEOMETRY_SHADER_ROOT_ACCESS' |
'DENY_PIXEL_SHADER_ROOT_ACCESS' |
'DENY_AMPLIFICATION_SHADER_ROOT_ACCESS' |
'DENY_MESH_SHADER_ROOT_ACCESS' |
'ALLOW_STREAM_OUTPUT' |
'LOCAL_ROOT_SIGNATURE' |
'CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED' |
'SAMPLER_HEAP_DIRECTLY_INDEXED' ;
RootConstants = 'RootConstants' '('
( 'num32BitConstants' '=' POS_INT ) ',' BReg
{ ',' RootConstantArgs } ')' ;
RootConstantArgs =
( 'space' '=' POS_INT ) | ( 'visibility' '=' SHADER_VISIBILITY ) ;
POS_INT = [ + ] DIGITS ;
ROOT_DESCRIPTOR_FLAGS = 0 | 'DATA_STATIC' |
'DATA_STATIC_WHILE_SET_AT_EXECUTE' |
'DATA_VOLATILE' ;
RootCBV = 'CBV' '(' BReg { ',' RootParamArgs } ')' ;
RootSRV = 'SRV' '(' TReg { ',' RootParamArgs } ')' ;
RootUAV = 'UAV' '(' UReg { ',' RootParamArgs } ')' ;
RootParamArgs =
( 'space' '=' POS_INT ) |
( 'visibility' '=' SHADER_VISIBILITY ) |
( 'flags' '=' ROOT_DESCRIPTOR_FLAGS ) ;
DescriptorTable = 'DescriptorTable' '('
[ DTClause { : DTClause } ] [ : ( 'visibility' '=' SHADER_VISIBILITY ) ]
')' ;
DTClause : CBV | SRV | UAV | Sampler ;
DESCRIPTOR_RANGE_FLAGS =
[ DESCRIPTOR_RANGE_FLAG { '|' DESCRIPTOR_RANGE_FLAG } ] ;
DESCRIPTOR_RANGE_FLAG = 0 | 'DESCRIPTORS_VOLATILE' |
'DATA_VOLATILE' | 'DATA_STATIC' |
'DATA_STATIC_WHILE_SET_AT_EXECUTE' |
'DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS' ;
CBV = 'CBV' '(' BReg ClauseArgs ')' ;
SRV = 'SRV' '(' TReg ClauseArgs ')' ;
UAV = 'UAV' '(' UReg ClauseArgs ')' ;
Sampler = 'Sampler' '(' SReg { ',' ClauseArgs } ')' ;
ClauseArgs =
( 'numDescriptors' '=' NUM_DESCRIPTORS_UNBOUNDED ) |
( 'space' '=' POS_INT ) |
( 'offset' '=' DESCRIPTOR_RANGE_OFFSET ) |
( 'flags' '=' DESCRIPTOR_RANGE_FLAGS ) ;
SHADER_VISIBILITY = 'SHADER_VISIBILITY_ALL' |
'SHADER_VISIBILITY_VERTEX' |
'SHADER_VISIBILITY_HULL' |
'SHADER_VISIBILITY_DOMAIN' |
'SHADER_VISIBILITY_GEOMETRY' |
'SHADER_VISIBILITY_PIXEL' |
'SHADER_VISIBILITY_AMPLIFICATION' |
'SHADER_VISIBILITY_MESH' ;
DESCRIPTOR_RANGE_OFFSET = 'unbounded' | POS_INT ;
DESCRIPTOR_RANGE_OFFSET = 'DESCRIPTOR_RANGE_OFFSET_APPEND' | POS_INT ;
StaticSampler = 'StaticSampler' '(' SReg { ',' SamplerArgs }')' ;
SamplerArgs =
( 'filter' '=' FILTER ) |
( 'addressU' '=' TEXTURE_ADDRESS ) |
( 'addressV' '=' TEXTURE_ADDRESS ) |
( 'addressW' '=' TEXTURE_ADDRESS ) |
( 'mipLODBias' '=' NUMBER ) |
( 'maxAnisotropy' '=' NUMBER ) |
( 'comparisonFunc' '=' COMPARISON_FUNC ) |
( 'borderColor' '=' STATIC_BORDER_COLOR ) |
( 'minLOD' '=' NUMBER ) |
( 'maxLOD' '=' NUMBER ) |
( 'space' '=' POS_INT ) |
( 'visibility' '=' SHADER_VISIBILITY ) |
( 'flags' '=' STATIC_SAMPLER_FLAGS ) ;
BReg = 'b' DIGITS ;
TReg = 't' DIGITS ;
UReg = 'u' DIGITS ;
SReg = 's' DIGITS ;
FILTER = 'FILTER_MIN_MAG_MIP_POINT' |
'FILTER_MIN_MAG_POINT_MIP_LINEAR' |
'FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT' |
'FILTER_MIN_POINT_MAG_MIP_LINEAR' |
'FILTER_MIN_LINEAR_MAG_MIP_POINT' |
'FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR' |
'FILTER_MIN_MAG_LINEAR_MIP_POINT' |
'FILTER_MIN_MAG_MIP_LINEAR' |
'FILTER_ANISOTROPIC' |
'FILTER_COMPARISON_MIN_MAG_MIP_POINT' |
'FILTER_COMPARISON_MIN_MAG_POINT_MIP_LINEAR' |
'FILTER_COMPARISON_MIN_POINT_MAG_LINEAR_MIP_POINT' |
'FILTER_COMPARISON_MIN_POINT_MAG_MIP_LINEAR' |
'FILTER_COMPARISON_MIN_LINEAR_MAG_MIP_POINT' |
'FILTER_COMPARISON_MIN_LINEAR_MAG_POINT_MIP_LINEAR' |
'FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT' |
'FILTER_COMPARISON_MIN_MAG_MIP_LINEAR' |
'FILTER_COMPARISON_ANISOTROPIC' |
'FILTER_MINIMUM_MIN_MAG_MIP_POINT' |
'FILTER_MINIMUM_MIN_MAG_POINT_MIP_LINEAR' |
'FILTER_MINIMUM_MIN_POINT_MAG_LINEAR_MIP_POINT' |
'FILTER_MINIMUM_MIN_POINT_MAG_MIP_LINEAR' |
'FILTER_MINIMUM_MIN_LINEAR_MAG_MIP_POINT' |
'FILTER_MINIMUM_MIN_LINEAR_MAG_POINT_MIP_LINEAR' |
'FILTER_MINIMUM_MIN_MAG_LINEAR_MIP_POINT' |
'FILTER_MINIMUM_MIN_MAG_MIP_LINEAR' |
'FILTER_MINIMUM_ANISOTROPIC' |
'FILTER_MAXIMUM_MIN_MAG_MIP_POINT' |
'FILTER_MAXIMUM_MIN_MAG_POINT_MIP_LINEAR' |
'FILTER_MAXIMUM_MIN_POINT_MAG_LINEAR_MIP_POINT' |
'FILTER_MAXIMUM_MIN_POINT_MAG_MIP_LINEAR' |
'FILTER_MAXIMUM_MIN_LINEAR_MAG_MIP_POINT' |
'FILTER_MAXIMUM_MIN_LINEAR_MAG_POINT_MIP_LINEAR' |
'FILTER_MAXIMUM_MIN_MAG_LINEAR_MIP_POINT' |
'FILTER_MAXIMUM_MIN_MAG_MIP_LINEAR' |
'FILTER_MAXIMUM_ANISOTROPIC' ;
TEXTURE_ADDRESS = 'TEXTURE_ADDRESS_WRAP' |
'TEXTURE_ADDRESS_MIRROR' | 'TEXTURE_ADDRESS_CLAMP' |
'TEXTURE_ADDRESS_BORDER' | 'TEXTURE_ADDRESS_MIRROR_ONCE' ;
COMPARISON_FUNC = 'COMPARISON_NEVER' | 'COMPARISON_LESS' |
'COMPARISON_EQUAL' | 'COMPARISON_LESS_EQUAL' |
'COMPARISON_GREATER' | 'COMPARISON_NOT_EQUAL' |
'COMPARISON_GREATER_EQUAL' | 'COMPARISON_ALWAYS' ;
STATIC_BORDER_COLOR = 'STATIC_BORDER_COLOR_TRANSPARENT_BLACK' |
'STATIC_BORDER_COLOR_OPAQUE_BLACK' |
'STATIC_BORDER_COLOR_OPAQUE_WHITE' |
'STATIC_BORDER_COLOR_OPAQUE_BLACK_UINT' |
'STATIC_BORDER_COLOR_OPAQUE_WHITE_UINT' ;
STATIC_SAMPLER_FLAGS = 'SAMPLER_FLAG_UINT_BORDER_COLOR' |
'SAMPLER_FLAG_NON_NORMALIZED_COORDINATES';
Root Signature Versioning
Currently, DirectX supports three versions of root signatures: 1.0, 1.1 and 1.2. Version 1.1 includes additional flags for descriptor ranges and root descriptors. See the DirectX Documentation for full details. Version 1.2 includes additions flags for static samplers. These changes are documented in the VulkanOn12 Spec for root signatures.
The metadata format specification will be the same, regardless of the version. Each version has different defaults and different valid flag combinations. Further details are specified in validations section
In the AST, the version is used during parsing, validation and metadata generation to enforce compatibility with the metadata representation.
In the metadata representation, this is specified and used to perform
the correct validation of root signatures, as well as being represented in
the final object file.
Validation and Diagnostics
As well as validating that the root signature is syntactically correct, the compiler must also validate that the shader is compatible with the it. For example, it must validate that the root signature binds each register that is used by the shader. Note that only resources referenced by the entry point need to be bound:
StructuredBuffer<float4> a : register(t0);
StructuredBuffer<float4> b : register(t1);
// valid
[RootSignature("SRV(t0)")]
float4 eg1() : SV_TARGET { return a[0]; }
// invalid: b is bound to t1 that is not bound in the root signature.
[RootSignature("SRV(t0)")]
float4 eg2() : SV_TARGET { return b[0]; }
Proposed solution
Driver
A new optional flag called -hlsl-rootsig-ver
needs to be added in Options.td
and its associated description in LangOptions.td
. If the flag is not specified,
the latest supported version of root signature will be selected by default.
Root Signatures in the AST
A new attribute, HLSLRootSignatureAttr
(defined in Attr.td
), is added to
capture the string defining the root signature. AdditionalMembers
is used to
add a member that retains the version and a member that holds the parsed
representation of the root signature.
Parsing of the root signature string happens in Sema, and some validation and diagnostics can be produced at this stage. For example:
- is the root signature string syntactically correct?
- is the specified root signature internally consistent?
- is the right type of register used in each parameter / descriptor range?
- are all parsed elements correct according to the chosen root signature version?
- is each register bound only once?
- see Validations in Sema for full list
The in-memory representation is guaranteed to be valid as far as the above checks are concerned.
The root signature AST nodes are serialized / deserialized as normal bitcode.
In the root signature DSL, a root signature is made up of a list of “root elements”. The in-memory datastructures are designed around this concept; the RootSignature class is essentially a vector of variants.
Example:
RootSignature[
"RootFlags(ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT),"
"CBV(b0, space=1),"
"StaticSampler(s1),"
"DescriptorTable("
" SRV(t0, numDescriptors=unbounded),"
" UAV(u5, space=1, numDescriptors=10, offset=5))"
]
When parsed will produce the equivalent of:
parsedRootSignature = RootSignature{
Version = Version_1_1,
RootElements = {
RootFlags(ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT),
RootCBV(0, 1), // register 0, space 1
StaticSampler(1, 0), // register 1, space 0
DescriptorTable({
SRV(0, 0, unbounded, append), // register 0, space 0, unbounded, offset append
UAV(5, 1, 10, 5) // register 5, space 1, 10 descriptors, offset 5
})
}
};
Default Values of Optional Parameters
Many of the parameters of each root element are optional. If they are not specified they will take the following default values. These comply with previous documentation here.
General Parameters:
visibility = SHADER_VISIBLITY_ALL
Root Descriptor Specific:
space = 0
Descriptor Range Specific:
numDescriptors = 1
space = 0
offset = DESCRIPTOR_RANGE_OFFSET_APPEND
Static Sampler Specific:
filter = FILTER_ANSIOTROPIC
addressU = TEXTURE_ADDRESS_WRAP
addressV = TEXTURE_ADDRESS_WRAP
addressW = TEXTURE_ADDRESS_WRAP
mipLODBias = 0.f
maxAnsiotropy = 16
comparisonFunc = COMPARISON_LESS_EQUAL
borderColor = STATIC_BORDER_COLOR_OPAQUE_WHITE
minLOD = 0.f
maxLOD = 3.402823466e+38f
Flags:
As specified in the grammar, ‘0’ denotes there are no flags set.
RootFlags = 0
ROOT_DESCRIPTOR_FLAGS
andDESCRIPTOR_RANGE_FLAGS
- Version 1.0:
CBV
:DATA_VOLATILE | DESCRIPTORS_VOLATILE
SRV
:DATA_VOLATILE | DESCRIPTORS_VOLATILE
UAV
:DATA_VOLATILE | DESCRIPTORS_VOLATILE
Sampler
:DESCRIPTORS_VOLATILE
- Version 1.1 onwards:
CBV
:DATA_STATIC_WHILE_SET_AT_EXECUTE
SRV
:DATA_STATIC_WHILE_SET_AT_EXECUTE
UAV
:DATA_VOLATILE
Sampler
:0
- Version 1.0:
STATIC_SAMPLER_FLAGS
:0
Root Signatures in the LLVM IR
During frontend code generation an IR-based representation of the root signature is generated from the in-memory data structures stored in the AST. This is stored as metadata nodes, identified by named metadata. The metadata format itself is a straightforward transcription of the in-memory data structure - so it is a list of root elements.
While the attribute is attached to a function, the metadata collects all the root signatures together, with the initial metadata associating the root signatures with functions.
Example for same root signature as above:
!dx.rootsignatures = !{!2} ; list of function/root signature pairs
!2 = !{ ptr @main, !3, i32 2 } ; function, root signature, version
!3 = !{ !4, !5, !6, !7 } ; list of root signature elements
!4 = !{ !"RootFlags", i32 1 } ; 1 = allow_input_assembler_input_layout
!5 = !{ !"RootCBV", i32 0, i32 1, i32 0, i32 0 } ; register 0, space 1, 0 = visiblity, 0 = flags
!6 = !{ !"StaticSampler", i32 1, i32 0, ... } ; register 1, space 0, (additional params omitted)
!7 = !{ !"DescriptorTable", i32 0, !8, !9 } ; 0 = visibility, range list !8, !9
!8 = !{ !"SRV", i32 0, i32 0, i32 -1, i32 -1, i32 4 } ; register 0, space 0, unbounded descriptors, offset append, flags 4
!9 = !{ !"UAV", i32 5, i32 1, i32 10, i32 5, i32 2 } ; register 5, space 1, 10 descriptors, offset 5, flags 2
See Metadata Schema for details.
The IR schema has been designed so that many of the things that need to be validated during parsing can only be represented in a valid way. For example, it is not possible to have an incorrect register type for a root parameter / descriptor range. However, it is possible to represent root signatures where registers are bound multiple times, or where there are multiple RootFlags entries, so subsequent stages should not assume that any given root signature in IR is valid.
DX Container Blob Generation
During backend code generation, the LLVM IR metadata representation of the root signature is converted to data structures that are more closely aligned to the final file format. For example, root parameters and static samplers can be intermingled in the previous formats, but are now separated into separate arrays at this point.
Example for same root signature as above:
rootSignature = RootSignature(
Version_1_1,
ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT,
{ // parameters
RootCBV(0, 1),
DescriptorTable({
SRV(0, 0, unbounded, append, 0),
UAV(5, 1, 10, 5, 0)
})
},
{ // static samplers
StaticSampler(1, 0)
});
At this point, final validation is performed to ensure that the root signature itself is valid. One key validation here is to check that each register is only bound once in the root signature. Even though this validation has been performed in the Clang frontend, we also need to support scenarios where the IR comes from other frontends, so the validation must be performed here as well.
Once the root signature itself has been validated, validation is performed against the shader to ensure that any registers that the shader uses are bound in the root signature. This validation needs to occur after any dead-code elimation has completed.
Testing
Testing DX Container generation requires a two stage testing strategy.
- Use Google Test unit tests to create and inspect binary files for specific hex values, this is useful for local validation.
- Cyclic tests, generating YAML from the binary, and then check the other way as well. Some examples are the existing DX Container unit tests. Such test infrastructure will require the design and construction of a disassembler for Root Signature Blob or DX Container.
Detailed design
Validations in Sema
All the values should be legal.
Most values like ShaderVisibility/ParameterType are covered by syntactical checks in Sema. The additional semantic rules not already covered by the grammar are listed here.
For ROOT_DESCRIPTOR_FLAGS, only the following values are valid
- For version 1.0, only the value DATA_VOLATILE is valid.
- For version 1.1 onwards, the following values are valid:
- 0
- DATA_STATIC
- DATA_STATIC_WHILE_SET_AT_EXECUTE
- DATA_VOLATILE
For DESCRIPTOR_RANGE_FLAGS on a Sampler, only the following values are valid
- For version 1.0, only the value DESCRIPTORS_VOLATILE is valid.
- For version 1.1 onwards, the following values are valid:
- 0
- DESCRIPTORS_VOLATILE
- DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS
For DESCRIPTOR_RANGE_FLAGS on a CBV/SRV/UAV
- For version 1.0, only the value DATA_VOLATILE | DESCRIPTORS_VOLATILE is valid.
- For version 1.1 onwards, the following values are valid:
- 0
- DESCRIPTORS_VOLATILE
- DATA_VOLATILE
- DATA_STATIC
- DATA_STATIC_WHILE_SET_AT_EXECUTE
- DESCRIPTORS_VOLATILE | DATA_VOLATILE
- DESCRIPTORS_VOLATILE | DATA_STATIC_WHILE_SET_AT_EXECUTE
- DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS
- DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS | DATA_VOLATILE
- DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS | DATA_STATIC
- DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS | DATA_STATIC_WHILE_SET_AT_EXECUTE
StaticSampler
- Max/MinLOD cannot be NaN.
- MaxAnisotropy cannot exceed 16.
- MipLODBias must be within range of [-16, 15.99].
- When the flag
SAMPLER_FLAG_NON_NORMALIZED_COORDINATES
is set:Filter
must be one ofFILTER_MIN_MAG_MIP_POINT
FILTER_MIN_MAG_LINEAR_MIP_POINT
FILTER_MINIMUM_MIN_MAG_MIP_POINT
FILTER_MINIMUM_MIN_MAG_LINEAR_MIP_POINT
FILTER_MAXIMUM_MIN_MAG_MIP_POINT
FILTER_MAXIMUM_MIN_MAG_LINEAR_MIP_POINT
MinLOD
andMaxLOD
must be 0AddressU
andAddressV
must beTEXTURE_ADDRESS_MODE_CLAMP
orTEXTURE_ADDRESS_MODE_BORDER
- When the flag
SAMPLER_FLAG_UINT_BORDER_COLOR
is set:BorderColor
must be one ofSTATIC_BORDER_COLOR_TRANSPARENT_BLACK
STATIC_BORDER_COLOR_OPAQUE_BLACK_UINT
STATIC_BORDER_COLOR_OPAQUE_WHITE_UINT
- If
BorderColor
is any of*_UINT
values, then flagSAMPLER_FLAG_UINT_BORDER_COLOR
must be set.
Register Value The value
0xFFFFFFFF
is invalid.CBV(b4294967295)
will result in an error as it refers past a valid address.Register Space -The range 0xFFFFFFF0 to 0xFFFFFFFF is reserved.
CBV(b0, space=4294967295)
is invalid due to the use of reserved space 0xFFFFFFFF.Resource ranges must not overlap.
CBV(b2), DescriptorTable(CBV(b0, numDescriptors=5))
will result in an error due to overlapping at b2.Note that a valid value for
numDescriptors
isunbounded
and requires overlap analysis.
Metadata Schema
Named Root Signature table
!dx.rootsignatures = !{!2}
A named metadata node, dx.rootsignatures
is used to identify the root
signature table. The table itself is a list of references to function/root
signature pairs.
Function/Root Signature Pair
!2 = !{ptr @main, !3, i32 2 }
The function/root signature associates a function (the first operand) with a reference to a root signature (the second operand) and its version (the third operand), following. The valid values for version are:
- 1: version 1.0
- 2: version 1.1
- 3: version 1.2
Root Signature
!3 = !{ !4, !5, !6, !7 }
The root signature itself consists of a list of references to root signature elements.
Root Signature Elements
Root signature elements are identified by the first operand, which is a string. The following root signature elements are defined:
- Root flags (“RootFlags”)
- Root constants (“RootConstants”)
- Root descriptors (“RootCBV”, “RootSRV”, “RootUAV”)
- Descriptor tables (“DescriptorTable”)
- Static samplers (“StaticSampler”)
As in the string representation of the root signature, the elements can appear in any order. This does mean that invalid root signatures can be represented in the metadata (eg multiple root flags). As a result of this anything that takes the metadata as input must validate the incoming metadata, even if the HLSL frontend happens to perform validation before generating the metadata.
Root Flags
!4 = { !"RootFlags", i32 1 }
Operands:
- i32: the root signature flags (D3D12_ROOT_SIGNATURE_FLAGS)
Root Constants
!123 = { !"RootConstants", i32 0, i32 1, i32 2, i32 3 }
Operands:
- i32: shader visibility (D3D12_SHADER_VISIBILITY)
- i32: shader register
- i32: register space
- i32: number 32 bit values
Root descriptors
Root descriptors come in three flavors, but they have the same structure. The flavors are:
- Root constant buffer view (“RootCBV”)
- Root shader resource view (“RootSRV”)
- Root unordered access view (“RootUAV”)
!5 = { !"RootCBV", i32 0, i32 1, i32 0, i32 0 }
Operands:
- i32: shader visibility (D3D12_SHADER_VISIBILITY)
- i32: shader register
- i32: register space
- i32: root descriptor flags (D3D12_ROOT_DESCRIPTOR_FLAGS)
Descriptor Tables
Descriptor tables are made up of descriptor ranges.
!7 = { !"DescriptorTable", i32 0, !8, !9 }
Operands:
- i32: shader visibility (D3D12_SHADER_VISIBILITY)
- remaining operands are references to descriptor ranges
Descriptor Ranges
!8 = !{ !"SRV", i32 0, i32 0, i32 -1, i32 -1, i32 4 }
!9 = !{ !"UAV", i32 5, i32 1, i32 10, i32 5, i32 2 }
Operands:
- string: type of range - “SRV”, “UAV”, “CBV” or “Sampler”
- i32: number of descriptors in the range
- number of descriptors can take the value of
-1
to denote anunbounded
descriptor range during root signature creation. This must denote the end of the table and does not allow the next descriptor range to be appended.
- number of descriptors can take the value of
- i32: base shader register
- i32: register space
- i32: offset (D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND)
- offset can take the value of
-1
which will be interpreted asD3D12_DESCRIPTOR_RANGE_OFFSET_APPEND
when the root signature is created. This denotes that this descriptor range will immediately follow the preceding range, or, there is no offset from the table start.
- offset can take the value of
- i32: descriptor range flags (D3D12_DESCRIPTOR_RANGE_FLAGS)
Static Samplers
!6 = !{ !"StaticSampler", i32 1, i32 0, ... }; remaining operands omitted for space
Operands:
- i32: Filter (D3D12_FILTER)
- i32: AddressU (D3D12_TEXTURE_ADDRESS_MODE)
- i32: AddressV (D3D12_TEXTURE_ADDRESS_MODE)
- i32: AddressW (D3D12_TEXTURE_ADDRESS_MODE)
- float: MipLODBias
- i32: MaxAnisotropy
- i32: ComparisonFunc (D3D12_COMPARISON_FUNC)
- i32: BorderColor (D3D12_STATIC_BORDER_COLOR)
- float: MinLOD
- float: MaxLOD
- i32: ShaderRegister
- i32: RegisterSpace
- i32: ShaderVisibility (D3D12_SHADER_VISIBILITY)
- i32: Flags (D3D12_SAMPLER_FLAGS)
Validations during DXIL generation
All the things validated in Sema.
All the validation rules mentioned in Validations In Sema need to be checked during DXIL generation as well. The difference between checks in Sema and DXIL generation is that Sema could rely on syntactical checks to validate values in many cases. However, in DXIL generation, all values need to be checked to ensure they fall within the correct range:
RootFlags
- (RootFlags & 0x80000fff) should equals 0.
Valid values for ShaderVisibility
- SHADER_VISIBILITY_ALL
- SHADER_VISIBILITY_VERTEX
- SHADER_VISIBILITY_HULL
- SHADER_VISIBILITY_DOMAIN
- SHADER_VISIBILITY_GEOMETRY
- SHADER_VISIBILITY_PIXEL
- SHADER_VISIBILITY_AMPLIFICATION
- SHADER_VISIBILITY_MESH
Valid values for RootDescriptorFlags
- 0
- DataVolatile
- DataStaticWihleSetAtExecute
- DataStatic
Valid values for DescriptorRangeFlags on CBV/SRV/UAV
- For root signature version 1.0 must be DATA_VOLATILE | DESCRIPTORS_VOLATILE.
- For root signature version 1.1 onwards:
- 0
- DESCRIPTORS_VOLATILE
- DATA_VOLATILE
- DATA_STATIC
- DATA_STATIC_WHILE_SET_AT_EXECUTE
- DESCRIPTORS_VOLATILE | DATA_VOLATILE
- DESCRIPTORS_VOLATILE | DATA_STATIC_WHILE_SET_AT_EXECUTE
- DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS
- DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS | DATA_VOLATILE
- DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS | DATA_STATIC
- DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS | DATA_STATIC_WHILE_SET_AT_EXECUTE
Valid values for DescriptorRangeFlags on Sampler
- For root signature version 1.0 must be DESCRIPTORS_VOLATILE.
- For root signature version 1.1 onwards:
- 0
- DESCRIPTORS_VOLATILE
- DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS
StaticSampler
Valid values for Filter
- FILTER_MIN_MAG_MIP_POINT
- FILTER_MIN_MAG_POINT_MIP_LINEAR
- FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT
- FILTER_MIN_POINT_MAG_MIP_LINEAR
- FILTER_MIN_LINEAR_MAG_MIP_POINT
- FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR
- FILTER_MIN_MAG_LINEAR_MIP_POINT
- FILTER_MIN_MAG_MIP_LINEAR
- FILTER_ANISOTROPIC
- FILTER_COMPARISON_MIN_MAG_MIP_POINT
- FILTER_COMPARISON_MIN_MAG_POINT_MIP_LINEAR
- FILTER_COMPARISON_MIN_POINT_MAG_LINEAR_MIP_POINT
- FILTER_COMPARISON_MIN_POINT_MAG_MIP_LINEAR
- FILTER_COMPARISON_MIN_LINEAR_MAG_MIP_POINT
- FILTER_COMPARISON_MIN_LINEAR_MAG_POINT_MIP_LINEAR
- FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT
- FILTER_COMPARISON_MIN_MAG_MIP_LINEAR
- FILTER_COMPARISON_ANISOTROPIC
- FILTER_MINIMUM_MIN_MAG_MIP_POINT
- FILTER_MINIMUM_MIN_MAG_POINT_MIP_LINEAR
- FILTER_MINIMUM_MIN_POINT_MAG_LINEAR_MIP_POINT
- FILTER_MINIMUM_MIN_POINT_MAG_MIP_LINEAR
- FILTER_MINIMUM_MIN_LINEAR_MAG_MIP_POINT
- FILTER_MINIMUM_MIN_LINEAR_MAG_POINT_MIP_LINEAR
- FILTER_MINIMUM_MIN_MAG_LINEAR_MIP_POINT
- FILTER_MINIMUM_MIN_MAG_MIP_LINEAR
- FILTER_MINIMUM_ANISOTROPIC
- FILTER_MAXIMUM_MIN_MAG_MIP_POINT
- FILTER_MAXIMUM_MIN_MAG_POINT_MIP_LINEAR
- FILTER_MAXIMUM_MIN_POINT_MAG_LINEAR_MIP_POINT
- FILTER_MAXIMUM_MIN_POINT_MAG_MIP_LINEAR
- FILTER_MAXIMUM_MIN_LINEAR_MAG_MIP_POINT
- FILTER_MAXIMUM_MIN_LINEAR_MAG_POINT_MIP_LINEAR
- FILTER_MAXIMUM_MIN_MAG_LINEAR_MIP_POINT
- FILTER_MAXIMUM_MIN_MAG_MIP_LINEAR
- FILTER_MAXIMUM_ANISOTROPIC
Valid values for TextureAddress
- TEXTURE_ADDRESS_WRAP
- TEXTURE_ADDRESS_MIRROR
- TEXTURE_ADDRESS_CLAMP
- TEXTURE_ADDRESS_BORDER
- TEXTURE_ADDRESS_MIRROR_ONCE
Valid values for ComparisonFunc
- 0
- COMPARISON_NEVER
- COMPARISON_LESS
- COMPARISON_EQUAL
- COMPARISON_LESS_EQUAL
- COMPARISON_GREATER
- COMPARISON_NOT_EQUAL
- COMPARISON_GREATER_EQUAL
- COMPARISON_ALWAYS
Valid values for StaticBorderColor
- STATIC_BORDER_COLOR_TRANSPARENT_BLACK
- STATIC_BORDER_COLOR_OPAQUE_BLACK
- STATIC_BORDER_COLOR_OPAQUE_WHITE
Comparison filter must have ComparisonFunc not equal to 0.
When the Filter of a StaticSampler is
FILTER_COMPARISON*
, the ComparisonFunc cannot be 0.Valid values for Flags
- For root signature versions 1.0 and 1.1, Flags must be 0
- For root signature version 1.2:
- SAMPLER_FLAG_UINT_BORDER_COLOR
- SAMPLER_FLAG_NON_NORMALIZED_COORDINATES
Samplers cannot be mixed with other resource types in a descriptor table.
Texture2D<float4> myTexture;
SamplerState mySampler;
RWTexture2D<float4> outputTexture;
[RootSignature("DescriptorTable(SRV(t0), UAV(u0), Sampler(s0))")]
[numthreads(4, 1, 1)]
void CS_Main(uint3 id : SV_DispatchThreadID)
{
outputTexture[id.xy] = myTexture.Sample(mySampler, 0);
}
NumDescriptors cannot be 0.
[RootSignature("DescriptorTable(UAV(u0, NumDescriptors=0))")]
[numthreads(4, 1, 1)]
void CS_Main(uint3 id : SV_DispatchThreadID)
{}
Cannot append range with implicit lower bound after an unbounded range.
[RootSignature("DescriptorTable(UAV(u0, NumDescriptors=unbounded), UAV(u1000, NumDescriptors=10))")]
[numthreads(4, 1, 1)]
void CS_Main(uint3 id : SV_DispatchThreadID)
{}
Overflow of shader register range.
[RootSignature("DescriptorTable(UAV(u4294967295, NumDescriptors=100))")]
[numthreads(4, 1, 1)]
void CS_Main(uint3 id : SV_DispatchThreadID)
{}
RegisterSpace
cannot use system reserved ranges.
[RootSignature("DescriptorTable(UAV(u1, NumDescriptors=100,space=4294967280))")]
[numthreads(4, 1, 1)]
void CS_Main(uint3 id : SV_DispatchThreadID)
{}
Overlapping register ranges are not allowed.
[RootSignature("DescriptorTable(UAV(u1, NumDescriptors=100), UAV(u5, NumDescriptors=5))")]
[numthreads(4, 1, 1)]
void CS_Main(uint3 id : SV_DispatchThreadID)
{}
Shader has root bindings but root signature uses a DENY flag to disallow root binding access to the shader stage.
This shader code is correct, it is supposed to fail only if compiled to a pixel shader, due to the usage of a DENY_*
flag.
cbuffer MyConstants : register(b0) // Binds to register 0, which the root signature defines
{
float4 MyColor;
};
[RootSignature("DescriptorTable(CBV(b0)), RootFlags(DENY_PIXEL_SHADER_ROOT_ACCESS)")]
float4 PS_Main() : SV_TARGET
{
return MyColor;
}
Sampler descriptor ranges can’t specify DATA_* flags.
cbuffer MyConstants : register(b0) // Binds to register 0, which the root signature defines
{
float4 MyColor;
};
[RootSignature("DescriptorTable(Sampler(s0, flags=DATA_VOLATILE))")]
float4 PS_Main() : SV_TARGET
{
return MyColor;
}
Descriptor range flags cannot specify more than one DATA_* flag.
cbuffer MyConstants : register(b0) // Binds to register 0, which the root signature defines
{
float4 MyColor;
};
[RootSignature("DescriptorTable(CBV(b0, flags=DATA_VOLATILE | DATA_STATIC_WHILE_SET_AT_EXECUTE))")]
float4 PS_Main() : SV_TARGET
{
return MyColor;
}
Descriptor range flags cannot specify DESCRIPTORS_VOLATILE with the DATA_STATIC flag at the same time.
cbuffer MyConstants : register(b0) // Binds to register 0, which the root signature defines
{
float4 MyColor;
};
[RootSignature("DescriptorTable(CBV(b0, flags=DESCRIPTORS_VOLATILE | DATA_STATIC ))")]
float4 PS_Main() : SV_TARGET
{
return MyColor;
}
Root descriptor flags cannot specify more than one DATA_* flag at a time.
cbuffer MyConstants : register(b0) // Binds to register 0, which the root signature defines
{
float4 MyColor;
};
[RootSignature("CBV(b0, flags=DATA_VOLATILE | DATA_STATIC)")]
float4 PS_Main() : SV_TARGET
{
return MyColor;
}
Descriptor range is not fully bound in root signature.
cbuffer MyConstants : register(b0) // Binds to register 0, which the root signature does not define
{
float4 MyColor;
};
[RootSignature("CBV(b1)")]
float4 PS_Main() : SV_TARGET
{
return MyColor;
}
This error might also happen, if the shader visibility removes registers that were binding other resources. For example:
cbuffer MyConstants : register(b0) // Binds to register 0, which the root signature defines
{
float4 MyColor;
};
[RootSignature("DescriptorTable(CBV(b0), visibility=SHADER_VISIBILITY_VERTEX)")]
float4 PS_Main() : SV_TARGET
{
return MyColor;
}
SRV or UAV root descriptors can only be Raw or Structured buffers.
Texture2D<float4> gTex : register(t0); // texture object bound to t0
SamplerState gSamp : register(s0);
struct PSInput {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
[RootSignature("DescriptorTable(Sampler(s0)), SRV(t0)")]
float4 PS_Main(PSInput IN) : SV_TARGET
{
return gTex.Sample(gSamp, IN.uv);
}
Alternatives considered (Optional)
Store Root Signatures as Strings
The root signature could be stored in its string form in the AST and in LLVM IR. There’s a simplicity to this, and precedant with some other attributes. However, it does mean that there will be multiple places where the string needs to be parsed creating challenges for code organization as well as performance concerns.
In addition, it unnecessarily ties all parts of the system with the current root signature DSL, which is something that should we want to avoid.
Store Root Signatures in the serialized Direct3D format
Direct3D specifies a serialized format for root signatures, that we will eventually need to generate in order to populate the DXBC container. One option would be to generate this early on and store it in the AST / IR.
This approach was not chosen because the serialized format is not well suited to manipulation and storage in Clang/LLVM, it loses data (eg the language allow a “DESCRIPTOR_RANGE_OFFSET_APPEND” value that should be resolved in the serialized format).
In addition, the specific serialized format is subject to change as the root signature specification evolves and it seems that this is something that Clang and LLVM should be decoupled from as much as possible.
Introduce a HLSLRootSignatureDecl
Although the current design does not support HLSL state objects - specifically
the LocalRootSignature
and GlobalRootSignature
subobjects - we could
anticipate their needs and add a HLSLRootSignatureDecl
that could be shared
between HLSLRootSignatureAttr
and whatever AST nodes are introduces for these
subojects. The problem is that we’d need to design pretty much the entire HLSL
state objects feature to do this properly. Instead, we chose to build a complete
feature without state object support and accept that some refactoring in this
area may be necessary to share code between root signatures in attributes and
root signatures in subojects.
Deduplicate root signatures
It’s possible that the same root signature string could be presented to the compiler multiple times. An extra layer of indirection in the parsing code could allow us to avoid parsing the root signature multiple times.
As this would strictly be an optimization and isn’t required for correctness, this is something that will be considered if profiling shows us that
- multiple duplicate root signatures is a common scenario and
- parsing them takes a significant amount of time.
Reused / share D3D code
We could conceivably just use the D3D12 D3D12_VERSIONED_ROOT_SIGNATURE_DESC
datastructures for this, rather than building our own parallel versions. Also,
we could even try and get D3D’s serialization code open-sourced so we don’t need
to maintain multiple implementations of it. This doesn’t mesh well with LLVM
since it would be adding external dependencies. We would also need to ensure
that LLVM can be built in all the host environments it supports - this means
binary dependencies are not viable, and any existing code would likely need to
be reworked so much for portability and comformance with LLVM coding conventions
that the effort would not be worthwhile.