@@ -35,204 +35,236 @@ resource. It will return a target type to represent the handle. Then other
35
35
intrinsics will be used to access the resource using the handle. Previous
36
36
proposals left open what the target types should be for SPIR-V.
37
37
38
- The general pattern for the solution will be that ` @llvm.spv.handle.fromBinding `
39
- will return a SPIR-V pointer to a type ` T ` . The type for ` T ` will be detailed
40
- when discussing each HLSL resource type. The SPIR-V backend will create a global
41
- variable with type ` T ` if ` range_size ` is 1, and ` T[range_size] ` otherwise.
42
-
43
- The reason we want ` @llvm.spv.handle.fromBinding ` to return a pointer is to make
44
- it easier to satisfy the SPIR-V requirements in the
45
- [ Universal Validation Rules] ( https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_universal_validation_rules ) .
46
- Specifically,
47
-
48
- > All OpSampledImage instructions, or instructions that load an image or sampler
49
- > reference, must be in the same block in which their Result <id > are consumed.
50
-
51
- It is the responsibility of the intrinsics that use the handles to load the
52
- images and samplers.
38
+ The type for the handle will depend on the type of resource, and will be
39
+ detailed in the following sections.
53
40
54
41
The following sections will reference table 4 in the
55
42
[ shader resrouce interface] ( https://docs.vulkan.org/spec/latest/chapters/interfaces.html#interfaces-resources )
56
43
for Vulkan.
57
44
58
- ### Textures and typed buffers
59
-
60
- All of these resource types are represented using an image type in SPIRV. The
61
- ` Texture* ` types are implemented as sampled images. the ` RWTexture* ` types are
62
- implemented as storage images. ` Buffer ` is implemented as a uniform buffer, and
63
- ` RWBuffer ` is implemented as a storage buffer.
45
+ ### SPIR-V target types
64
46
65
- For these cases the return type from ` @llvm.spv.handle.fromBinding ` would be:
47
+ The must be appropriate SPIR-V target types to represent the HLSL resources. We
48
+ could try to represent the resources using the exact SPIR-V type that will be.
49
+ The problem is that the HLSL resources does not map too closely with SPIR-V.
66
50
67
- ``` llvm-ir
68
- target("spirv.Pointer", 0 /* UniformConstantStorageClass */, target("spirv.Image", ...))
69
- ```
51
+ Consider ` StructuredBuffer ` , ` RWStructuredBuffer ` ,
52
+ ` RasterizerOrderedStructuredBuffer ` , ` AppendStructureBuffer ` , and
53
+ ` ConsumeStructuredBuffer ` . These resource types do not map directly to SPIR-V.
54
+ They have multiple implicit features that need to map to different SPIR-V:
70
55
71
- The details of the ` spirv.Image ` type depend on the specific declaration, and
72
- are detailed in the "Mapping Resource Attributes to DXIL and SPIR-V" proposal.
56
+ 1 . They all contains an array of memory that maps to a storage buffer.
57
+ 2 . Other than ` StructuredBuffer ` , they all contains a separate counter variable
58
+ that is its own storage buffer.
59
+ 3 . The references to ` RasterizerOrderedStructuredBuffer ` are contained in
60
+ implicit critical regions. In SPIR-V, explicit instructions are used to
61
+ start and stop the critical region.
73
62
74
- ### Structured buffers and texture buffers
63
+ This makes it impossible to create a handle type that maps directly to a SPIR-V
64
+ type. To handle this, we will create a target type ` spv.Buffer ` :
75
65
76
- All structured buffers and texture buffers are represented as storge buffers in
77
- SPIR-V. The Vulkan documentation has two ways to represent a storage buffer. The
78
- first representation was removed in SPIR-V 1.3 (Vulkan 1.1). We will generate
79
- only the second representation.
66
+ ```
67
+ target("spv.Buffer", ElementType, StorageClass, IsWriteable, IsROV)
68
+ target("spv.Buffer", ElementType, StorageClass, IsWriteable, IsROV, CounterSet, CounterBinding)
69
+ ```
80
70
81
- For these cases the return type from ` @llvm.spv.handle.fromBinding ` for
82
- ` RWStructuredBuffer<T> ` would be:
71
+ ` ElementType ` is the type for the storage buffer array, and ` StorageClass ` is
72
+ the storage class for the array. ` IsWritable ` is true of the resource an be
73
+ written to, and ` IsROV ` is true if it is a rasterizer order view. If the
74
+ resource has an associated counter variable, its set and binding can be provided
75
+ in ` CounterSet ` and ` CounterBinding ` .
76
+
77
+ In the SPIR-V backend, there will be a legalization pass that will lower the
78
+ ` spv.Buffer ` type to code closer to the SPIR-V to be generated:
79
+
80
+ 1 . Calls to ` @llvm.spv.handle.fromBinding ` will be replaced by two calls. One
81
+ that returns a handle to the array, and another that return a handle to the
82
+ counter, if necessary.
83
+ 2 . Calls to ` @llvm.spv.resource.getpointer ` will have the handle replaced by
84
+ the handle of the array.
85
+ 3 . Calls to ` @llvm.spv.resource.updatecounter ` will be replaced by a call to
86
+ ` @llvm.spv.resource.getpointer ` with the handle of the counter followed by
87
+ an atomic add.
88
+ 4 . If the type of the original handle is rasterizer ordered, all uses of
89
+ ` @llvm.spv.resource.getpointer ` will be surrounded by instructions to begin
90
+ and end the critical region.
91
+
92
+ A separate legalization pass will then move the critical region markers so that
93
+ they follow the rules required by the SPIR-V specification. This will be the
94
+ same as the
95
+ [ ` InvocationInterlockPlacementPass ` ] ( https://github.com/KhronosGroup/SPIRV-Tools/blob/682bcd51548e670811f1d03511968bb59a1157ce/source/opt/invocation_interlock_placement_pass.h )
96
+ pass in SPIR-V Tools.
97
+
98
+ The types for the handles will be target types that represent pointers. The
99
+ handle for the array will be
83
100
84
101
``` llvm-ir
85
102
%T = type { ... } ; Fully laid out version of T.
86
103
%T1 = type { [0 x %T] } ; The SPIR-V backend should turn the array into a runtime array.
87
- target("spirv.Type", target(spirv.Literal, /* StorageBuffer */ 12),
88
- target("spirv.DecoratedType", %T1, /* block */ 2),
89
- /* OpTypePointer */32)
104
+ target("spirv.Type", target(spirv.Literal, StorageClass), %T1,
105
+ /* OpTypePointer */32)
90
106
```
91
107
92
- Note that the llvm-ir does not have to be exactly that, but should be
93
- equivalent. For example, there does not have to be a identified type for ` %T1 ` .
108
+ TODO: I need to check if a call to ` int_spv_assign_decoration ` can be added that
109
+ will decorate the target type with the block decoration .
94
110
95
- For ` StructuredBuffer<T> ` ,
111
+ The types for the buffers must have an
112
+ [ explicit layout] ( https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#ExplicitLayout ) .
113
+ The layout information will be obtained from the DataLayout class:
96
114
97
- ``` llvm-ir
98
- %T = type { ... } ; Fully laid out version of T.
99
- %T1 = type { [0 x %T] } ; The SPIR-V backend should turn the array into a runtime array.
100
- target("spirv.Type", target(spirv.Literal, /* StorageBuffer */ 12),
101
- target("spirv.DecoratedType",
102
- target("spirv.DecoratedType", %T1, /* block */ 2),
103
- /* NonWriteable */ 24),
104
- /* OpTypePointer */32)
105
- ```
115
+ 1 . Struct offsets will come from ` DataLayout::getStructLayout ` , which returns
116
+ the offset for each member.
117
+ 2 . The array stride will be the size of the array elements. This assumes that
118
+ structs have appropriate padding at the end to ensure its size is a multiple
119
+ of its alignment.
120
+ 3 . Matrix stride?
121
+ 4 . Row major vs Col major?
106
122
107
- This is the same as ` RWStructuredBuffer ` except that it has the NonWritable
108
- decoration .
123
+ It is Clang's responsibility to make sure that the data layout is set correctly,
124
+ and that the structs have the correct explicit padding for this to be correct .
109
125
110
- The specific layout for ` T ` is out of scope for this proposal, and will be part
111
- of another proposal.
126
+ The type of the handle for the counter will be
112
127
113
- ### Constant buffers
128
+ ``` llvm-ir
129
+ target("spirv.Type", target(spirv.Literal, /* StorageBuffer */ 12),
130
+ target("spirv.DecoratedType", { i32 }, /* block */ 2),
131
+ /* OpTypePointer */32)
132
+ ```
114
133
115
- Constant buffers are implemented as uniform buffers. They will have the exact
116
- same representation as a ` StructuredBuffer ` except that the storage class will
117
- be ` Uniform ` instead of ` StorageBuffer ` . The layout will potentially be
118
- different.
134
+ ### Textures and typed buffers
119
135
120
- ### Samplers
136
+ All of these resource types are represented using an image type in SPIRV. The
137
+ ` Texture* ` types are implemented as sampled images. The ` RWTexture* ` types are
138
+ implemented as storage images. ` Buffer ` is implemented as a uniform buffer, and
139
+ ` RWBuffer ` is implemented as a storage buffer.
121
140
122
- The return type from ` @llvm.spv.handle.fromBinding ` for a sampler will be:
141
+ For these cases the return type from ` @llvm.spv.handle.fromBinding ` would be the
142
+ image type matching the resource type:
123
143
124
144
``` llvm-ir
125
- target("spirv.Type", target(spirv.Literal, /* UniformConstantStorageClass */ 0),
126
- target("spirv.Sampler"),
127
- /* OpTypePointer */32)
145
+ target("spirv.Image", ...)
128
146
```
129
147
130
- This is the same for a ` SamplerState ` and ` SamplerComparisonState ` .
131
-
132
- ### Byte address buffers
148
+ The details of the ` spirv.Image ` type depend on the specific declaration, and
149
+ are detailed in the "Mapping Resource Attributes to DXIL and SPIR-V" proposal.
133
150
134
- If
135
- [ untyped pointers] ( https://htmlpreview.github.io/?https://github.com/KhronosGroup/SPIRV-Registry/blob/main/extensions/KHR/SPV_KHR_untyped_pointers.html )
136
- are available, we will want to use the untyped pointers. However, if they are
137
- not available, we will need to represent it as an array of integers, as is done
138
- in DXC.
151
+ Note that this creates disconnect with the
152
+ [ Universal Validation Rules] ( https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_universal_validation_rules ) .
153
+ Specifically,
139
154
140
- TODO: I'm thinking it is the responsibility of SPIRVTargetInfo to make this
141
- decision .
155
+ > All OpSampledImage instructions, or instructions that load an image or sampler
156
+ > reference, must be in the same block in which their Result < id > are consumed .
142
157
143
- If
144
- [ untyped pointers] ( https://htmlpreview.github.io/?https://github.com/KhronosGroup/SPIRV-Registry/blob/main/extensions/KHR/SPV_KHR_untyped_pointers.html )
145
- are available, then the return type from ` @llvm.spv.handle.fromBinding ` for a
146
- ` RWByteAddressBuffer ` would be:
158
+ The image object is conceptually loaded at the location that
159
+ ` @llvm.spv.handle.fromBinding ` is called. There is nothing forcing this
160
+ intrinsic to be called in the same basic block in which it is used. It is the
161
+ responsibility of the backend to replicate the load in the basic block in which
162
+ it is used.
163
+
164
+ ### Structured Buffers
165
+
166
+ The handle for structured buffers will
167
+
168
+ | HLSL Resource Type | Handle Type |
169
+ | ------------------------------------ | ----------------------------------- |
170
+ | StructuredBuffer<T > | spv.Buffer(T, StorageBuffer, false, |
171
+ : : false) :
172
+ | RWStructuredBuffer<T > | spv.Buffer(T, StorageBuffer, true, |
173
+ : : false, set, binding) :
174
+ | RasterizerOrderedStructuredBuffer<T > | spv.Buffer(T, StorageBuffer, true, |
175
+ : : true, set, binding) :
176
+ | AppendStructuredBuffer<T > | spv.Buffer(T, StorageBuffer, true, |
177
+ : : false, set, binding) :
178
+ | ConsumeStructuredBuffer<T > | spv.Buffer(T, StorageBuffer, true, |
179
+ : : false, set, binding) :
180
+
181
+ The ` set ` and ` binding ` will be set following the convention in DXC. The set
182
+ will be the same as the set for the main storage. If the
183
+ ` vk::counter_binding(b) ` attribute is attached to the variable, then the binding
184
+ will be ` b ` . Otherwise, the ` binding ` will be the binding number of the main
185
+ storage plus 1.
186
+
187
+ ### Texture buffers
188
+
189
+ Texture buffers are implemented in SPIR-V as storage buffers. From a SPIR-V
190
+ perspective, this makes it the same as a ` StructureBuffer ` , and will be
191
+ represented the same way:
147
192
148
- ``` llvm-ir
149
- target("spirv.Type", target(spirv.Literal, /* StorageBuffer */ 12),
150
- /* OpTypeUntypedPointerKHR */ 4417)
193
+ ```
194
+ spv.Buffer(T, StorageBuffer, false, false)
151
195
```
152
196
153
- This assumes that knowledge of untyped pointers is added to the SPIR-V backend.
154
- If it is not added, we will have to explicitly attached the capability and
155
- extension to the type.
197
+ ### Constant buffers
156
198
157
- If untyped pointers are not available, then the return type from
158
- ` @llvm.spv.handle.fromBinding ` would be:
199
+ In SPIR-V, constant buffers are implemented as uniform buffers. The only
200
+ difference between a uniform buffer and storage buffer is the storage class.
201
+ Uniform buffers use the ` Uniform ` storage class. The handle type will be:
159
202
160
- ``` llvm-ir
161
- target("spirv.Type", target(spirv.Literal, /* StorageBuffer */ 12),
162
- target("spirv.DecoratedType", { [0 x i32] }, /* block */ 2),
163
- /* OpTypePointer */32)
203
+ ```
204
+ spv.Buffer(T, Uniform, false, false)
164
205
```
165
206
166
- It would be the same for ` ByteAddressBuffer ` except the ` NonWriteable `
167
- decoration is will be added.
207
+ ### Samplers
168
208
169
- The intrinsics that use the ByteAddressBuffers will not change depending on the
170
- type used. The SPIR-V backend should recognize the type and implement the
171
- operation accordingly.
209
+ The type of the handle for a sampler will be:
172
210
173
- ### Rasterizer Order Views
211
+ ``` llvm-ir
212
+ target("spirv.Sampler")
213
+ ```
174
214
175
- TODO: This needs to be redone. We might need to add the attribute to the
176
- fromBinding call site.
215
+ This is the same for a ` SamplerState ` and ` SamplerComparisonState ` .
177
216
178
- If a resource is a rasterizer order view it will generate the exact same code as
179
- its regular version except
217
+ ### Byte address buffers
180
218
181
- 1 . All uses of the resource will have a call site attribute indicating that
182
- this call must be part of the fragment shader's critical section. This is a
183
- new target attribute which will be call ` spirv.InterlockedCritical ` .
184
- 2 . The entry points (that reference directly or indirectly?) the ROVs will have
185
- an attribute ` spirv.InterlockMode ` which could have the value
186
- ` SampleOrdered ` , ` SampleUnordered ` , ` PixelOrdered ` , ` PixelUnordered ` ,
187
- ` ShadingRateOrdered ` , or ` ShadingRateUnordered ` .
219
+ DXC represents byte address buffers as a storage buffer of 32-bit integers. The
220
+ problem with this is that loads and store require lots of data manipulation to
221
+ correctly handle the data. It also means we cannot do atomic operations unless
222
+ they are 32-bit operations.
188
223
189
- A pass similar to the ` InvocationInterlockPlacementPass ` pass in SPIR-V Tools
190
- will be run in the SPIR-V backend to add instructions to begin and end the
191
- critical section. This pass will be run after structurizing the llvm-ir, and
192
- before ISel .
224
+ Because of this limitation, we do not want Clang to enforce a particular
225
+ representation. Instead, we can represent the buffer as a buffer with a ` void `
226
+ type. The backend indicates to the backend it can choose the representation, but
227
+ it is responsible for updating accessed to match the representation it chooses .
193
228
194
- The SPIR-V backend will add the appropriate interlock execution mode to the
195
- module based on the attribute on the entry point.
229
+ Note that if
230
+ [ untyped pointers] ( https://htmlpreview.github.io/?https://github.com/KhronosGroup/SPIRV-Registry/blob/main/extensions/KHR/SPV_KHR_untyped_pointers.html )
231
+ are available, this will map naturally to untyped pointers.
232
+
233
+ | HLSL Resource Type | Handle Type |
234
+ | ---------------------------------- | -------------------------------------- |
235
+ | ByteAddressBuffer | spv.Buffer(void, StorageBuffer, false, |
236
+ : : false) :
237
+ | RWByteAddressBuffer | spv.Buffer(void, StorageBuffer, true, |
238
+ : : false) :
239
+ | RasterizerOrderedByteAddressBuffer | spv.Buffer(void, StorageBuffer, true, |
240
+ : : true) :
196
241
197
242
### Feedback textures
198
243
199
244
These resources do not have a straight-forward implementation in SPIR-V, and
200
245
they were not implemented in DXC. We will issue an error if these resource are
201
246
used when targeting SPIR-V.
202
247
203
- ## Detailed design
204
-
205
- * The detailed design is not required until the feature is under review.*
206
-
207
- This section should grow into a full specification that will provide enough
208
- information for someone who isn't the proposal author to implement the feature.
209
- It should also serve as the basis for documentation for the feature. Each
210
- feature will need different levels of detail here, but some common things to
211
- think through are:
212
-
213
- * Is there any potential for changed behavior?
214
- * Will this expose new interfaces that will have support burden?
215
- * How will this proposal be tested?
216
- * Does this require additional hardware/software/human resources?
217
- * What documentation should be updated or authored?
218
-
219
248
## Alternatives considered (Optional)
220
249
221
- ### Returning an image type in ` @llvm.spv.handle.fromBinding `
250
+ ### Returning pointers as the handle
251
+
252
+ We considered making all handles return by ` @llvm.spv.handle.fromBinding ` to be
253
+ pointers to some type. For textures, it would return a pointer to the image
254
+ type.
222
255
223
- We considered implementing returning the ` target("spirv.Image", ...) ` type from
224
- ` @llvm.spv.handle.fromBinding ` instead of returning a pointer to the type. This
225
- caused problems because the uses of the image have to be in the same basic
226
- block, and, in general, the uses of the handle are not in the same basic block
227
- as the call to ` @llvm.spv.handle.fromBinding ` .
256
+ This would have been nice because load of the image object would no longer be in
257
+ ` @llvm.spv.handle.fromBinding ` and would be in the intrinsic that uses the
258
+ handle. That would automatically make it in the same basic block as it use.
228
259
229
- To fix this, we would have to add a pass in the backend to fix up the problem by
230
- replicating code, but this seems less desirable when we can generate the code
231
- correctly .
260
+ The problem is that this does not work well for structured buffers, because, as
261
+ far as HLSL is concerned, the handle for a structured buffer references two
262
+ resources as detailed above. There is no way to represent this properly .
232
263
233
- It also makes the implementation of ` @llvm.spv.handle.fromBinding ` more
234
- complicated because it will have to be treated differently than structured
235
- buffers.
264
+ Less important, but still worth mentioning, is that in SPIR-V, the image object
265
+ is the handle to the image. We chose the design the was the better match
266
+ conceptually. Replicating the load of the image object is not a difficult
267
+ problem to solve.
236
268
237
269
## Acknowledgments (Optional)
238
270
0 commit comments