Skip to content

Commit 181a5a6

Browse files
authored
[0018] SPIRV resource representation (#98)
Adds a proposal for how HLSL resources will be represented in llvm-ir when targeting SPIR-V.
1 parent 16a235d commit 181a5a6

File tree

1 file changed

+283
-0
lines changed

1 file changed

+283
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
<!-- {% raw %} -->
2+
3+
# HLSL resources in SPIR-V
4+
5+
* Proposal: [0018](0018-spirv-resource-representation.md)
6+
* Author(s): [Steven Perron](https://github.com/s-perron)
7+
* Status: **Design In Progress**
8+
9+
*During the review process, add the following fields as needed:*
10+
11+
* PRs: [#114273](https://github.com/llvm/llvm-project/pull/114273),
12+
[#111052](https://github.com/llvm/llvm-project/pull/111052),
13+
[#111564](https://github.com/llvm/llvm-project/pull/111564),
14+
[#115178](https://github.com/llvm/llvm-project/pull/115178)
15+
16+
## Introduction
17+
18+
There is a need to represent the HLSL resources in llvm-ir in a way that the
19+
SPIR-V backend is able to create the correct code. We have already done some
20+
implementation work for `Buffer` and `RWBuffer`. This was done as a
21+
proof-of-concept, and now we needed to determine how the other resource types
22+
will be represented.
23+
24+
## Motivation
25+
26+
The HLSL resources are fundamental to HLSL, and they are required in a Vulkan
27+
implementation.
28+
29+
## Proposed solution
30+
31+
We want to match the general solution proposed in
32+
[0006-resource-representations.md](0006-resource-representations.md). The
33+
`@llvm.spv.handle.fromBinding` intrinsic will be used to get a handle to the
34+
resource. It will return a target type to represent the handle. Then other
35+
intrinsics will be used to access the resource using the handle. Previous
36+
proposals left open what the target types should be for SPIR-V.
37+
38+
The type for the handle will depend on the type of resource, and will be
39+
detailed in the following sections.
40+
41+
The following sections will reference table 4 in the
42+
[shader resource interface](https://docs.vulkan.org/spec/latest/chapters/interfaces.html#interfaces-resources)
43+
for Vulkan.
44+
45+
### SPIR-V target types
46+
47+
There must be appropriate SPIR-V target types to represent the HLSL resources.
48+
We could try to represent the resources using the exact SPIR-V type that will be
49+
needed. The problem is that the HLSL resources does not map too closely with
50+
SPIR-V.
51+
52+
Consider `StructuredBuffer`, `RWStructuredBuffer`,
53+
`RasterizerOrderedStructuredBuffer`, `AppendStructureBuffer`, and
54+
`ConsumeStructuredBuffer`. These resource types do not map directly to SPIR-V.
55+
They have multiple implicit features that need to map to different SPIR-V:
56+
57+
1. They all contain an array of memory that maps to a storage buffer.
58+
2. Other than `StructuredBuffer`, they all contain a separate counter variable
59+
that is its own storage buffer.
60+
3. The references to `RasterizerOrderedStructuredBuffer` are contained in
61+
implicit critical regions. In SPIR-V, explicit instructions are used to
62+
start and stop the critical region.
63+
64+
This makes it impossible to create a handle type that maps directly to a SPIR-V
65+
type. For now, the counter variable will not be handled. We will create a target
66+
type `spirv.VulkanBuffer` to represent a storage or uniform buffer:
67+
68+
```
69+
target("spirv.VulkanBuffer", ElementType, StorageClass, IsWriteable, IsROV)
70+
```
71+
72+
`ElementType` is the type for the storage buffer array, and `StorageClass` is
73+
the storage class for the array. `IsWriteable` is true if the resource can be
74+
written to, and `IsROV` is true if it is a rasterizer order view.
75+
76+
In the SPIR-V backend, there will be a legalization pass that will lower the
77+
`spirv.VulkanBuffer` type to code closer to the SPIR-V to be generated:
78+
79+
1. Calls to `@llvm.spv.resource.getpointer` will have the handle replaced by
80+
the handle of the array.
81+
2. If the type of the original handle is rasterizer ordered, all uses of
82+
`@llvm.spv.resource.getpointer` will be surrounded by instructions to begin
83+
and end the critical region.
84+
85+
A separate legalization pass will then move the critical region markers so that
86+
they follow the rules required by the SPIR-V specification. This will be the
87+
same as the
88+
[`InvocationInterlockPlacementPass`](https://github.com/KhronosGroup/SPIRV-Tools/blob/682bcd51548e670811f1d03511968bb59a1157ce/source/opt/invocation_interlock_placement_pass.h)
89+
pass in SPIR-V Tools.
90+
91+
The types for the buffers must have an
92+
[explicit layout](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#ExplicitLayout).
93+
The layout information will be obtained from the DataLayout class:
94+
95+
1. Struct offsets will come from `DataLayout::getStructLayout`, which returns
96+
the offset for each member.
97+
2. The array stride will be the size of the array elements. This assumes that
98+
structs have appropriate padding at the end to ensure its size is a multiple
99+
of its alignment.
100+
3. Matrix stride?
101+
4. Row major vs Col major?
102+
103+
It is Clang's responsibility to make sure that the data layout is set correctly,
104+
and that the structs have the correct explicit padding for this to be correct.
105+
106+
### Textures and typed buffers
107+
108+
All of these resource types are represented using an image type in SPIRV. The
109+
`Texture*` types are implemented as sampled images. The `RWTexture*` types are
110+
implemented as storage images. `Buffer` is implemented as a uniform buffer, and
111+
`RWBuffer` is implemented as a storage texel buffer.
112+
113+
For these cases the return type from `@llvm.spv.handle.fromBinding` would be the
114+
image type matching the resource type:
115+
116+
```llvm-ir
117+
target("spirv.Image", ...)
118+
```
119+
120+
The details of the `spirv.Image` type depend on the specific declaration. Except
121+
for the image format, the value for each operand is given in the
122+
[Mapping Resource Attributes to DXIL and SPIR-V](https://github.com/llvm/wg-hlsl/blob/main/proposals/0015-resource-attributes-in-dxil-and-spirv.md)
123+
proposal. For all resource types other than `RWBuffer<T>` and `RWTexture*<T>`,
124+
the image format will be `Unknown`.
125+
126+
For `RWBuffer<T>` and `RWTexture*<T>` resource types, if the Vulkan version is
127+
1.3 or later, the image format will be `Unknown`. This satisfies
128+
[VUID-RuntimeSpirv-apiVersion-07954](https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#VUID-RuntimeSpirv-apiVersion-07954)
129+
and
130+
[VUID-RuntimeSpirv-apiVersion-07955](https://docs.vulkan.org/spec/latest/appendices/spirvenv.html#VUID-RuntimeSpirv-apiVersion-07954).
131+
132+
Otherwise, the image format for those resource types will be determined by the
133+
template type `T`, and will match the existing behaviour implemented in DXC.
134+
135+
Note that this creates a disconnect with the
136+
[Universal Validation Rules](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_universal_validation_rules).
137+
Specifically,
138+
139+
> All OpSampledImage instructions, or instructions that load an image or sampler
140+
> reference, must be in the same block in which their Result <id> are consumed.
141+
142+
The image object is conceptually loaded at the location that
143+
`@llvm.spv.handle.fromBinding` is called. There is nothing forcing this
144+
intrinsic to be called in the same basic block in which it is used. It is the
145+
responsibility of the backend to replicate the load in the basic block in which
146+
it is used.
147+
148+
### Structured Buffers
149+
150+
The handle for structured buffers will be
151+
152+
| HLSL Resource Type | Handle Type |
153+
|--------------------------------------|----------------------------------------------------|
154+
| StructuredBuffer<T> | spirv.VulkanBuffer(T, StorageBuffer, false, false) |
155+
| RWStructuredBuffer<T> | spirv.VulkanBuffer(T, StorageBuffer, true, false) |
156+
| RasterizerOrderedStructuredBuffer<T> | spirv.VulkanBuffer(T, StorageBuffer, true, true) |
157+
| AppendStructuredBuffer<T> | spirv.VulkanBuffer(T, StorageBuffer, true, false) |
158+
| ConsumeStructuredBuffer<T> | spirv.VulkanBuffer(T, StorageBuffer, true, false) |
159+
160+
### Texture buffers
161+
162+
Texture buffers are implemented in SPIR-V as storage buffers. From a SPIR-V
163+
perspective, this makes it the same as a `StructureBuffer`, and will be
164+
represented the same way:
165+
166+
```
167+
spirv.VulkanBuffer(T, StorageBuffer, false, false)
168+
```
169+
170+
### Constant buffers
171+
172+
In SPIR-V, constant buffers are implemented as uniform buffers. The only
173+
difference between a uniform buffer and storage buffer is the storage class.
174+
Uniform buffers use the `Uniform` storage class. The handle type will be:
175+
176+
```
177+
spirv.VulkanBuffer(T, Uniform, false, false)
178+
```
179+
180+
### Samplers
181+
182+
The type of the handle for a sampler will be:
183+
184+
```llvm-ir
185+
target("spirv.Sampler")
186+
```
187+
188+
This is the same for a `SamplerState` and `SamplerComparisonState`.
189+
190+
### Byte address buffers
191+
192+
DXC represents byte address buffers as a storage buffer of 32-bit integers. The
193+
problem with this is that loads and store require lots of data manipulation to
194+
correctly handle the data. It also means we cannot do atomic operations unless
195+
they are 32-bit operations.
196+
197+
Because of this limitation, we do not want Clang to enforce a particular
198+
representation. Instead, we can represent the buffer as a buffer with a `void`
199+
type. The backend indicates to the backend it can choose the representation, but
200+
it is responsible for updating accessed to match the representation it chooses.
201+
202+
Note that if
203+
[untyped pointers](https://htmlpreview.github.io/?https://github.com/KhronosGroup/SPIRV-Registry/blob/main/extensions/KHR/SPV_KHR_untyped_pointers.html)
204+
are available, this will map naturally to untyped pointers.
205+
206+
| HLSL Resource Type | Handle Type |
207+
|------------------------------------|-------------------------------------------------------|
208+
| ByteAddressBuffer | spirv.VulkanBuffer(void, StorageBuffer, false, false) |
209+
| RWByteAddressBuffer | spirv.VulkanBuffer(void, StorageBuffer, true, false) |
210+
| RasterizerOrderedByteAddressBuffer | spirv.VulkanBuffer(void, StorageBuffer, true, true) |
211+
212+
### Feedback textures
213+
214+
These resources do not have a straight-forward implementation in SPIR-V, and
215+
they were not implemented in DXC. We will issue an error if these resource are
216+
used when targeting SPIR-V.
217+
218+
## Alternatives considered (Optional)
219+
220+
### Returning pointers as the handle
221+
222+
We considered making all handles return by `@llvm.spv.handle.fromBinding` to be
223+
pointers to some type. For textures, it would return a pointer to the image
224+
type.
225+
226+
This would have been nice because load of the image object would no longer be in
227+
`@llvm.spv.handle.fromBinding` and would be in the intrinsic that uses the
228+
handle. That would automatically make it in the same basic block as it use.
229+
230+
The problem is that this does not work well for structured buffers, because, as
231+
far as HLSL is concerned, the handle for a structured buffer references two
232+
resources as detailed above. There is no way to represent this properly.
233+
234+
Less important, but still worth mentioning, is that in SPIR-V, the image object
235+
is the handle to the image. We chose the design the was the better match
236+
conceptually. Replicating the load of the image object is not a difficult
237+
problem to solve.
238+
239+
## Open Questions
240+
241+
1. How will the binding for the counter resource be represented?
242+
243+
The design for the counter variable associated with structured buffer types is
244+
not complete. However, there is one important restriction the Clang codegen does
245+
not diverge too much from DXIL:
246+
247+
The storage for the storage buffer and the counter variable must be access
248+
through the same handle. The intrinsics that use it will determine which
249+
resource is being accessed.
250+
251+
They will have to somehow be added to the `resource.gethandlefrombinding`. They
252+
cannot be added to the target type. If they were, the types for the resource
253+
aliases would not match, causing problem in codegen. For example:
254+
255+
```c++
256+
RWStructuredBuffer<int> a;
257+
258+
// The type for `b` handle will be different from `a`'s handle, because it
259+
// needs a different counter var.
260+
RWStructuredBuffer<int> b;
261+
262+
static RWStructuredBuffer<int> c; // What type should `c`'s be?
263+
264+
void main() {
265+
c = a; // It must match the type for a.
266+
c = b; // It must also match the type for b.
267+
```
268+
269+
2. Do we need `vk::image_format` for Vulkan 1.3 and later?
270+
271+
We need to determine whether we can deprecate the use of `vk::image_format` for
272+
Vulkan 1.3 and later. We could potentially use unknown for all resource types.
273+
We need to assess if there is any advantage to specifying a particular format.
274+
If no advantage exists, then we should not attempt to support specific formats.
275+
276+
3. Determine how to add the appropriate decorations for matrices.
277+
278+
If a matrix is part of a storage buffer, it must have an explicit layout with
279+
MatrixStride and either RowMajor or ColMajor decorations. Because matrices are
280+
not yet implemented, we cannot yet determine how these decorations will be
281+
added.
282+
283+
<!-- {% endraw %} -->

0 commit comments

Comments
 (0)