================ @@ -0,0 +1,794 @@ +====================== +DXIL Resource Handling +====================== + +.. contents:: + :local: + +.. toctree:: + :hidden: + +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. + +Samplers +-------- + +.. code-block:: llvm + + target("dx.Sampler", SamplerType) + +The "dx.Sampler" type is used to represent sampler state. The sampler type is +an enum value from the DXIL ABI, and these appear in sampling operations as +well as LOD calculations and texture gather. + +Constant Buffers +---------------- + +.. code-block:: llvm + + target("dx.CBuffer", BufferSize) + +The "dx.CBuffer" type is a constant buffer of the given size. Note that despite +the name this is distinct from the buffer types, and can only be read using the +``llvm.dx.cbufferLoad`` operation. + +Buffers +------- + +.. code-block:: llvm + + target("dx.Buffer", ElementType, IsWriteable, IsROV) + +There is only one buffer type. This can represent both UAVs and SRVs via the +``IsWriteable`` field. Since the type that's encoded is an llvm type, it +handles both ``Buffer`` and ``StructuredBuffer`` uniformly. For ``RawBuffer``, +the type is ``i8``, which is unambiguous since ``char`` isn't a legal type in +HLSL. + +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. + +Textures +-------- + +.. code-block:: llvm + + target("dx.Texture1D", ElementType, IsWriteable, IsROV) + target("dx.Texture1DArray", ...) + target("dx.Texture2D", ...) + target("dx.Texture2DArray", ...) + target("dx.Texture3D", ...) + target("dx.TextureCUBE", ...) + target("dx.TextureCUBEArray", ...) + + target("dx.Texture2DMS", ElementType, IsWriteable, IsROV, SampleCount) + target("dx.Texture2DMSArray", ...) + + target("dx.FeedbackTexture2D", ElementType, IsWriteable, IsROV, FeedbackType) + target("dx.FeedbackTexture2DArray", ...) + +There are a number of texture types, but they are mostly interestingly +different in their dimensions. These are distinct so that we can overload the +various sample and texture load/store operations such that their parameters are +appropriate to the type. + +.. list-table:: Texture 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). + * - SampleCount + - Sample count for a multisampled texture. + * - FeedbackType + - Feedback type for a feedback texture. + +Raytracing Resources +-------------------- + +.. code-block:: llvm + + target("dx.RTAccelerationStructure") + +.. note:: TODO: Describe RTAccelerationStructure + +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.handle.fromBinding``, +``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. + +.. _dx.op.createHandle: https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#resource-handles + +.. list-table:: ``@llvm.dx.handle.fromBinding`` + :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. + * - ``%index`` + - 4 + - ``i32`` + - Index of the resource to access. + * - ``%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: + +.. code-block:: llvm + + ; RWBuffer<float4> Buf : register(u5, space3) + %buf = call target("dx.Buffer", <4 x float>, 1, 0) + @llvm.dx.handle.fromBinding.tdx.Buffer_v4f32_1_0( + i32 3, i32 5, i32 1, i32 0, i1 false) + + ; RWBuffer<uint> Buf : register(u7, space2) + %buf = call target("dx.Buffer", i32, 1, 0) + @llvm.dx.handle.fromBinding.tdx.Buffer_i32_1_0t( + i32 2, i32 7, i32 1, i32 0, i1 false) + + ; Buffer<uint4> Buf[24] : register(t3, space5) + %buf = call target("dx.Buffer", <4 x i32>, 0, 0) + @llvm.dx.handle.fromBinding.tdx.Buffer_v4i32_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.Buffer", {<4 x f32>, <4 x i32>}, 0, 0) + @llvm.dx.handle.fromBinding.tdx.Buffer_sl_v4f32v4i32s_0_0t( + i32 4, i32 2, i32 1, i32 0, i1 false) + + ; ByteAddressBuffer Buf : register(t8, space1) + %buf = call target("dx.Buffer", i8, 0, 0) + @llvm.dx.handle.fromBinding.tdx.Buffer_i8_0_0t( + i32 1, i32 8, i32 1, i32 0, i1 false) + + ; cbuffer cb0 { + ; float4 g_MaxThreadIter : packoffset(c0); + ; float4 g_Window : packoffset(c1); + ; } + %cb0 = call target("dx.CBuffer", 32) + @llvm.dx.handle.fromBinding.tdx.CBuffer_32t( + i32 0, i32 0, i32 1, i32 0, i1 false) + + ; Texture2D<float4> ColorMapTexture : register(t3); + %tex = call target("dx.Texture2D", <4 x f32>, 0, 0) + @llvm.dx.handle.fromBinding.tdx.Texture2D_v4f32_0_0t( + i32 0, i32 3, i32 1, i32 0, i1 false) + + ; Texture1D<float4> Buf[5] : register(t3); + ; Texture1D<float4> B = Buf[NonUniformResourceIndex(i)]; + %tex = call target("dx.Texture1D", <4 x f32>, 0, 0) + @llvm.dx.handle.fromBinding.tdx.Texture1D_v4f32_0_0t( + i32 0, i32 3, i32 5, i32 %i, i1 true) + + ; SamplerState ColorMapSampler : register(s0); + %smp = call target("dx.Sampler", 0) + @llvm.dx.handle.fromBinding.tdx.Sampler_0t( + i32 0, i32 0, i32 1, i32 0, 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: + +.. code-block:: llvm + + ; RWStructuredBuffer<float4> Buf = ResourceDescriptorHeap[2]; + declare + target("dx.Buffer", <4 x float>, 1, 0) + @llvm.dx.handle.fromHeap.tdx.Buffer_v4f32_1_0( + i32 %index, i1 %non_uniform) + ; ... + %buf = call target("dx.Buffer", <4 x f32>, 1, 0) + @llvm.dx.handle.fromHeap.tdx.Buffer_v4f32_1_0( + i32 2, i1 false) + + ; struct S { float f; }; + ; ConstantBuffer<S> CB = ResourceDescriptorHeap[0]; + %cb0 = call target("dx.CBuffer", 4) + @llvm.dx.handle.fromBinding.tdx.CBuffer_4t( + i32 0, i1 false) + + ; Texture2D<float4> ColorMapTexture : register(t3); + %tex = call target("dx.Texture2D", <4 x f32>, 0, 0) + @llvm.dx.handle.fromBinding.tdx.Texture2D_v4f32_0_0t( + i32 0, i1 false) + + ; Texture1D<float4> Buf[5] : register(t3); + ; Texture1D<float4> B = Buf[NonUniformResourceIndex(i)]; + %tex = call target("dx.Texture1D", <4 x f32>, 0, 0) + @llvm.dx.handle.fromHeap.tdx.Texture1D_v4f32_0_0t( + i32 %i, i1 true) + + ; SamplerState ColorMapSampler = ResourceDescriptorHeap[3]; + %smp = call target("dx.Sampler", 0) + @llvm.dx.handle.fromBinding.tdx.Sampler_0t( + i32 3, i1 false) + +Buffer Loads and Stores +----------------------- + +*relevant types: Buffers* + +We separate loading from buffers into two operations, ``llvm.dx.bufferLoad`` +and ``llvm.dx.bufferLoadComponent``. Store operations consist of their inverse, +``llvm.dx.bufferStore`` and ``llvm.dx.bufferStoreComponent``. These map to the +DXIL `rawBufferLoad`_ and `rawBufferStore`_ operations (and their older non-raw +counterparts). + +We opt for two different intrinsics to best support the two main ways of +accessing buffer data. + +The ``llvm.dx.bufferLoad`` intrinsic can return either a single element of a +buffer or a vector of consecutive elements. This makes accessing buffers of +scalars and simple vectors like `float4` simple, and is also convenient when +loading an entire `struct`. The variant with a vector of elements returned is +most useful for raw buffers, where we can load a number of bytes and `bitcast` +to the appropriate type, but can also be used to preserve the information that +we're loading data in bulk if needed. + +The ``llvm.dx.bufferLoadComponent`` intrinsic has an extra index so that it can +be used to access specific struct elements or a particular component of a +simple vector type, like ``x`` of a ``float4``. This API gives the same +flexibility as the DXIL ``rawBufferLoad`` operation, but in a slightly more +readable way since it avoids ``undef`` values and bit masks, as well as using +an index instead of a byte offset. + +The types involved in the store intrinsics match the load intrinsics. + +When lowering these to the DXIL operations we need to pay attention to the DXIL +version and the type of data in the buffer. Post DXIL 1.2 structured and byte ---------------- bogner wrote:
Current version differentiates between raw/structured and typed loads, so this should all be fairly straightforward. Whether we lower raw/structured loads in one or two steps when targetting dxil 1.2 is mostly an implementation detail - we can do whatever makes more sense. https://github.com/llvm/llvm-project/pull/90553 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits