| # 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 |