|
| 1 | +# Input semantics |
| 2 | + |
| 3 | +* Proposal: [NNNN](http://NNNN-input-semantics.md) |
| 4 | +* Author(s): [Nathan Gauër](https://github.com/Keenuts) |
| 5 | +* Status: **Design In Progress** |
| 6 | + |
| 7 | +## Introduction |
| 8 | + |
| 9 | +HLSL shaders can read form the pipeline state using [semantics](https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics). |
| 10 | +This proposal looks into how to implement input semantics in Clang. |
| 11 | +Output semantics are out of scope of this proposal, but some parts will be |
| 12 | +similar. |
| 13 | + |
| 14 | +## Motivation |
| 15 | + |
| 16 | +HLSL input semantics are a core part of the shading language. |
| 17 | + |
| 18 | +## Behavior |
| 19 | + |
| 20 | +### HLSL |
| 21 | + |
| 22 | +Input semantics are used by the API to determine how to connect pipeline |
| 23 | +data to the shader. |
| 24 | + |
| 25 | +Input semantic attributes can be applied to a function parameter, or a struct |
| 26 | +field's declaration. They only carry meaning when used on an entry point |
| 27 | +parameter, or a struct type used by one of the entry point parameters. |
| 28 | +All other uses are simply ignored. |
| 29 | + |
| 30 | +HLSL has two kinds of semantics: System and User. |
| 31 | +System semantics are linked to specific parts of the pipeline, while |
| 32 | +user semantics are just a way for the user to link the output of a stage |
| 33 | +to the input of another stage. |
| 34 | + |
| 35 | +When the semantic attribute is applied to a struct (type or value), it applies |
| 36 | +recursively to each inner fields, shadowing any other semantic. |
| 37 | + |
| 38 | +Each scalar in the entrypoint must have a semantic attribute attached, either |
| 39 | +directly or inherited from the parent struct type. |
| 40 | + |
| 41 | +Example: |
| 42 | + |
| 43 | +```hlsl |
| 44 | +struct B { |
| 45 | + int b1 : SB1; |
| 46 | + int b2 : SB2; |
| 47 | +}; |
| 48 | +
|
| 49 | +struct C { |
| 50 | + int c1 : SC1; |
| 51 | + int c2 : SC2; |
| 52 | +}; |
| 53 | +
|
| 54 | +struct D { |
| 55 | + int d1; |
| 56 | +}; |
| 57 | +
|
| 58 | +struct E { |
| 59 | + int e1 : EC; |
| 60 | + int e2 : EC; |
| 61 | +}; |
| 62 | +
|
| 63 | +struct F { |
| 64 | + int f1 : FC; |
| 65 | + int f2 : FC; |
| 66 | +}; |
| 67 | +
|
| 68 | +[[shader("pixel")]] |
| 69 | +void main(float a : SA, B b : SB, C c, D d, E e, F f : SF) { } |
| 70 | +``` |
| 71 | + |
| 72 | +In this example: |
| 73 | +- `a` is linked to the semantic `SA`. |
| 74 | +- `b.b1` and `b.b2` are linked to the semantic `SB` because `SB` shadows the |
| 75 | + semantics attached to each field. |
| 76 | +- `c.c1` has the semantic `SC1`, and `c.c2` the semantic `SC2`. |
| 77 | +- `d.d1`, hence `d`, is illegal: no semantic is attached to `d.d1`. |
| 78 | +- `e.e1` and `e.e2` are invalid: `EC` usage is duplicated without being inherited. |
| 79 | +- `f.f1` and `f.f2` semantic is `SF`, shadowing the duplicated `FC` semantic. |
| 80 | + |
| 81 | +**Note**: HLSL forbids explicit **non-shadowed** semantic duplication. In this |
| 82 | +sample, the parameter `e` uses `E`, which explicitly declares two fields with |
| 83 | +the same semantic. This is illegal. \ |
| 84 | +`b` has the semantic `SB` applied on the whole struct. Meaning all its fields |
| 85 | +share the same semantic `SB`. This is legal because the duplication comes |
| 86 | +from inheritance. |
| 87 | +Lastly, `f` explicitly duplicates the semantic `FC`. But because those are |
| 88 | +shadowed by the semantic `SF`, this is valid HLSL. |
| 89 | + |
| 90 | +**Note**: Implicit semantic duplication is allowed for user semantics, but |
| 91 | +always forbidden for system semantics. |
| 92 | + |
| 93 | +### SPIR-V |
| 94 | + |
| 95 | +On the SPIR-V side, user semantics are translated into `Location` |
| 96 | +decorated `Input` variables. The `Location` decoration takes an index. |
| 97 | +System semantics are either translated to `Location` decorated `Input` |
| 98 | +variables, or `BuiltIn` decorated `Input` variables. |
| 99 | + |
| 100 | +In the example above, there are no system semantics, meaning every |
| 101 | +parameter would get a `Location` decorated variable associated. |
| 102 | +Each scalar field/parameter is associated with a unique index starting at 0, |
| 103 | +from the first parameter's field to the last parameter's field. |
| 104 | + |
| 105 | +In the sample above: |
| 106 | + |
| 107 | +- `a` would have the `Location 0`. |
| 108 | +- `b.b1` would have the `Location 1`. |
| 109 | +- `b.b2` would have the `Location 2`. |
| 110 | +- `c.c1` would have the `Location 3`. |
| 111 | +- ... |
| 112 | + |
| 113 | +It is also possible to explicitly set the index, using the |
| 114 | +`[[vk::location(/* Index */)]]` attribute. \ |
| 115 | +Mixing implicit and explicit location assignment is **not legal**. |
| 116 | +Hence interaction between both mechanism is out of scope. |
| 117 | + |
| 118 | +### DXIL |
| 119 | + |
| 120 | +On the DXIL side, some system semantics are translated to builtin function |
| 121 | +calls. But most are visible along user semantics in the `input signature`. |
| 122 | + |
| 123 | +To pass data between stages, DirectX provides a fixed list of <4 x 32bit> |
| 124 | +registers. E.g: 32 x <4 x 32 bit> for VS in D3D11. |
| 125 | + |
| 126 | +The input signature assigns to each semantic a `Name`, `Index`, `Mask`, |
| 127 | +`Register`, `SysValue`, and `Format`. |
| 128 | + |
| 129 | +- `Name`: the semantic attribute name. |
| 130 | +- `Index`: used to differentiate two inputs sharing the same `Name`. |
| 131 | +- `Register`: determines which 16-byte register the value shall be read from. |
| 132 | +- `Mask`:what 32-bit part is used for this value, e.g: `xyz` or `x`. |
| 133 | +- `Format`: how to interpret the data, e.g. `float` or `int`. |
| 134 | +- `SysValue`: `NONE` for user semantic, a known string for system semantics. |
| 135 | + |
| 136 | +Unlike in SPIR-V, the `Register` and `Mask` cannot be simply deduced from the |
| 137 | +iteration order. Those value depends on the [packing rules](https://github.com/Microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#signature-packing) |
| 138 | +of the inputs. |
| 139 | + |
| 140 | +## Proposal |
| 141 | + |
| 142 | +SPIR-V and DXIL semantic lowering is very different. SPIR-V respects the |
| 143 | +parameters/field order, while DXIL packs by following an extensive set of |
| 144 | +rules. The System vs User semantics handling is also divergent. |
| 145 | + |
| 146 | +This means we will be able to share the Sema checks, but will have to build |
| 147 | +two distinct paths during codegen. |
| 148 | + |
| 149 | +### Sema |
| 150 | + |
| 151 | +Type check for semantics is currently handled in `SemaDeclAttr.cpp`. \ |
| 152 | +Iteration is done on each declaration, and if a semantic attribute is present, |
| 153 | +the type is checked. |
| 154 | +Each declaration being handled independently, this method does not support |
| 155 | +inherited/shadowed semantic attributes. |
| 156 | + |
| 157 | +Sema checks are divided into three parts: |
| 158 | + - check for type compatibility between the variable and the semantic. |
| 159 | + - check for semantic duplication. |
| 160 | + - check for invalid system semantic usage depending on shader stage. |
| 161 | + |
| 162 | +Proposition is to remove the type validation from the `SemaDeclAttr` and |
| 163 | +move it later into SemaHLSL, with the other checks. |
| 164 | +Idea is we need to have the inheritance rules to check types, so we should |
| 165 | +avoid duplicating this logic in two places. |
| 166 | + |
| 167 | +The pseudo-code for this check should be: |
| 168 | + |
| 169 | +```cpp |
| 170 | + void checkSemantic(std::unordered_set<HLSLAnnotationAttr> &UsedSemantics, |
| 171 | + DeclaratorDecl *Decl, |
| 172 | + HLSLAnnotationAttr *InheritedSemantic = nullptr) { |
| 173 | + |
| 174 | + HLSLAnnotationAttr *Semantic = InheritedSemantic ? InheritedSemantic : Decl->get<HLSLAnnotationAttr>(); |
| 175 | + RecordDecl *RD = dyn_cast<RecordDecl>(Decl); |
| 176 | + |
| 177 | + // Case 1: type is a scalar, and we have a semantic. End case. |
| 178 | + if (Semantic && !RD) { |
| 179 | + if (UsedSemantics.contains(Semantic) && !InheritedSemantic) |
| 180 | + Fail("Explicit semantic duplication", Decl->getLocation()) |
| 181 | + |
| 182 | + UsedSemantics.insert(Semantic) |
| 183 | + diagnoseSemanticType(Decl, Semantic); |
| 184 | + diagnoseSemanticEnvironment(Decl, Context.ShaderEnv); |
| 185 | + return; |
| 186 | + } |
| 187 | + |
| 188 | + // Case 2: type is scalar, but we have no semantic: error |
| 189 | + if (!RD) |
| 190 | + Fail("Missing semantic", Decl->getLocation()); |
| 191 | + |
| 192 | + // Case 3: it's a struct. Simply recurse, optionnally inherit semantic. |
| 193 | + if (RecordDecl *RD = dyn_cast<RecordDecl>(Decl)) { |
| 194 | + for (FieldDecl *FD : Decl->asRecordDecl()->getFields()) |
| 195 | + checkSemantic(UsedSemantics, FD, Semantic); |
| 196 | + } |
| 197 | + } |
| 198 | + |
| 199 | + ... |
| 200 | + |
| 201 | + std::unordered_set<clang::HLSLAnnotationAttr> UsedSemantics; |
| 202 | + for (ParmVarDecl Decl : entrypoint->getParams()) { |
| 203 | + checkSemantic(UsedSemantics, Decl, /* InheritedSemantic= */ nullptr); |
| 204 | + } |
| 205 | +``` |
| 206 | +
|
| 207 | +At this point, we are guaranteed to have only valid and unique semantics, as |
| 208 | +well as valid types. |
| 209 | +
|
| 210 | +### CodeGen |
| 211 | +
|
| 212 | +DXIL and SPIR-V codegen will be very different, but the flattening/inheritance |
| 213 | +bit can be shared. |
| 214 | +
|
| 215 | +The proposal is to provide a sorted list in `CGHLSLRuntime`: |
| 216 | +
|
| 217 | +```cpp |
| 218 | +struct SemanticIO { |
| 219 | + // The active semantic for this scalar/field. |
| 220 | + HLSLAnnotationAttr *Semantic; |
| 221 | +
|
| 222 | + // Info about this field/scalar. |
| 223 | + DeclaratorDecl *Decl; |
| 224 | + llvm::Type *Type; |
| 225 | +
|
| 226 | + // The loaded value in the wrapper for this scalar/field. Each target |
| 227 | + // must provide this value. |
| 228 | + llvm::Value *Value = nullptr; |
| 229 | +}; |
| 230 | +
|
| 231 | +Vector<SemanticIO> InputSemantics; |
| 232 | +``` |
| 233 | + |
| 234 | +The proposal is to let each target implement the logic in CGHLSLRuntime to |
| 235 | +load the semantics, and set the `Value` field of the `SemanticIO` struct. |
| 236 | +The order of the vector represents the flattening order. |
| 237 | + |
| 238 | +Providing the full list should allow DXIL to easily implement packing rules. |
| 239 | + |
| 240 | +At this stage, `CGHLSLRuntime` will have an ordered list of `SemanticIO` |
| 241 | +structs. The order is from the first parameter/field to the last |
| 242 | +parameter/field (DFS), and each will point to an `llvm::Value`. |
| 243 | + |
| 244 | +The common code will then build the arguments list for the entrypoint call |
| 245 | +using the provided `llvm::Value`. This is shared across targets. |
0 commit comments