Skip to content

Commit d47c389

Browse files
committed
Rewrite proposal to work with the counter buffer for Structured buffers.
1 parent 027ee74 commit d47c389

File tree

1 file changed

+172
-140
lines changed

1 file changed

+172
-140
lines changed

proposals/XXXX-spirv-resource-representation.md

+172-140
Original file line numberDiff line numberDiff line change
@@ -35,204 +35,236 @@ resource. It will return a target type to represent the handle. Then other
3535
intrinsics will be used to access the resource using the handle. Previous
3636
proposals left open what the target types should be for SPIR-V.
3737

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.
5340

5441
The following sections will reference table 4 in the
5542
[shader resrouce interface](https://docs.vulkan.org/spec/latest/chapters/interfaces.html#interfaces-resources)
5643
for Vulkan.
5744

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
6446

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.
6650

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:
7055

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.
7362

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`:
7565

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+
```
8070

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
83100

84101
```llvm-ir
85102
%T = type { ... } ; Fully laid out version of T.
86103
%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)
90106
```
91107

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.
94110

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:
96114

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?
106122

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.
109125

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
112127

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+
```
114133

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
119135

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.
121140

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:
123143

124144
```llvm-ir
125-
target("spirv.Type", target(spirv.Literal, /* UniformConstantStorageClass */ 0),
126-
target("spirv.Sampler"),
127-
/* OpTypePointer */32)
145+
target("spirv.Image", ...)
128146
```
129147

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.
133150

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,
139154

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.
142157
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:
147192

148-
```llvm-ir
149-
target("spirv.Type", target(spirv.Literal, /* StorageBuffer */ 12),
150-
/* OpTypeUntypedPointerKHR */ 4417)
193+
```
194+
spv.Buffer(T, StorageBuffer, false, false)
151195
```
152196

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
156198

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:
159202

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)
164205
```
165206

166-
It would be the same for `ByteAddressBuffer` except the `NonWriteable`
167-
decoration is will be added.
207+
### Samplers
168208

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:
172210

173-
### Rasterizer Order Views
211+
```llvm-ir
212+
target("spirv.Sampler")
213+
```
174214

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`.
177216

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
180218

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.
188223

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.
193228

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) :
196241

197242
### Feedback textures
198243

199244
These resources do not have a straight-forward implementation in SPIR-V, and
200245
they were not implemented in DXC. We will issue an error if these resource are
201246
used when targeting SPIR-V.
202247

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-
219248
## Alternatives considered (Optional)
220249

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.
222255

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.
228259

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.
232263

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.
236268

237269
## Acknowledgments (Optional)
238270

0 commit comments

Comments
 (0)