|
| 1 | +<!-- {% raw %} --> |
| 2 | + |
| 3 | +# HLSL resources in SPIR-V |
| 4 | + |
| 5 | +* Proposal: [NNNN](NNNN-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 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 discusses 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. |
| 53 | + |
| 54 | +The following sections will reference table 4 in the |
| 55 | +[shader resrouce interface](https://docs.vulkan.org/spec/latest/chapters/interfaces.html#interfaces-resources) |
| 56 | +for Vulkan. |
| 57 | + |
| 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. |
| 64 | + |
| 65 | +For these cases the return type from `@llvm.spv.handle.fromBinding` would be: |
| 66 | + |
| 67 | +```llvm-ir |
| 68 | +target("spirv.Pointer", 0 /* UniformConstantStorageClass */, target("spirv.Image", ...)) |
| 69 | +``` |
| 70 | + |
| 71 | +The details of the `spirv.Image` type depend on the specific declaration. |
| 72 | + |
| 73 | +TODO: We need to determine the image format. |
| 74 | + |
| 75 | +### Structured buffers and texture buffers |
| 76 | + |
| 77 | +All structured buffers and texture buffers are represented as storge buffers in |
| 78 | +SPIR-V. The Vulkan documentation has two ways to represent a storage buffer. The |
| 79 | +first representation was removed in SPIR-V 1.3 (Vulkan 1.1). We will generate |
| 80 | +only the second representation. |
| 81 | + |
| 82 | +For these cases the return type from `@llvm.spv.handle.fromBinding` for |
| 83 | +`RWStructuredBuffer<T>` would be: |
| 84 | + |
| 85 | +```llvm-ir |
| 86 | +target("spirv.Pointer", 12 /* StorageBuffer */, T') |
| 87 | +``` |
| 88 | + |
| 89 | +Where `T' = struct { T t[]; }`. |
| 90 | + |
| 91 | +For `StructuredBuffer<T>`, |
| 92 | + |
| 93 | +```llvm-ir |
| 94 | +target("spirv.Pointer", 12 /* StorageBuffer */, const T') |
| 95 | +``` |
| 96 | + |
| 97 | +TODO: We need to determine how the Block decoration will be added. The current |
| 98 | +idea will have clang describe the type in detail, which means that clang needs a |
| 99 | +way to say that the type `T'` requires the decoration. There is a |
| 100 | +`spirv.Decoration` metadata, but that works on global variable only at this |
| 101 | +time. We could have the target spirv type access the metadata node as an |
| 102 | +operand. This interacts with the implementation of `vk::ext_decorate`, which |
| 103 | +will have to be able to apply decoration to members of types. |
| 104 | + |
| 105 | +TODO: We need to determine what the layout of the struct should be. Will we |
| 106 | +support multiple layout as we do in DXC? This could be communicated to the back |
| 107 | +end by adding the decorations to the type in the same way as the previous todo. |
| 108 | + |
| 109 | +Note: We are in the middle of implementing `vk::SpirvType` in clang. That should |
| 110 | +add a way to implement the target types without adding a specific |
| 111 | +`spirv.Pointer` target type. |
| 112 | + |
| 113 | +### Constant buffers |
| 114 | + |
| 115 | +Constant buffers are implemented as uniform buffers. They will have the exact |
| 116 | +same representation as a structured buffer except that the storage class will be |
| 117 | +`Uniform` instead of `UniformConstant`. |
| 118 | + |
| 119 | +### Samplers |
| 120 | + |
| 121 | +For these cases the return type from `@llvm.spv.handle.fromBinding` would be: |
| 122 | + |
| 123 | +```llvm-ir |
| 124 | +target("spirv.Pointer", 0 /* UniformConstantStorageClass */, target("spirv.Sampler")) |
| 125 | +``` |
| 126 | + |
| 127 | +This is the same for a `SamplerState` and `SamplerComparisonState`. |
| 128 | + |
| 129 | +### Byte address buffers |
| 130 | + |
| 131 | +If |
| 132 | +[untyped pointers](https://htmlpreview.github.io/?https://github.com/KhronosGroup/SPIRV-Registry/blob/main/extensions/KHR/SPV_KHR_untyped_pointers.html) |
| 133 | +are available, we will want to use the untyped pointers. However, if they are |
| 134 | +not available, we will need to represent it as an array of integers, as is done |
| 135 | +in DXC. |
| 136 | + |
| 137 | +TODO: I'm thinking it is the responsibility of SPIRVTargetInfo to make this |
| 138 | +decision. |
| 139 | + |
| 140 | +If |
| 141 | +[untyped pointers](https://htmlpreview.github.io/?https://github.com/KhronosGroup/SPIRV-Registry/blob/main/extensions/KHR/SPV_KHR_untyped_pointers.html) |
| 142 | +are available, then the return type from `@llvm.spv.handle.fromBinding` would |
| 143 | +be: |
| 144 | + |
| 145 | +```llvm-ir |
| 146 | +target("spirv.UntypedPointer", 12 /* StorageBuffer */) |
| 147 | +``` |
| 148 | + |
| 149 | +If untyped pointers are not available, then the return type from |
| 150 | +`@llvm.spv.handle.fromBinding` would be: |
| 151 | + |
| 152 | +```llvm-ir |
| 153 | +target("spirv.Pointer", 12 /* StorageBuffer */, { uint32_t[] }) |
| 154 | +``` |
| 155 | + |
| 156 | +TODO: Add the `block` decoration. |
| 157 | + |
| 158 | +The intrinsics that use the ByteAddressBuffers will not change depending on the |
| 159 | +type used. The SPIR-V backend should recognize the type and implement |
| 160 | +accordingly. |
| 161 | + |
| 162 | +### Rasterizer Order Views |
| 163 | + |
| 164 | +If a resource is a rasterizer order view it will generate the exact same code as |
| 165 | +its regular version except |
| 166 | + |
| 167 | +1. All uses of the resource will have a call site attribute indicating that |
| 168 | + this call must be part of the fragment shader's critical section. This is a |
| 169 | + new target attribute which will be call `spirv.InterlockedCritical`. |
| 170 | +2. The entry points (that reference directly or indirectly?) the ROVs will have |
| 171 | + an attribute `spirv.InterlockMode` which could have the value |
| 172 | + `SampleOrdered`, `SampleUnordered`, `PixelOrdered`, `PixelUnordered`, |
| 173 | + `ShadingRateOrdered`, or `ShadingRateUnordered`. |
| 174 | + |
| 175 | +A pass similar to the `InvocationInterlockPlacementPass` pass in SPIR-V Tools |
| 176 | +will be run in the SPIR-V backend to add instructions to begin and end the |
| 177 | +critical section. This pass will be run after structurizing the llvm-ir, and |
| 178 | +before ISel. |
| 179 | + |
| 180 | +The SPIR-V backend will add the appropriate interlock execution mode to the |
| 181 | +module based on the attribute on the entry point. |
| 182 | + |
| 183 | +### Feedback textures |
| 184 | + |
| 185 | +These resources do not have a straight-forward implementation in SPIR-V, and |
| 186 | +they were not implemented in DXC. We will issue an error if these resource are |
| 187 | +used when targeting SPIR-V. |
| 188 | + |
| 189 | +## Detailed design |
| 190 | + |
| 191 | +*The detailed design is not required until the feature is under review.* |
| 192 | + |
| 193 | +This section should grow into a full specification that will provide enough |
| 194 | +information for someone who isn't the proposal author to implement the feature. |
| 195 | +It should also serve as the basis for documentation for the feature. Each |
| 196 | +feature will need different levels of detail here, but some common things to |
| 197 | +think through are: |
| 198 | + |
| 199 | +* Is there any potential for changed behavior? |
| 200 | +* Will this expose new interfaces that will have support burden? |
| 201 | +* How will this proposal be tested? |
| 202 | +* Does this require additional hardware/software/human resources? |
| 203 | +* What documentation should be updated or authored? |
| 204 | + |
| 205 | +## Alternatives considered (Optional) |
| 206 | + |
| 207 | +### Returning an image type in `@llvm.spv.handle.fromBinding` |
| 208 | + |
| 209 | +We considered implementing returning the `target("spirv.Image", ...)` type from |
| 210 | +`@llvm.spv.handle.fromBinding` instead of returning a pointer to the type. This |
| 211 | +caused problems because the uses of the image have to be in the same basic |
| 212 | +block, and, in general, the uses of the handle are not in the same basic block |
| 213 | +as the call to `@llvm.spv.handle.fromBinding`. |
| 214 | + |
| 215 | +To fix this, we would have to add a pass in the backend to fix up the problem by |
| 216 | +replicating code, but this seems less desirable when we can generate the code |
| 217 | +correctly. |
| 218 | + |
| 219 | +It also makes the implementation of `@llvm.spv.handle.fromBinding` more |
| 220 | +complicated because it will have to be treated differently than structured |
| 221 | +buffers. |
| 222 | + |
| 223 | +## Acknowledgments (Optional) |
| 224 | + |
| 225 | +<!-- {% endraw %} --> |
0 commit comments