HLSL Working Group

[0015] - Mapping Resource Attributes to DXIL and SPIR-V

StatusAccepted
Author

Introduction

There are a number of objects in HLSL that map to shader model resources. We need to represent these in various ways throughout lowering from the HLSL language to DXIL and SPIR-V program representations suitable for consumption by GPU drivers.

This document tries to summarize the information that’s needed in the various parts of the compiler, survey DXC’s implementation, and ask questions and make recommendations about how we want to handle all of this in clang.

Motivation

There are a number of places where we need to represent resource objects throughout the compiler. From an HLSL language perspective we need to carry sufficient information in the AST to track these types. At the LLVM IR level we need target specific types. Finally, the resulting DXIL and SPIR-V have their own representations.

Proposed solution

We’ll discuss the set of HLSL objects that we need to lower to various types of resources, how they map to DXIL constructs, how DXC represents these in SPIR-V, and finally a set of attributes that’s sufficient to generate the code we need in both DXIL and SPIR-V.

HLSL Objects

The resource objects in HLSL can be divided in a few different ways, but generally the interesting attributes are the layout and dimensionality and whether the objects are writeable.

HLSL Object
Texture1DRead-only 1D texture
Texture1DArrayRead-only array of 1D textures
Texture2DRead-only 2D texture
Texture2DArrayRead-only array of 2D textures
Texture2DMSRead-only 2D texture with multisampling
Texture2DMSArrayRead-only array of 2D textures with multisampling
Texture3DRead-only 3D texture
TextureCUBERead-only cubemapped texture
TextureCUBEArrayRead-only array of cubemapped textures
RWTexture1DRead/Write 1D texture
RWTexture1DArrayRead/Write array of 1D textures
RWTexture2DRead/Write 2D texture
RWTexture2DArrayRead/Write array of 2D textures
RWTexture2DMSRead/Write 2D texture with multisampling
RWTexture2DMSArrayRead/Write array of 2D textures with multisampling
RWTexture3DRead/Write 3D texture
RasterizerOrderedTexture1DRead/Write 1D texture with ROV guarantees
RasterizerOrderedTexture1DArrayRead/Write array of 1D textures with ROV guarantees
RasterizerOrderedTexture2DRead/Write 2D texture with ROV guarantees
RasterizerOrderedTexture2DArrayRead/Write array of 2D textures with ROV guarantees
RasterizerOrderedTexture3DRead/Write 3D texture with ROV guarantees
FeedbackTexture2DTexture feedback map
FeedbackTexture2DArrayArray of texture feedback maps
BufferRead-only buffer of scalar or vector types
RWBufferRead/write buffer of scalar or vector types
RasterizerOrderedBufferRead/write buffer with ROV guarantees
ByteAddressBufferRead-only byte-addressable buffer
RWByteAddressBufferRead-write byte-addressable buffer
RasterizerOrderedByteAddressBufferRead-write byte-addressable buffer with ROV guarantees
StructuredBufferRead-only buffer of UDTs
RWStructuredBufferRead-write buffer of UDTs
RasterizerOrderedStructuredBufferRead-write buffer of UDTs with ROV guarantees
AppendStructuredBufferStreaming output buffer
ConsumeStructuredBufferStreaming Input buffer
cbufferStruct declaration syntax for legacy format constant buffers
ConstantBufferTemplate syntax for legacy format constant buffers
tbufferStruct declaration syntax for legacy format texture buffers
TextureBufferTemplate syntax for legacy format texture buffers
SamplerStateSampler state
SamplerComparisonStateSampler comparison state

A few notes:

  • Textures and Buffer/RWBuffer have requirements that their elements fit in a scalar or four 4-byte elements, for both historical reasons and because they’re generally mapped to image formats.
  • The layouts cbuffer and tbuffer are tied to DXBC’s historical 16-byte row layout rules.
  • ByteAddress and Structured buffers aren’t constrained by image format or legacy layout.
  • Sampler state is represented by opaque objects that we won’t go into too much detail about here.

DXIL

DXIL represents resources in two different and subtly incompatible ways. All versions of DXIL have metadata resource records, and since shader model 6.6 we also have the resource properties that are bit-packed into the “annotateHandle” DXIL operation. There appears to be have been intent at some point to replace the metadata with the annotation model, but my understanding is that we need to continue to generate both in clang’s DXIL implementation to match DXC.

DXIL Metadata

The DXIL metadata records a resource class, a “shape”, and a few resource class dependent properties:

  • The Resource class is SRV (Shader resource view), UAV (Unordered Access View), CBV (Constant buffer view), or Sampler. This is largely only used for determining which register class a resource belongs to, and is mostly valuable in differentiating between (writeable) UAVs and (read-only) SRVs.
  • The “shape” is more commonly referred to as “resource kind” outside of the DXIL docs. This covers all of the texture and buffer types, as well as “tbuffer” which maps to the cbuffer layout when the resource class is SRV.
  • The resource class dependent properties attempt to cover the variety of possibilities per class but have a few issues. See the footnotes below.
RC“Shape”MS CountElement TypeStruct StrideROVCBuf SizeFeedback
Texture1DSRVTexture1D0enum----
Texture1DArraySRVTexture1DArray0enum----
Texture2DSRVTexture2D0enum----
Texture2DArraySRVTexture2DArray0enum----
Texture2DMSSRVTexture2DMSnumber1enum----
Texture2DMSArraySRVTexture2DMSArraynumber1enum----
Texture3DSRVTexture3D0enum----
TextureCUBESRVTextureCUBE0enum----
TextureCUBEArraySRVTextureCUBEArray0enum----
RWTexture1DUAVTexture1D-enum-false--
RWTexture1DArrayUAVTexture1DArray-enum-false--
RWTexture2DUAVTexture2D-enum-false--
RWTexture2DArrayUAVTexture2DArray-enum-false--
RWTexture2DMSUAVTexture2DMS-2enum-false--
RWTexture2DMSArrayUAVTexture2DMSArray-2enum-false--
RWTexture3DUAVTexture3D-enum-false--
RasterizerOrderedTexture1DUAVTexture1D-enum-true--
RasterizerOrderedTexture1DArrayUAVTexture1DArray-enum-true--
RasterizerOrderedTexture2DUAVTexture2D-enum-true--
RasterizerOrderedTexture2DArrayUAVTexture2DArray-enum-true--
RasterizerOrderedTexture3DUAVTexture3D-enum-true--
FeedbackTexture2DUAVFeedbackTexture2D---false-enum
FeedbackTexture2DArrayUAVFeedbackTexture2DArray---false-enum
BufferSRVTypedBuffer0enum----
RWBufferUAVTypedBuffer-enum-false--
RasterizerOrderedBufferUAVTypedBuffer-enum-true--
ByteAddressBufferSRVRawBuffer0-----
RWByteAddressBufferUAVRawBuffer---false--
RasterizerOrderedByteAddressBufferUAVRawBuffer---true--
StructuredBufferSRVStructuredBuffer0-number---
RWStructuredBufferUAVStructuredBuffer--numberfalse--
RasterizerOrderedStructuredBufferUAVStructuredBuffer--numbertrue--
AppendStructuredBufferUAVStructuredBuffer--numberfalse--
ConsumeStructuredBufferUAVStructuredBuffer--numberfalse--
cbufferCBuf-----number-
ConstantBufferCBuf-----number-
tbufferSRVTBuffer0u323----
TextureBufferSRVTBuffer0u323----

DXIL Binding Annotation

DXIL binding annotations bit-pack information into two integers, as represented by the unions in the DxilResourceProperties class. This format isn’t really documented, and is probably most easily understood by looking at the loadPropsFromResourceBase method, which populates the class’s members.

RCKindAlignUAVROVStrideCBuf SizeFeedbackElt TypeElt CountMS Count
Texture1DSRVTexture1D0falsefalse---enumnumber0
Texture1DArraySRVTexture1DArray0falsefalse---enumnumber0
Texture2DSRVTexture2D0falsefalse---enumnumber0
Texture2DArraySRVTexture2DArray0falsefalse---enumnumber0
Texture2DMSSRVTexture2DMS0falsefalse---enumnumbernumber
Texture2DMSArraySRVTexture2DMSArray0falsefalse---enumnumbernumber
Texture3DSRVTexture3D0falsefalse---enumnumber0
TextureCUBESRVTextureCUBE0falsefalse---enumnumber0
TextureCUBEArraySRVTextureCUBEArray0falsefalse---enumnumber0
RWTexture1DUAVTexture1D0truefalse---enumnumber0
RWTexture1DArrayUAVTexture1DArray0truefalse---enumnumber0
RWTexture2DUAVTexture2D0truefalse---enumnumber0
RWTexture2DArrayUAVTexture2DArray0truefalse---enumnumber0
RWTexture2DMSUAVTexture2DMS0truefalse---enumnumbernumber
RWTexture2DMSArrayUAVTexture2DMSArray0truefalse---enumnumbernumber
RWTexture3DUAVTexture3D0truefalse---enumnumber0
RasterizerOrderedTexture1DUAVTexture1D0truetrue---enumnumber0
RasterizerOrderedTexture1DArrayUAVTexture1DArray0truetrue---enumnumber0
RasterizerOrderedTexture2DUAVTexture2D0truetrue---enumnumber0
RasterizerOrderedTexture2DArrayUAVTexture2DArray0truetrue---enumnumber0
RasterizerOrderedTexture3DUAVTexture3D0truetrue---enumnumber0
FeedbackTexture2DSRVFeedbackTexture2D0falsefalse--enum---
FeedbackTexture2DArraySRVFeedbackTexture2DArray0falsefalse--enum---
BufferSRVTypedBuffer0falsefalse---enumnumber0
RWBufferUAVTypedBuffer0truefalse---enumnumber0
RasterizerOrderedBufferUAVBuffer0truetrue---enumnumber0
ByteAddressBufferSRVRawBuffer0falsefalse------
RWByteAddressBufferUAVRawBuffer0truefalse------
RasterizerOrderedByteAddressBufferUAVRawBuffer0truetrue------
StructuredBufferSRVStructuredBuffernumberfalsefalsenumber-----
RWStructuredBufferUAVStructuredBuffernumbertruefalsenumber-----
RasterizerOrderedStructuredBufferUAVStructuredBuffernumbertruetruenumber-----
AppendStructuredBufferUAVStructuredBuffernumbertruefalsenumber-----
ConsumeStructuredBufferUAVStructuredBuffernumbertruefalsenumber-----
cbufferCBufCBuffer0falsefalse-number----
ConstantBufferCBufCBuffer0falsefalse-number----
tbufferSRVTBuffer0falsefalse-number4----
TextureBufferSRVTBuffer0falsefalse-number4----

DirectX Target Extension Types

In LLVM IR we’ll represent all of the DXIL resource types via target extension types, as described in proposal 0006 and the DXILResources docs.

TargetExtType
Texture1Dtarget("dx.Texture", <Type>, /*IsWriteable*/ 0, /*IsROV*/ 0, <IsSigned>, Texture1D)
Texture1DArraytarget("dx.Texture", <Type>, /*IsWriteable*/ 0, /*IsROV*/ 0, <IsSigned>, Texture1DArray)
Texture2Dtarget("dx.Texture", <Type>, /*IsWriteable*/ 0, /*IsROV*/ 0, <IsSigned>, Texture2D)
Texture2DArraytarget("dx.Texture", <Type>, /*IsWriteable*/ 0, /*IsROV*/ 0, <IsSigned>, Texture2DArray)
Texture3Dtarget("dx.Texture", <Type>, /*IsWriteable*/ 0, /*IsROV*/ 0, <IsSigned>, Texture3D)
TextureCUBEtarget("dx.Texture", <Type>, /*IsWriteable*/ 0, /*IsROV*/ 0, <IsSigned>, TextureCUBE)
TextureCUBEArraytarget("dx.Texture", <Type>, /*IsWriteable*/ 0, /*IsROV*/ 0, <IsSigned>, TextureCUBEArray)
RWTexture1Dtarget("dx.Texture", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 0, <IsSigned>, Texture1D)
RWTexture1DArraytarget("dx.Texture", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 0, <IsSigned>, Texture1DArray)
RWTexture2Dtarget("dx.Texture", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 0, <IsSigned>, Texture2D)
RWTexture2DArraytarget("dx.Texture", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 0, <IsSigned>, Texture2DArray)
RWTexture3Dtarget("dx.Texture", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 0, <IsSigned>, Texture3D)
RasterizerOrderedTexture1Dtarget("dx.Texture", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 1, <IsSigned>, Texture1D)
RasterizerOrderedTexture1DArraytarget("dx.Texture", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 1, <IsSigned>, Texture1DArray)
RasterizerOrderedTexture2Dtarget("dx.Texture", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 1, <IsSigned>, Texture2D)
RasterizerOrderedTexture2DArraytarget("dx.Texture", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 1, <IsSigned>, Texture2DArray)
RasterizerOrderedTexture3Dtarget("dx.Texture", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 1, <IsSigned>, Texture3D)
Texture2DMStarget("dx.MSTexture", <Type>, /*IsWriteable*/ 0, Count, <IsSigned>, Texture2DMS)
Texture2DMSArraytarget("dx.MSTexture", <Type>, /*IsWriteable*/ 0, Count, <IsSigned>, Texture2DMSArray)
RWTexture2DMStarget("dx.MSTexture", <Type>, /*IsWriteable*/ 1, Count, <IsSigned>, Texture2DMS)
RWTexture2DMSArraytarget("dx.MSTexture", <Type>, /*IsWriteable*/ 1, Count, <IsSigned>, Texture2DMSArray)
FeedbackTexture2Dtarget("dx.FeedbackTexture", FeedbackType, FeedbackTexture2D)
FeedbackTexture2DArraytarget("dx.FeedbackTexture", FeedbackType, FeedbackTexture2DArray)
Buffertarget("dx.TypedBuffer", <Type>, /*IsWriteable*/ 0, /*IsROV*/ 0, <IsSigned>)
RWBuffertarget("dx.TypedBuffer", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 0, <IsSigned>)
RasterizerOrderedBuffertarget("dx.TypedBuffer", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 1, <IsSigned>)
ByteAddressBuffertarget("dx.RawBuffer", i8, /*IsWriteable*/ 0, /*IsROV*/ 0)5
RWByteAddressBuffertarget("dx.RawBuffer", i8, /*IsWriteable*/ 1, /*IsROV*/ 0)5
RasterizerOrderedByteAddressBuffertarget("dx.RawBuffer", i8, /*IsWriteable*/ 1, /*IsROV*/ 1)5
StructuredBuffertarget("dx.RawBuffer", <Type>, /*IsWriteable*/ 0, /*IsROV*/ 0)
RWStructuredBuffertarget("dx.RawBuffer", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 0)
RasterizerOrderedStructuredBuffertarget("dx.RawBuffer", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 1)
AppendStructuredBuffertarget("dx.RawBuffer", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 0)
ConsumeStructuredBuffertarget("dx.RawBuffer", <Type>, /*IsWriteable*/ 1, /*IsROV*/ 0)
cbuffertarget("dx.CBuffer", <Type>, ...)
ConstantBuffertarget("dx.CBuffer", <Type>, ...)
tbuffer
TextureBuffer
SamplerStatetarget("dx.Sampler", SamplerType::Default)
SamplerComparisonStatetarget("dx.Sampler", SamplerType::Default)

SPIR-V

DXC’s SPIR-V backend treats “Buffer” as a texture and maps DXC textures directly to OpTypeImage. In LLVM IR there is a 1-1 correspondence between the set of SPIR-V target extension types and the resulting OpTypeImage for the simple cases.

DimDepthArrayedMSSampledImage Format
Texture1D1DUnknown (2)falsefalser/o (1)
Texture1DArray1DUnknown (2)truefalser/o (1)
Texture2D2DUnknown (2)falsefalser/o (1)
Texture2DArray2DUnknown (2)truefalser/o (1)
Texture2DMS2DUnknown (2)falsetruer/o (1)
Texture2DMSArray2DUnknown (2)truetruer/o (1)
Texture3D3DUnknown (2)falsefalser/o (1)
TextureCUBECubeUnknown (2)falsefalser/o (1)
TextureCUBEArrayCubeUnknown (2)truefalser/o (1)
RWTexture1D1DUnknown (2)falsefalser/w (2)
RWTexture1DArray1DUnknown (2)truefalser/w (2)
RWTexture2D2DUnknown (2)falsefalser/w (2)
RWTexture2DArray2DUnknown (2)truefalser/w (2)
RWTexture2DMS2DUnknown (2)falsetruer/w (2)
RWTexture2DMSArray2DUnknown (2)truetruer/w (2)
RWTexture3D3DUnknown (2)falsefalser/w (2)
BufferBufferUnknown (2)falsefalser/o (1)
RWBufferBufferUnknown (2)falsefalser/w (2)

Note that dxc generally guesses at the image format for SPIR-V, and there is a vk::image_format attribute in DXC’s HLSL implementation that can be used to choose something in particular. We need to document what we’ll be doing here, considering the many discussions and issues about this over the years in DXC (#4941, #4773, #2498, #3395, #4868, …)

DXC lowers Constant/Texture/Structured/Byte Buffers to OpTypeStruct in SPIR-V with a storage class that depends on the Vulkan version (Uniform or StorageBuffer) and various layout decorations. DXC tracks the necessary information to lower these in side structures, and we’ll likely need to keep track of that information in new SPIR-V target extension types in our implementation.

Vulkan Buffer TypeSPIR-V Decoration
ByteAddressBufferStorageBufferBlock
RWByteAddressBufferStorageBufferBlock
StructuredBufferStorageBufferBlock
RWStructuredBufferStorageBufferBlock
AppendStructuredBufferStorageBufferBlock
ConsumeStructuredBufferStorageBufferBlock
cbufferUniformBlock
ConstantBufferUniformBlock
tbufferStorageBufferBlock
TextureBufferStorageBufferBlock

The “RasterizerOrdered” (or ROV) resources in HLSL don’t map directly to SPIR-V. In DXC, we emulate ROV by treating these the same as their non-ROV counterparts, wrapping accesses to the resource in critical sections using SPV_EXT_fragment_shader_interlock, and then relying on spirv-opt to clean up. We will probably need to extend the SPIR-V target extension types to represent these, and then have the backend do what it needs to do with that information.

RasterizerOrderedTexture1D
RasterizerOrderedTexture1DArray
RasterizerOrderedTexture2D
RasterizerOrderedTexture2DArray
RasterizerOrderedTexture3D
RasterizerOrderedBuffer
RasterizerOrderedByteAddressBuffer
RasterizerOrderedStructuredBuffer

Feedback textures aren’t implemented in DXC.

FeedbackTexture2D
FeedbackTexture2DArray

Attributes

From all of this, we’re able to come up with a set of attributes that can represent all of the texture and buffer types. These should carry sufficient information to lower to both of the targets:

RCTypeROVDimMSFeedbackArrayRawRow
Texture1DSRVvec4-1D-----
Texture1DArraySRVvec4-1D--yes--
Texture2DSRVvec4-2D-----
Texture2DArraySRVvec4-2D--yes--
Texture2DMSSRVvec4-2Dyes----
Texture2DMSArraySRVvec4-2Dyes-yes--
Texture3DSRVvec4-3D-----
TextureCUBESRVvec4-Cube-----
TextureCUBEArraySRVvec4-Cube--yes--
RWTexture1DUAVvec4-1D-----
RWTexture1DArrayUAVvec4-1D--yes--
RWTexture2DUAVvec4-2D-----
RWTexture2DArrayUAVvec4-2D--yes--
RWTexture2DMSUAVvec4-2Dyes----
RWTexture2DMSArrayUAVvec4-2Dyes-yes--
RWTexture3DUAVvec4-3D-----
RasterizerOrderedTexture1DUAVvec4yes1D-----
RasterizerOrderedTexture1DArrayUAVvec4yes1D--yes--
RasterizerOrderedTexture2DUAVvec4yes2D-----
RasterizerOrderedTexture2DArrayUAVvec4yes2D--yes--
RasterizerOrderedTexture3DUAVvec4yes3D-----
FeedbackTexture2DSRV----fbtype---
FeedbackTexture2DArraySRV----fbtypeyes--
BufferSRVvec4-------
RWBufferUAVvec4-------
RasterizerOrderedBufferUAVvec4yes------
ByteAddressBufferSRV------yes-
RWByteAddressBufferUAV------yes-
RasterizerOrderedByteAddressBufferUAV-yes----yes-
StructuredBufferSRVstruct-----yes-
RWStructuredBufferUAVstruct-----yes-
RasterizerOrderedStructuredBufferUAVstructyes----yes-
AppendStructuredBufferUAVstruct-----yes-
ConsumeStructuredBufferUAVstruct-----yes-
cbufferCBufstruct------yes
ConstantBufferCBufstruct------yes
tbufferSRVstruct------yes
TextureBufferSRVstruct------yes

Alternatives considered

We could also forgo the large set of specific attributes and boil things down to mostly resource class and resource kind. This is in some ways simpler, but has two main downsides:

  1. Not quite everything is represented, so we still need attributes for various numeric values, and some parameters like feedback type.
  2. The resource kind ties us fairly tightly to design decisions in DXIL, so may come across as anachronistic as we move to SPIR-V.

For these reasons, I think the broader set of specific attributes is a better approach.


  1. All SRVs have a sample count, but it’s zero when not multisampled. ↩︎ ↩︎

  2. The metadata does not represent sample count for RW multisample textures. ↩︎ ↩︎

  3. For tbuffer, element type seems to always be the enum value 5, for u32 ↩︎ ↩︎

  4. LLVM is probably buggy here currently. It differs from DXC and sets element type/count instead of cbuf size. ↩︎ ↩︎

  5. It may be better to use void or omit the type entirely rather than i8 here. ↩︎ ↩︎ ↩︎