| # DXIL Resource Handling |
| |
| ```{toctree} |
| :hidden: true |
| ``` |
| |
| ## Introduction |
| |
| Resources in DXIL are represented via `TargetExtType` in LLVM IR and |
| eventually lowered by the DirectX backend into metadata in DXIL. |
| |
| In DXC and DXIL, static resources are represented as lists of SRVs (Shader |
| Resource Views), UAVs (Uniform Access Views), CBVs (Constant Bffer Views), and |
| Samplers. This metadata consists of a "resource record ID" which uniquely |
| identifies a resource and type information. As of shader model 6.6, there are |
| also dynamic resources, which forgo the metadata and are described via |
| `annotateHandle` operations in the instruction stream instead. |
| |
| In LLVM we attempt to unify some of the alternative representations that are |
| present in DXC, with the aim of making handling of resources in the middle end |
| of the compiler simpler and more consistent. |
| |
| ## Resource Type Information and Properties |
| |
| There are a number of properties associated with a resource in DXIL. |
| |
| `Resource ID` |
| |
| : An arbitrary ID that must be unique per resource type (SRV, UAV, etc). |
| |
| In LLVM we don't bother representing this, instead opting to generate it at |
| DXIL lowering time. |
| |
| `Binding information` |
| |
| : Information about where the resource comes from. This is either (a) a |
| register space, lower bound in that space, and size of the binding, or (b) |
| an index into a dynamic resource heap. |
| |
| In LLVM we represent binding information in the arguments of the |
| {ref}`handle creation intrinsics <dxil-resources-handles>`. When generating |
| DXIL we transform these calls to metadata, `dx.op.createHandle`, |
| `dx.op.createHandleFromBinding`, `dx.op.createHandleFromHeap`, and |
| `dx.op.createHandleForLib` as needed. |
| |
| `Type information` |
| |
| : The type of data that's accessible via the resource. For buffers and |
| textures this can be a simple type like `float` or `float4`, a struct, |
| or raw bytes. For constant buffers this is just a size. For samplers this is |
| the kind of sampler. |
| |
| In LLVM we embed this information as a parameter on the `target()` type of |
| the resource. See {ref}`dxil-resources-types-of-resource`. |
| |
| `Resource kind information` |
| |
| : The kind of resource. In HLSL we have things like `ByteAddressBuffer`, |
| `RWTexture2D`, and `RasterizerOrderedStructuredBuffer`. These map to a |
| set of DXIL kinds like `RawBuffer` and `Texture2D` with fields for |
| certain properties such as `IsUAV` and `IsROV`. |
| |
| In LLVM we represent this in the `target()` type. We omit information |
| that's deriveable from the type information, but we do have fields to encode |
| `IsWriteable`, `IsROV`, and `SampleCount` when needed. |
| |
| :::{note} |
| TODO: There are two fields in the DXIL metadata that are not |
| represented as part of the target type: `IsGloballyCoherent` and |
| `HasCounter`. |
| |
| Since these are derived from analysis, storing them on the type would mean |
| we need to change the type during the compiler pipeline. That just isn't |
| practical. It isn't entirely clear to me that we need to serialize this info |
| into the IR during the compiler pipeline anyway - we can probably get away |
| with an analysis pass that can calculate the information when we need it. |
| |
| If analysis is insufficient we'll need something akin to `annotateHandle` |
| (but limited to these two properties) or to encode these in the handle |
| creation. |
| ::: |
| |
| (dxil-resources-types-of-resource)= |
| |
| ## Types of Resource |
| |
| We define a set of `TargetExtTypes` that is similar to the HLSL |
| representations for the various resources, albeit with a few things |
| parameterized. This is different than DXIL, as simplifying the types to |
| something like "dx.srv" and "dx.uav" types would mean the operations on these |
| types would have to be overly generic. |
| |
| ### Buffers |
| |
| ```llvm |
| target("dx.TypedBuffer", ElementType, IsWriteable, IsROV, IsSigned) |
| target("dx.RawBuffer", ElementType, IsWriteable, IsROV) |
| ``` |
| |
| We need two separate buffer types to account for the differences between the |
| 16-byte [bufferLoad] / [bufferStore] operations that work on DXIL's |
| TypedBuffers and the [rawBufferLoad] / [rawBufferStore] operations that are |
| used for DXIL's RawBuffers and StructuredBuffers. We call the latter |
| "RawBuffer" to match the naming of the operations, but it can represent both |
| the Raw and Structured variants. |
| |
| HLSL's Buffer and RWBuffer are represented as a TypedBuffer with an element |
| type that is a scalar integer or floating point type, or a vector of at most 4 |
| such types. HLSL's ByteAddressBuffer is a RawBuffer with an `i8` element type. |
| HLSL's StructuredBuffers are RawBuffer with a struct, vector, or scalar type. |
| |
| One unfortunate necessity here is that TypedBuffer needs an extra parameter to |
| differentiate signed vs unsigned ints. The is because in LLVM IR int types |
| don't have a sign, so to keep this information we need a side channel. |
| |
| These types are generally used by BufferLoad and BufferStore operations, as |
| well as atomics. |
| |
| There are a few fields to describe variants of all of these types: |
| |
| :::{list-table} Buffer Fields |
| :header-rows: 1 |
| |
| * - Field |
| - Description |
| * - ElementType |
| - Type for a single element, such as `i8`, `v4f32`, or a structure type. |
| * - IsWriteable |
| - Whether or not the field is writeable. This distinguishes SRVs (not |
| writeable) and UAVs (writeable). |
| * - IsROV |
| - Whether the UAV is a rasterizer ordered view. Always `0` for SRVs. |
| * - IsSigned |
| - Whether an int element type is signed ("dx.TypedBuffer" only) |
| ::: |
| |
| [bufferLoad]: https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#bufferload |
| [bufferStore]: https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#bufferstore |
| [rawBufferLoad]: https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#rawbufferload |
| [rawBufferStore]: https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#rawbufferstore |
| |
| ## Resource Operations |
| |
| (dxil-resources-handles)= |
| |
| ### Resource Handles |
| |
| We provide a few different ways to instantiate resources in the IR via the |
| `llvm.dx.handle.*` intrinsics. These intrinsics are overloaded on return |
| type, returning an appropriate handle for the resource, and represent binding |
| information in the arguments to the intrinsic. |
| |
| The three operations we need are `llvm.dx.resource.handlefrombinding`, |
| `llvm.dx.handle.fromHeap`, and `llvm.dx.handle.fromPointer`. These are |
| rougly equivalent to the DXIL operations `dx.op.createHandleFromBinding`, |
| `dx.op.createHandleFromHeap`, and `dx.op.createHandleForLib`, but they fold |
| the subsequent `dx.op.annotateHandle` operation in. Note that we don't have |
| an analogue for [dx.op.createHandle], since `dx.op.createHandleFromBinding` |
| subsumes it. |
| |
| We diverge from DXIL and index from the beginning of the binding rather than |
| indexing from the beginning of the binding space. This matches the semantics |
| more clearly and avoids a non-obvious invariant in what constitutes valid |
| arguments. |
| |
| [dx.op.createHandle]: https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#resource-handles |
| |
| :::{list-table} `@llvm.dx.resource.handlefrombinding` |
| :header-rows: 1 |
| |
| * - Argument |
| - |
| - Type |
| - Description |
| * - Return value |
| - |
| - A `target()` type |
| - A handle which can be operated on |
| * - `%reg_space` |
| - 1 |
| - `i32` |
| - Register space ID in the root signature for this resource. |
| * - `%lower_bound` |
| - 2 |
| - `i32` |
| - Lower bound of the binding in its register space. |
| * - `%range_size` |
| - 3 |
| - `i32` |
| - Range size of the binding, where `0` denotes an unbounded range. |
| * - `%index` |
| - 4 |
| - `i32` |
| - Index from the beginning of the binding. |
| * - `%non-uniform` |
| - 5 |
| - i1 |
| - Must be `true` if the resource index may be non-uniform. |
| ::: |
| |
| :::{note} |
| TODO: Can we drop the uniformity bit? I suspect we can derive it from |
| uniformity analysis... |
| ::: |
| |
| Examples: |
| |
| ```llvm |
| ; RWBuffer<float4> Buf : register(u5, space3) |
| %buf = call target("dx.TypedBuffer", <4 x float>, 1, 0, 0) |
| @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_f32_1_0( |
| i32 3, i32 5, i32 1, i32 0, i1 false) |
| |
| ; RWBuffer<int> Buf : register(u7, space2) |
| %buf = call target("dx.TypedBuffer", i32, 1, 0, 1) |
| @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_i32_1_0t( |
| i32 2, i32 7, i32 1, i32 0, i1 false) |
| |
| ; Buffer<uint4> Buf[24] : register(t3, space5) |
| %buf = call target("dx.TypedBuffer", <4 x i32>, 0, 0, 0) |
| @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_i32_0_0t( |
| i32 2, i32 7, i32 24, i32 0, i1 false) |
| |
| ; struct S { float4 a; uint4 b; }; |
| ; StructuredBuffer<S> Buf : register(t2, space4) |
| %buf = call target("dx.RawBuffer", {<4 x float>, <4 x i32>}, 0, 0) |
| @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_sl_v4f32v4i32s_0_0t( |
| i32 4, i32 2, i32 1, i32 0, i1 false) |
| |
| ; ByteAddressBuffer Buf : register(t8, space1) |
| %buf = call target("dx.RawBuffer", i8, 0, 0) |
| @llvm.dx.resource.handlefrombinding.tdx.RawBuffer_i8_0_0t( |
| i32 1, i32 8, i32 1, i32 0, i1 false) |
| |
| ; RWBuffer<float4> Global[3] : register(u6, space5) |
| ; RWBuffer<float4> Buf = Global[2]; |
| %buf = call target("dx.TypedBuffer", <4 x float>, 1, 0, 0) |
| @llvm.dx.resource.handlefrombinding.tdx.TypedBuffer_f32_1_0( |
| i32 5, i32 6, i32 3, i32 2, i1 false) |
| ``` |
| |
| :::{list-table} `@llvm.dx.handle.fromHeap` |
| :header-rows: 1 |
| |
| * - Argument |
| - |
| - Type |
| - Description |
| * - Return value |
| - |
| - A `target()` type |
| - A handle which can be operated on |
| * - `%index` |
| - 0 |
| - `i32` |
| - Index of the resource to access. |
| * - `%non-uniform` |
| - 1 |
| - i1 |
| - Must be `true` if the resource index may be non-uniform. |
| ::: |
| |
| Examples: |
| |
| ```llvm |
| ; RWStructuredBuffer<float4> Buf = ResourceDescriptorHeap[2]; |
| declare |
| target("dx.RawBuffer", <4 x float>, 1, 0) |
| @llvm.dx.handle.fromHeap.tdx.RawBuffer_v4f32_1_0( |
| i32 %index, i1 %non_uniform) |
| ; ... |
| %buf = call target("dx.RawBuffer", <4 x f32>, 1, 0) |
| @llvm.dx.handle.fromHeap.tdx.RawBuffer_v4f32_1_0( |
| i32 2, i1 false) |
| ``` |
| |
| ### Accessing Resources as Memory |
| |
| *relevant types: Buffers, Textures, and CBuffers* |
| |
| Loading and storing from resources is generally represented in LLVM using |
| operations on memory that is only accessible via a handle object. Given a |
| handle, `llvm.dx.resource.getpointer` gives a pointer that can be used to read |
| and (depending on type) write to the resource. |
| |
| Accesses using `llvm.dx.resource.getpointer` are replaced with direct load and |
| store operations in the `DXILResourceAccess` pass. These direct loads and |
| stores are described later in this document. |
| |
| :::{note} |
| Currently the pointers returned by `dx.resource.getpointer` are in |
| the default address space, but that will likely change in the future. |
| ::: |
| |
| :::{list-table} `@llvm.dx.resource.getpointer` |
| :header-rows: 1 |
| |
| * - Argument |
| - |
| - Type |
| - Description |
| * - Return value |
| - |
| - Pointer |
| - A pointer to an object in the buffer |
| * - `%resource` |
| - 0 |
| - Any buffer, texture, or cbuffer type |
| - The resource to access |
| * - `%index` |
| - 1 |
| - `i32` |
| - Index into the resource |
| ::: |
| |
| Examples: |
| |
| ```llvm |
| %ptr = call ptr @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_v4f32_0_0_0t( |
| target("dx.TypedBuffer", <4 x float>, 0, 0, 0) %buffer, i32 %index) |
| ``` |
| |
| ### Loads, Samples, and Gathers |
| |
| *relevant types: Buffers and Textures* |
| |
| All load, sample, and gather operations in DXIL return a [ResRet] type. These |
| types are structs containing 4 elements of some basic type, and a 5th element |
| that is used by the [CheckAccessFullyMapped] operation. Some of these |
| operations, like [RawBufferLoad] include a mask and/or alignment that tell us |
| some information about how to interpret those four values. |
| |
| In the LLVM IR representations of these operations we instead return scalars or |
| vectors, but we keep the requirement that we only return up to 4 elements of a |
| basic type. This avoids some unnecessary casting and structure manipulation in |
| the intermediate format while also keeping lowering to DXIL straightforward. |
| |
| LLVM intrinsics that map to operations returning `ResRet` return an anonymous |
| struct with element-0 being the scalar or vector type, and element-1 being the |
| `i1` result of a `CheckAccessFullyMapped` call. We don't have a separate |
| call to `CheckAccessFullyMapped` at all, since that's the only operation that |
| can possibly be done on this value. In practice this may mean we insert a DXIL |
| operation for the check when this was missing in the HLSL source, but this |
| actually matches DXC's behaviour in practice. |
| |
| For TypedBuffer and Texture, we map directly from the contained type of the |
| resource to the return value of the intrinsic. Since these resources are |
| constrained to contain only scalars and vectors of up to 4 elements, the |
| lowering to DXIL ops is generally straightforward. The one exception we have |
| here is that `double` types in the elements are special - these are allowed in |
| the LLVM intrinsics, but are lowered to pairs of `i32` followed by |
| `MakeDouble` operations for DXIL. |
| |
| [ResRet]: https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#resource-operation-return-types |
| [CheckAccessFullyMapped]: https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/checkaccessfullymapped |
| |
| :::{list-table} `@llvm.dx.resource.load.typedbuffer` |
| :header-rows: 1 |
| |
| * - Argument |
| - |
| - Type |
| - Description |
| * - Return value |
| - |
| - A structure of the contained type and the check bit |
| - The data loaded from the buffer and the check bit |
| * - `%buffer` |
| - 0 |
| - `target(dx.TypedBuffer, ...)` |
| - The buffer to load from |
| * - `%index` |
| - 1 |
| - `i32` |
| - Index into the buffer |
| ::: |
| |
| Examples: |
| |
| ```llvm |
| %ret = call {<4 x float>, i1} |
| @llvm.dx.resource.load.typedbuffer.v4f32.tdx.TypedBuffer_v4f32_0_0_0t( |
| target("dx.TypedBuffer", <4 x float>, 0, 0, 0) %buffer, i32 %index) |
| %ret = call {float, i1} |
| @llvm.dx.resource.load.typedbuffer.f32.tdx.TypedBuffer_f32_0_0_0t( |
| target("dx.TypedBuffer", float, 0, 0, 0) %buffer, i32 %index) |
| %ret = call {<4 x i32>, i1} |
| @llvm.dx.resource.load.typedbuffer.v4i32.tdx.TypedBuffer_v4i32_0_0_0t( |
| target("dx.TypedBuffer", <4 x i32>, 0, 0, 0) %buffer, i32 %index) |
| %ret = call {<4 x half>, i1} |
| @llvm.dx.resource.load.typedbuffer.v4f16.tdx.TypedBuffer_v4f16_0_0_0t( |
| target("dx.TypedBuffer", <4 x half>, 0, 0, 0) %buffer, i32 %index) |
| %ret = call {<2 x double>, i1} |
| @llvm.dx.resource.load.typedbuffer.v2f64.tdx.TypedBuffer_v2f64_0_0t( |
| target("dx.TypedBuffer", <2 x double>, 0, 0, 0) %buffer, i32 %index) |
| ``` |
| |
| For RawBuffer, an HLSL load operation may return an arbitrarily sized result, |
| but we still constrain the LLVM intrinsic to return only up to 4 elements of a |
| basic type. This means that larger loads are represented as a series of loads, |
| which matches DXIL. Unlike in the [RawBufferLoad] operation, we do not need |
| arguments for the mask/type size and alignment, since we can calculate these |
| from the return type of the load during lowering. |
| |
| Note that RawBuffer loads represent either "structured" accesses, as in HLSL's |
| StructuredBuffer\<T>, or a "raw" access, as in HLSL's "ByteAddressBuffer". The |
| `%offset` parameter is only used for structured accesses, and *must* be |
| `poison` for raw accesses. |
| |
| :::{list-table} `@llvm.dx.resource.load.rawbuffer` |
| :header-rows: 1 |
| |
| * - Argument |
| - |
| - Type |
| - Description |
| * - Return value |
| - |
| - A structure of a scalar or vector and the check bit |
| - The data loaded from the buffer and the check bit |
| * - `%buffer` |
| - 0 |
| - `target(dx.RawBuffer, ...)` |
| - The buffer to load from |
| * - `%index` |
| - 1 |
| - `i32` |
| - Index into the buffer |
| * - `%offset` |
| - 2 |
| - `i32` |
| - Offset into the structure at the given index |
| ::: |
| |
| Examples: |
| |
| ```llvm |
| ; float |
| %ret = call {float, i1} |
| @llvm.dx.resource.load.rawbuffer.f32.tdx.RawBuffer_f32_0_0_0t( |
| target("dx.RawBuffer", float, 0, 0, 0) %buffer, |
| i32 %index, |
| i32 0) |
| %ret = call {float, i1} |
| @llvm.dx.resource.load.rawbuffer.f32.tdx.RawBuffer_i8_0_0_0t( |
| target("dx.RawBuffer", i8, 0, 0, 0) %buffer, |
| i32 %byte_offset, |
| i32 poison) |
| |
| ; float4 |
| %ret = call {<4 x float>, i1} |
| @llvm.dx.resource.load.rawbuffer.v4f32.tdx.RawBuffer_v4f32_0_0_0t( |
| target("dx.RawBuffer", float, 0, 0, 0) %buffer, |
| i32 %index, |
| i32 0) |
| %ret = call {float, i1} |
| @llvm.dx.resource.load.rawbuffer.v4f32.tdx.RawBuffer_i8_0_0_0t( |
| target("dx.RawBuffer", i8, 0, 0, 0) %buffer, |
| i32 %byte_offset, |
| i32 poison) |
| |
| ; struct S0 { float4 f; int4 i; }; |
| %ret = call {<4 x float>, i1} |
| @llvm.dx.resource.load.rawbuffer.v4f32.tdx.RawBuffer_sl_v4f32v4i32s_0_0t( |
| target("dx.RawBuffer", {<4 x float>, <4 x i32>}, 0, 0, 0) %buffer, |
| i32 %index, |
| i32 0) |
| %ret = call {<4 x i32>, i1} |
| @llvm.dx.resource.load.rawbuffer.v4i32.tdx.RawBuffer_sl_v4f32v4i32s_0_0t( |
| target("dx.RawBuffer", {<4 x float>, <4 x i32>}, 0, 0, 0) %buffer, |
| i32 %index, |
| i32 1) |
| |
| ; struct Q { float4 f; int3 i; } |
| ; struct R { int z; S x; } |
| %ret = call {i32, i1} |
| @llvm.dx.resource.load.rawbuffer.i32( |
| target("dx.RawBuffer", {i32, {<4 x float>, <3 x i32>}}, 0, 0, 0) |
| %buffer, i32 %index, i32 0) |
| %ret = call {<4 x float>, i1} |
| @llvm.dx.resource.load.rawbuffer.i32( |
| target("dx.RawBuffer", {i32, {<4 x float>, <3 x i32>}}, 0, 0, 0) |
| %buffer, i32 %index, i32 4) |
| %ret = call {<3 x i32>, i1} |
| @llvm.dx.resource.load.rawbuffer.i32( |
| target("dx.RawBuffer", {i32, {<4 x float>, <3 x i32>}}, 0, 0, 0) |
| %buffer, i32 %index, i32 20) |
| |
| ; byteaddressbuf.Load<int64_t4> |
| %ret = call {<4 x i64>, i1} |
| @llvm.dx.resource.load.rawbuffer.v4i64.tdx.RawBuffer_i8_0_0t( |
| target("dx.RawBuffer", i8, 0, 0, 0) %buffer, |
| i32 %byte_offset, |
| i32 poison) |
| ``` |
| |
| ### Stores |
| |
| *relevant types: Textures and Buffer* |
| |
| The [TextureStore], [BufferStore], and [RawBufferStore] DXIL operations |
| write four components to a texture or a buffer. These include a mask argument |
| that is used when fewer than 4 components are written, but notably this only |
| takes on the contiguous x, xy, xyz, and xyzw values. |
| |
| We define the LLVM store intrinsics to accept vectors when storing multiple |
| components rather than using `undef` and a mask, but otherwise match the DXIL |
| ops fairly closely. |
| |
| [TextureStore]: https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#texturestore |
| |
| For TypedBuffer, we only need one coordinate, and we must always write a vector |
| since partial writes aren't possible. Similarly to the load operations |
| described above, we handle 64-bit types specially and only handle 2-element |
| vectors rather than 4. |
| |
| Examples: |
| |
| :::{list-table} `@llvm.dx.resource.store.typedbuffer` |
| :header-rows: 1 |
| |
| * - Argument |
| - |
| - Type |
| - Description |
| * - Return value |
| - |
| - `void` |
| - |
| * - `%buffer` |
| - 0 |
| - `target(dx.TypedBuffer, ...)` |
| - The buffer to store into |
| * - `%index` |
| - 1 |
| - `i32` |
| - Index into the buffer |
| * - `%data` |
| - 2 |
| - A 4- or 2-element vector of the type of the buffer |
| - The data to store |
| ::: |
| |
| Examples: |
| |
| ```llvm |
| call void @llvm.dx.resource.store.typedbuffer.tdx.Buffer_v4f32_1_0_0t( |
| target("dx.TypedBuffer", f32, 1, 0) %buf, i32 %index, <4 x f32> %data) |
| call void @llvm.dx.resource.store.typedbuffer.tdx.Buffer_v4f16_1_0_0t( |
| target("dx.TypedBuffer", f16, 1, 0) %buf, i32 %index, <4 x f16> %data) |
| call void @llvm.dx.resource.store.typedbuffer.tdx.Buffer_v2f64_1_0_0t( |
| target("dx.TypedBuffer", f64, 1, 0) %buf, i32 %index, <2 x f64> %data) |
| ``` |
| |
| For Textures, the coordinates are a scalar for 1D textures and a vector of 2 or |
| 3 elements for the higher dimensional and array textures. Like TypedBuffer, a |
| store writes a whole texel, so partial writes have to be expressed as a |
| read-modify-write of the full value. |
| |
| Examples: |
| |
| :::{list-table} `@llvm.dx.resource.store.texture` |
| :header-rows: 1 |
| |
| * - Argument |
| - |
| - Type |
| - Description |
| * - Return value |
| - |
| - `void` |
| - |
| * - `%texture` |
| - 0 |
| - `target(dx.Texture, ...)` |
| - The texture to store into |
| * - `%coords` |
| - 1 |
| - `i32` or a 2- or 3-element vector of `i32` |
| - Coordinates into the texture |
| * - `%data` |
| - 2 |
| - Scalar or vector of the type of the texture |
| - The data to store |
| ::: |
| |
| Examples: |
| |
| ```llvm |
| call void @llvm.dx.resource.store.texture.tdx.Texture_v4f32_1_0_0_1t.i32.v4f32( |
| target("dx.Texture", <4 x float>, 1, 0, 0, 1) %tex, |
| i32 %coord, <4 x float> %data) |
| call void @llvm.dx.resource.store.texture.tdx.Texture_v4f32_1_0_0_2t.v2i32.v4f32( |
| target("dx.Texture", <4 x float>, 1, 0, 0, 2) %tex, |
| <2 x i32> %coords, <4 x float> %data) |
| call void @llvm.dx.resource.store.texture.tdx.Texture_v4f32_1_0_0_4t.v3i32.v4f32( |
| target("dx.Texture", <4 x float>, 1, 0, 0, 4) %tex, |
| <3 x i32> %coords, <4 x float> %data) |
| ``` |
| |
| For RawBuffer, we need two indices and we accept scalars and vectors of 4 or |
| fewer elements. Note that we do allow vectors of 4 64-bit elements here. |
| |
| Examples: |
| |
| :::{list-table} `@llvm.dx.resource.store.rawbuffer` |
| :header-rows: 1 |
| |
| * - Argument |
| - |
| - Type |
| - Description |
| * - Return value |
| - |
| - `void` |
| - |
| * - `%buffer` |
| - 0 |
| - `target(dx.RawBuffer, ...)` |
| - The buffer to store into |
| * - `%index` |
| - 1 |
| - `i32` |
| - Index into the buffer |
| * - `%offset` |
| - 2 |
| - `i32` |
| - Byte offset into structured buffer elements |
| * - `%data` |
| - 3 |
| - Scalar or vector |
| - The data to store |
| ::: |
| |
| Examples: |
| |
| ```llvm |
| ; float |
| call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_f32_1_0_0t.f32( |
| target("dx.RawBuffer", float, 1, 0, 0) %buffer, |
| i32 %index, i32 0, float %data) |
| call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_i8_1_0_0t.f32( |
| target("dx.RawBuffer", i8, 1, 0, 0) %buffer, |
| i32 %index, i32 0, float %data) |
| |
| ; float4 |
| call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_v4f32_1_0_0t.v4f32( |
| target("dx.RawBuffer", <4 x float>, 1, 0, 0) %buffer, |
| i32 %index, i32 0, <4 x float> %data) |
| call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_i8_1_0_0t.v4f32( |
| target("dx.RawBuffer", i8, 1, 0, 0) %buffer, |
| i32 %index, i32 0, <4 x float> %data) |
| |
| ; struct S0 { float4 f; int4 i; } |
| call void @llvm.dx.resource.store.rawbuffer.v4f32( |
| target("dx.RawBuffer", { <4 x float>, <4 x i32> }, 1, 0, 0) %buffer, |
| i32 %index, i32 0, <4 x float> %data0) |
| call void @llvm.dx.resource.store.rawbuffer.v4i32( |
| target("dx.RawBuffer", { <4 x float>, <4 x i32> }, 1, 0, 0) %buffer, |
| i32 %index, i32 16, <4 x i32> %data1) |
| |
| ; struct Q { float4 f; int3 i; } |
| ; struct R { int z; S x; } |
| call void @llvm.dx.resource.store.rawbuffer.i32( |
| target("dx.RawBuffer", {i32, {<4 x float>, <3 x half>}}, 1, 0, 0) |
| %buffer, |
| i32 %index, i32 0, i32 %data0) |
| call void @llvm.dx.resource.store.rawbuffer.v4f32( |
| target("dx.RawBuffer", {i32, {<4 x float>, <3 x half>}}, 1, 0, 0) |
| %buffer, |
| i32 %index, i32 4, <4 x float> %data1) |
| call void @llvm.dx.resource.store.rawbuffer.v3f16( |
| target("dx.RawBuffer", {i32, {<4 x float>, <3 x half>}}, 1, 0, 0) |
| %buffer, |
| i32 %index, i32 20, <3 x half> %data2) |
| |
| ; byteaddressbuf.Store<int64_t4> |
| call void @llvm.dx.resource.store.rawbuffer.tdx.RawBuffer_i8_1_0_0t.v4f64( |
| target("dx.RawBuffer", i8, 1, 0, 0) %buffer, |
| i32 %index, i32 0, <4 x double> %data) |
| ``` |
| |
| ### Constant Buffer Loads |
| |
| *relevant types: CBuffers* |
| |
| The [CBufferLoadLegacy] operation, which despite the name is the only |
| supported way to load from a cbuffer in any DXIL version, loads a single "row" |
| of a cbuffer, which is exactly 16 bytes. The return value of the operation is |
| represented by a [CBufRet] type, which has variants for 2 64-bit values, 4 |
| 32-bit values, and 8 16-bit values. |
| |
| We represent these in LLVM IR with 3 separate operations, which return a |
| 2-element, 4-element, or 8-element struct respectively. |
| |
| [CBufferLoadLegacy]: https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#cbufferLoadLegacy |
| [CBufRet]: https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#cbufferloadlegacy |
| |
| :::{list-table} `@llvm.dx.resource.load.cbufferrow.4` |
| :header-rows: 1 |
| |
| * - Argument |
| - |
| - Type |
| - Description |
| * - Return value |
| - |
| - A struct of 4 32-bit values |
| - A single row of a cbuffer, interpreted as 4 32-bit values |
| * - `%buffer` |
| - 0 |
| - `target(dx.CBuffer, ...)` |
| - The buffer to load from |
| * - `%index` |
| - 1 |
| - `i32` |
| - Index into the buffer |
| ::: |
| |
| Examples: |
| |
| ```llvm |
| %ret = call {float, float, float, float} |
| @llvm.dx.resource.load.cbufferrow.4( |
| target("dx.CBuffer", target("dx.Layout", {float}, 4, 0)) %buffer, |
| i32 %index) |
| %ret = call {i32, i32, i32, i32} |
| @llvm.dx.resource.load.cbufferrow.4( |
| target("dx.CBuffer", target("dx.Layout", {i32}, 4, 0)) %buffer, |
| i32 %index) |
| ``` |
| |
| :::{list-table} `@llvm.dx.resource.load.cbufferrow.2` |
| :header-rows: 1 |
| |
| * - Argument |
| - |
| - Type |
| - Description |
| * - Return value |
| - |
| - A struct of 2 64-bit values |
| - A single row of a cbuffer, interpreted as 2 64-bit values |
| * - `%buffer` |
| - 0 |
| - `target(dx.CBuffer, ...)` |
| - The buffer to load from |
| * - `%index` |
| - 1 |
| - `i32` |
| - Index into the buffer |
| ::: |
| |
| Examples: |
| |
| ```llvm |
| %ret = call {double, double} |
| @llvm.dx.resource.load.cbufferrow.2( |
| target("dx.CBuffer", target("dx.Layout", {double}, 8, 0)) %buffer, |
| i32 %index) |
| %ret = call {i64, i64} |
| @llvm.dx.resource.load.cbufferrow.2( |
| target("dx.CBuffer", target("dx.Layout", {i64}, 4, 0)) %buffer, |
| i32 %index) |
| ``` |
| |
| :::{list-table} `@llvm.dx.resource.load.cbufferrow.8` |
| :header-rows: 1 |
| |
| * - Argument |
| - |
| - Type |
| - Description |
| * - Return value |
| - |
| - A struct of 8 16-bit values |
| - A single row of a cbuffer, interpreted as 8 16-bit values |
| * - `%buffer` |
| - 0 |
| - `target(dx.CBuffer, ...)` |
| - The buffer to load from |
| * - `%index` |
| - 1 |
| - `i32` |
| - Index into the buffer |
| ::: |
| |
| Examples: |
| |
| ```llvm |
| %ret = call {half, half, half, half, half, half, half, half} |
| @llvm.dx.resource.load.cbufferrow.8( |
| target("dx.CBuffer", target("dx.Layout", {half}, 2, 0)) %buffer, |
| i32 %index) |
| %ret = call {i16, i16, i16, i16, i16, i16, i16, i16} |
| @llvm.dx.resource.load.cbufferrow.8( |
| target("dx.CBuffer", target("dx.Layout", {i16}, 2, 0)) %buffer, |
| i32 %index) |
| ``` |
| |
| ### Resource dimensions |
| |
| *relevant types: Textures and Buffer* |
| |
| The [GetDimensions] DXIL operation returns the dimensions of a texture or |
| buffer resource. It returns a [Dimensions] type, which is a struct |
| containing four `i32` values. The values in the struct represent the size |
| of each dimension of the resource, and when aplicable the number of array |
| elements or number of samples. The mapping is defined in the |
| [GetDimensions] documentation. |
| |
| The LLVM IR representation of this operation has several forms |
| depending on the resource type and the specific `getDimensions` query. |
| The intrinsics return a scalar or anonymous struct with up to 4 `i32` |
| elements. The intrinsic names include suffixes to indicate the number of |
| elements in the return value. The suffix `.x` indicates a single `i32` |
| return value, `.xy` indicates a struct with two `i32` values, and `.xyz` |
| indicates a struct with three `i32` values. |
| |
| Intrinsics representing queries on multisampled texture resources include |
| `.ms.` in their name and their return value includes an additional `i32` for |
| the number of samples. |
| |
| Intrinsics with `mip_level` argument and `.levels.` in their name are used |
| for texture resources with multiple MIP levels. Their return |
| struct includes an additional `i32` for the number of levels the resource has. |
| |
| ```llvm |
| i32 @llvm.dx.resource.getdimensions.x( target("dx.*") handle ) |
| {i32, i32} @llvm.dx.resource.getdimensions.xy( target("dx.*") handle ) |
| {i32, i32, i32} @llvm.dx.resource.getdimensions.xyz( target("dx.*") handle ) |
| {i32, i32} @llvm.dx.resource.getdimensions.levels.x( target("dx.*") handle, i32 mip_level ) |
| {i32, i32, i32} @llvm.dx.resource.getdimensions.levels.xy( target("dx.*") handle, i32 mip_level ) |
| {i32, i32, i32, i32} @llvm.dx.resource.getdimensions.levels.xyz( target("dx.*") handle, i32 mip_level ) |
| {i32, i32, i32} @llvm.dx.resource.getdimensions.ms.xy( target("dx.*") handle ) |
| {i32, i32, i32, i32} @llvm.dx.resource.getdimensions.ms.xyz( target("dx.*") handle ) |
| ``` |
| |
| :::{list-table} `@llvm.dx.resource.getdimensions.*` |
| :header-rows: 1 |
| |
| * - Argument |
| - |
| - Type |
| - Description |
| * - Return value |
| - |
| - `i32`, `{i32, i32}`, `{i32, i32, i32}`, or `{i32, i32, i32, i32}` |
| - Width, height, and depth of the resource (based on the specific suffix), and a number of levels or samples where aplicable. |
| * - `%handle` |
| - 0 |
| - `target(dx.*)` |
| - Resource handle |
| * - `%mip_level` |
| - 1 |
| - `i32` |
| - MIP level for the requested dimensions. |
| ::: |
| |
| Examples: |
| |
| ```llvm |
| ; RWBuffer<float4> |
| %dim = call i32 @llvm.dx.resource.getdimensions.x(target("dx.TypedBuffer", <4 x float>, 1, 0, 0) %handle) |
| |
| ; Texture2D |
| %0 = call {i32, i32} @llvm.dx.resource.getdimensions.xy(target("dx.Texture", ...) %tex2d) |
| %tex2d_width = extractvalue {i32, i32} %0, 0 |
| %tex2d_height = extractvalue {i32, i32} %0, 1 |
| |
| ; Texture2DArray with levels |
| %1 = call {i32, i32, i32, i32} @llvm.dx.resource.getdimensions.levels.xyz( |
| target("dx.Texture", ...) %tex2darray, i32 1) |
| %tex2darray_width = extractvalue {i32, i32, i32, i32} %1, 0 |
| %tex2darray_height = extractvalue {i32, i32, i32, i32} %1, 1 |
| %tex2darray_elem_count = extractvalue {i32, i32, i32, i32} %1, 2 |
| %tex2darray_levels_count = extractvalue {i32, i32, i32, i32} %1, 3 |
| |
| ; Texture2DMS |
| %2 = call {i32, i32, i32} @llvm.dx.resource.getdimensions.ms.xy( |
| target("dx.Texture", ...) %tex2dms) |
| %tex2dms_width = extractvalue {i32, i32, i32} %2, 0 |
| %tex2dms_height = extractvalue {i32, i32, i32} %2, 1 |
| %tex2dms_samples_count = extractvalue {i32, i32, i32} %2, 2 |
| ``` |
| |
| [Dimensions]: https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#resource-operation-return-types |
| [GetDimensions]: https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#getdimensions |