DirectX12 - Resource Binding(资源绑定)

GPU图形驱动工程师,这个DX12 Spec系列会持续更新,任何GPU相关的问题都可以互动:

DirectX12 Spec 总目录

// 2023年8月5日更新,翻看之前写的有点挫,重新修改一下

1. descriptor

1.1 基本概念

  • 不管是做上层的游戏开发,还是做底层的驱动开发,都绕不开descriptor这个概念。
  • app想做一些定制化的运算,比如说实现某个特效等,都要游戏开发者去手搓hlsl语句(opengl这边叫glsl,其实都差不多),hlsl如何访问需要的resource呢?就是通过descriptor
  • 如字面意思,descriptor就是一个描述符,描述了resource的gpu va(virtual address),format,dimension等信息

1.2 举个例子

  • 常见的SRV(shader resource view,shader就是上面说的hlsl语句,那shader resource view就是描述了一个hlsl里面,能直接访问的resource),在CreateShaderResourceView()接口中,开发者要提供以下参数:
HRESULT CreateShaderResourceView(
  [in]            ID3D11Resource                        *pResource,
  [in, optional]  const D3D11_SHADER_RESOURCE_VIEW_DESC *pDesc,
  [out, optional] ID3D11ShaderResourceView              **ppSRView
);
  • pResource指向的就是app自己创建的resource指针,当然在app侧直接解引用是无法使用的,因为这个指针是directx runtime在管理
  • pDesc的结构如下:
typedef struct D3D11_SHADER_RESOURCE_VIEW_DESC {
  DXGI_FORMAT         Format;
  D3D11_SRV_DIMENSION ViewDimension;
  union {
    D3D11_BUFFER_SRV        Buffer;
    D3D11_TEX1D_SRV         Texture1D;
    D3D11_TEX1D_ARRAY_SRV   Texture1DArray;
    D3D11_TEX2D_SRV         Texture2D;
    D3D11_TEX2D_ARRAY_SRV   Texture2DArray;
    D3D11_TEX2DMS_SRV       Texture2DMS;
    D3D11_TEX2DMS_ARRAY_SRV Texture2DMSArray;
    D3D11_TEX3D_SRV         Texture3D;
    D3D11_TEXCUBE_SRV       TextureCube;
    D3D11_TEXCUBE_ARRAY_SRV TextureCubeArray;
    D3D11_BUFFEREX_SRV      BufferEx;
  };
} D3D11_SHADER_RESOURCE_VIEW_DESC;
  • 这里面的参数就很丰富了,描述了resource的维度信息,细分为buffer、1d、2d、3d,然后再去看buffer、1d、2d、3d里面该去提供哪些信息,无非就是ElementWidth,LOD这些
  • ppSRView返回创建成功的SRV指针,同样在app侧直接解引用是无法使用的,这个指针是directx runtime在管理
  • 如果只是为了了解descriptor的基本概念,看到这里就应该结束了,但是很不幸,因为resource资源的多样性,和directx的灵活性,在简单的descriptor基础概念上,微软又整了很多花活,想深入了解还得继续往下看

1.2 descriptor type

  • descriptor按照所访问resource的类型,可以分为:
  1. Constant buffer view (CBV)Constant buffers contain shader constant data and can be accessed by any GPU shader,注意这里的constant不同于C/C++中的常量,这里的constant是在shader运算过程种保持数据不变,而其他状态可以更新数据
  2. Unordered access view (UAV)An unordered access view enables the reading and writing to the texture (or other resource) in any order,这里是指为shader无序访问同一块Resource提供读写安全机制,类似于读写锁
  3. Shader resource view (SRV)Shader resource views typically wrap textures in a format that the shaders can access them,这里SRV为Shader访问Resource最普通的一种形式,无特殊功能
  4. Samplers:Shader访问采样资源,详细可以查一下细节,Sample采样是提高图形质量的一种手段,在抗锯齿方面有MSAA(多重采样抗锯齿)、SSAA(超级采样抗锯齿)等各种算法
  5. Render Target View (RTV):Render Target就是dx12整个渲染出来的图形数据,会缓存到buffer中,系统再输出到显示器等,一般调用CreateSwapChainForHwnd()创建一块缓冲区,然后作为缓冲对象输出图形数据
  6. Depth Stencil View (DSV):做Depth Stencil测试用的缓冲区,3D显示中有图形遮挡的概念,可以用Depth Stencil Buffer缓存像素点的Z坐标,然后根据Z深度判定pixel是否输出和进一步计算
  7. Vertex Buffer View (VBV):Vertex用来保存所有输入的顶点信息(像素点用Vertex来表示,可以包括点的空间坐标、颜色、法向量等等,Vertex也可以是用户自定义的各种数据结构),是整个DX12 Pipeline的输入数据
  8. Index Buffer View (IBV):Index是配合Vertex一起使用的,简而言之就是对于Vertex建立索引,这样对于Vertex大量使用的情况,可以直接用索引代替,不用重复保存Vertex数据
  9. Stream Output View (SOV):在DX12 Pipeline中,我们可以选择在GS(Geometry Shader)之后,直接将数据通过Stream Output的方式,直接输出出来,SOV定义输出对象

2. descriptor heap & descriptor range & descriptor table

Descriptor Heap

2.1 descriptor heap

  • 有了descriptor的概念之后,descriptor heap也就好理解了,descriptor heap是分配出来的gpu memory,是存储descriptor的地方

2.2 descriptor range

  • 如上图所示,descriptor range就是从descriptor heap中拎出来的subrange,一段连续的descriptor。这个概念就像c语言中的数组和子数组

2.3 Descriptor Table

  • Descriptor Table = Descriptor Range 1 + Descriptor Range 2 + … + Descriptor Range n,我把n个Descriptor Range放在一起,就形成了一个Descriptor Table。
  • 需要指出来的是:Descriptor Table是由Descriptor Range构成的,每个Descriptor Range只能存放CBV/SRV/UAV/Sampler中的一种,不同的CBV/SRV/UAV Range可以混合存放于一个Table中,Sampler需要单独放在一个Descriptor Table中,不可与CBV/SRV/UAV混合存放

3. Root Signature

在这里插入图片描述

  • The root signature is the definition of an arranged collection of descriptor table, root constant and root descriptor,这里牵扯的概念有点多,官方文档也没仔细展开讲,我一个个说明:

3.1 Signature

  • 签名的概念应该很多编程语言都有,比如C/C++里面的函数签名,我在.h文件中做了一个函数申明,其中包括了函数形参,规定了传入实参的数据个数、类型等,这就是一个函数签名
  • 对应于DX12里面的Signature,你可以把GPU的Shader(前面提到的GPU流水线的各个运算模块)想象成一个函数,接受Descriptor(参数)并进行运算。开发者必须要在APP层申明一个Root Signature,规定好Shader需要用到哪些Descriptor,这样APP传下去的Descriptor(包含各种Resource信息)和Shader里面用到的(Resource)就会保持一致。

3.2 Root Argument、Root Parament

  • Root Signature类似于函数签名,Root Argument就是函数签名中的各个形参,所有Root Argument组合在一起形成了Root Signature。
  • Root Parament是啥?它其实就是开发者填写好的函数实参,是Root Argument的填好值的样子。

3.3 Descriptor Table、Root Constant、Root Descriptor

  • Descriptor Table:前文阐述过了,不再赘述。还是强调一点,Descriptor Table中只能用来存放CBV/SRV/UAV/Sampler中的1种或者组合
  • Root Descriptor:对于Descriptor Table,GPU会内部分配一块Memory专门存放Descriptor Table,而Root Descriptor则是直接将Descriptor信息放入GPU寄存器,这是硬件做好的。但是也会产生各种问题,比如硬件寄存器的数量是有限的,API申请的Root Descriptor数量可能会超,这些问题都会在Driver层做处理。Root Descriptor只能用来存放CBV/SRV/UAV
  • Root Constant:这个类似于Root Descriptor,不过Root Constant不会存储Resource的地址等信息让GPU解析访问,而是直接保存开发者给的Value,这些数值也是存在硬件寄存器中的,粒度是以32-bit为一个单位。

4. Limits

4.1 Tier Level

  • FL就是Feature Level的意思,可以看到Directx 9/10/11等不同Feature Level条件下,对于硬件需要支持的CBV/SRV/UAV/Sampler的最大数量是有要求的,达到对应的数量,硬件才能上报相应的Feature Level:
Tier 1, FL 9.4, 11.0+Tier 2, FL 11.0+Tier 3, FL 11.1+
Max # descriptors in a shader visible CBV/SRV/UAV heap100000010000001000000+
Max CBVs in all descriptor tables per shader stage1414full heap
Max SRVs in all descriptor tables per shader stage128full heapfull heap
Max UAVs in all descriptor tables across all stages8 (64 for FL 11.1+)64full heap
Max Samplers in all descriptor tables per shader stage16full heapfull heap

4.2 Root Signature Size

  • Root Argument:最大长度是64 DWORDS,这点会在Create Root Signature的时候由DX12 Runtime检查,如果长度超过64 DW并且开启了调试层,Runtime会直接报错并打印详细信息(DX12官方做好的这层Debug Layer很好用,错误信息打印非常全)。
  • Descriptor Table:占用1 DW。
  • Root Descriptor:占用2 DW。
  • Root Constant:占用1 DW * NumOfConstant(1个constant占用32-bit数据,具体占用多少取决于用户申明的NumOfConstant)。

5. API

5.1 CheckFeatureSupport()

  • 上面提到了,DirectX12根据硬件支持的Descriptor数量,将硬件的Feature Level数量划分为Tier1/2/3这3个Level,当DX12的Rumtime调用CheckFeatureSupport()向GPU Driver查询时,硬件可以根据自身情况返回3个Tier Level:
typedef enum D3D12_RESOURCE_BINDING_TIER {
  D3D12_RESOURCE_BINDING_TIER_1 = 1,
  D3D12_RESOURCE_BINDING_TIER_2 = 2,
  D3D12_RESOURCE_BINDING_TIER_3 = 3
} ;

5.2 Create Descriptor Heap

5.2.1 Descriptor Heap Type

typedef enum D3D12_DESCRIPTOR_HEAP_TYPE
{
    D3D12_CBV_SRV_UAV_DESCRIPTOR_HEAP,
    D3D12_SAMPLER_DESCRIPTOR_HEAP,
    D3D12_RTV_DESCRIPTOR_HEAP,
    D3D12_DSV_DESCRIPTOR_HEAP,
    D3D12_NUM_DESCRIPTOR_HEAP_TYPES
} D3D12_DESCRIPTOR_HEAP_TYPE;

5.2.2 CreateDescriptorHeap()

HRESULT CreateDescriptorHeap(
  [in]  const D3D12_DESCRIPTOR_HEAP_DESC *pDescriptorHeapDesc,
        REFIID                           riid,
  [out] void                             **ppvHeap
);
  • 其中需要填充好D3D12_DESCRIPTOR_HEAP_DESC结构体,其中规定了Heap存放Descriptor的类型和数量:
typedef struct D3D12_DESCRIPTOR_HEAP_DESC {
  D3D12_DESCRIPTOR_HEAP_TYPE  Type;
  UINT                        NumDescriptors;
  D3D12_DESCRIPTOR_HEAP_FLAGS Flags;
  UINT                        NodeMask;
} D3D12_DESCRIPTOR_HEAP_DESC;

5.2.3 如何访问Descriptor Heap中的某个Descriptor?

  • 答案是和C/C++中的数组一样,采用Start+Offset的形式,GetCPUHandleForHeapStart()获取Descriptor Heap的Start(不可直接解引用,需要给到DX12 Runtime),GetDescriptorHandleIncrementSize()获取Descriptor的SizeStart + Size * Index即可定位到某个Descriptor。

5.2.4 创建Root Descriptor?

  • Root Descriptor无外乎CBV/SRV/UAV/Sampler,那么我们对应调用CreateShaderResourceView() / CreateConstantBufferView() / CreateUnorderedAccessView()/CreateSampler()即可,注意填充对应的ResourceView参数。

5.2.5 特殊的SOV、RTV、DSV

  • 这3种Descriptor类型在1.1 Descriptor Type有提到过,在GPU Driver层一般有对应的register存储该Descriptor信息,我们只需要将对应的Resource创建好,然后将Resource信息存到Descriptor中,调用CreateStreamOutputView() / CreateRenderTargetView() / CreateDepthStencilView()即可,在Driver中会将Descriptor信息写到对应register。

6. Utils

6.1 Register Space

  • The purpose of the register space field is to expand the namespace for register bindings。举个例子,开发者对于同一个SRV寄存器t#0(t#0就是指t寄存器中的第0个,t#对应SRV,u#对应UAV,s#对应Sampler,c#对应CBV),既想要绑定到Resource0上,又想绑定到Resource1上面,那可以这样声明:在绑定到Resource0时,声明为(t#0,space0),绑定到Resource1时,声明为(t#0,space1)。这里的space0/1就是Register Space,类似于C++中的namespace,我在不同的namespace可以申明同一个名称的变量。

6. 参考文档

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值