The Beauty of DirectX 11 (4) -- Append/Consume,Byte Address and Indirect Argument Buffer
作者:clayman
仅供个人学习使用,请勿转载,勿用于任何商业用途。
Append/Consume Buffers
Append和comsume buffer都是SBV的变体。本质上,他们都是需要UAV绑定的资源,但他们在HLSL中实现了一种类似堆栈的访问行为:使用append()函数把元素push到buffer中,或者用consume()函数拉出元素。对于这两种buffer来说,添加元素的顺序并不重要,但所添加元素的数量很重要。UAV内部会记录添向buffer中添加或者删除的元素数量,因此当不同的GPU线程同时操作这样的buffer时,并不需要同步,大大提高了效率。
我们通过一个简单的例子来介绍这种buffer的用途,一个基于GPU的粒子系统:系统使用2块buffer保存粒子信息,一块保存粒子当前的状态,另一块保存更新过的粒子。运行时,computer shader中的每个一个GPU线程都使用consume()方法读取当前粒子信息,进行更新计算,然后,用append()方法把结果写到另一块buffer中。由于每个粒子的状态都是独立的,所以buffer中粒子的顺序无关紧要,只要保证数量正确即可。
创建A/C类型的buffer约束比较严格,因为buffer需要可同时读写,需要使用D3D11_USAGE_DEFAULT标志,此外绑定标识必须包含3D311_BIND_UNORDERED_ACCESS标识,一般还需要加上D3D11_BIND_SHADER_RESOURCE。创建相应的RV时,数据格式总是DXGI_FORMAT_R32_UNKNOWN,同时无论是append还是comsume buffer都必须用D3D11_BUFFER_UAV_FLAG_APPEND参数。上一部分介绍B/S buffer时曾经说个可以把B/S buffer划分为多个subresource,分别使用不同的RV,但是,对于允许读写操作UAV来说却不行,这样的资源只能作为一个整体。
下面是在HLSL中声明A/C buffer的代码
struc Particle
{
float3 position;
float3 velocity;
float time;
}
AppendStructuredBuffer<Particle> newSimulationState : register(u0);
ConsumeStrucuredBuffer<Particle> currentSimulationState : register (u1);
Byte Address Buffers
BAB为HLSL提供了一种相对低级的方式来访问显存块。与通过索引访问元素的方法不同,BAB使用字节地址访问其中的元素: 字节地址n代表的值为从资源开头偏移n个字节后的4个32位无符号整数的值,注意,n必须为4的倍数,指向的4个32位无符号整数也可以通过转型转义为其他类型的值。这种类型的buffer为HLSL提供了强大的数据管理操作能力,几乎可以用它来实现任意类型的数据结构。
举例来说,一个储存32位颜色值的链表,每个链表节点由一个颜色值和指向下一个元素的标记组成。当添加第一个元素时,颜色值被写到偏移值为0的位置,指向下一个元素的索引为-1。 添加第二个元素时,程序首先读取偏移值为0处的2个32位值,然后通过索引,依次找到链表末端,最后完成添加。使用BAB,以前很多只能在CPU上计算的传统算法,都可以能GPU上实现了! 当然,由于GPU天生的平行性,实际情况要稍微复杂一些,以后会仔细介绍。
BAB的创建方式与前面介绍的几种资源基本一致,唯一的区别是需要把MiscFlags指定为D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS。只读的BAB可以通过shader resource view绑定到任意shader阶段;作为UAV使用的可读写BAB则只能绑定到pixel shader和compute shader阶段。两种情况下,RV都必须是DXGI_FORMAT_R32_TYPELESS格式,UAV还必须加上D3D11_BUFFER_UAV_FLAG_RAW标识。
在HLSL中,用以下代码声明BAB资源:
ByteAddressBuffer rawBuffer0;
RWByteAddressBuffer rawBUffer1;
Indirect Argument Buffers
前面介绍的几种buffer主要用于把数据从CPU端传递到GPU端,而IAB的设计理念是允许GPU自己填充数据,并且在后面的计算中使用。IAB主要在以下三个函数中使用:
DrawInstancedIndirect(ID3D11Buffer* pBufferForArgs, UINT AlignedByteOffsetForArgs);
DrawIndexedInstancedIndirect(ID3D11Buffer *pBufferForArgs,UINT AlignedByteOffsetForArgs);
void DispatchIndirect(ID3D11Buffer *pBufferForArgs,UINT AlignedOffsetForArgs);
三个函数都以indirect后缀结尾,与没有indirect后缀的方法相比,虽然参数类型不同,本质却相同,比如DrawInstanced()接受4个参数:vertexCountPerInstance,instanceCount,startVertexLocation和startInstanceLocation。Indirect版本同样需要这4个参数,只不过把他们放到了第一个参数pBufferForArgs中,并且通过第二个参数定位他们在buffer中的位置。因此,必须保证pBufferForArgs在指定偏移位置有4个有效的uint值,并且偏移值必须以4byte对齐的,pBufferForArgs可以在不同位置,包含多组不同的参数值,通过offset选择希望使用的参数。这样的好处在于 buffer数据可由GPU填充,所以CPU完全不需要知道要绘制多少图元!图形管线输出的数据和CPU计算的结果都可以用来更新IAB数据。 比如,把compute shader的计算结果保存到AppendStructuredBuffer中,使用ID3D11DeviceContext:CopyStructureCount把AppendStructuredBuffer中的数据复制到IAB中,最后使用DrawInstancedIndirect()把CS计算的数据直接渲染出来,整个过程只需要很少的CPU参与,CPU完全不用知道渲染了多少图形。
创建IAB的方法和前面几种buffer没有太多区别,一般来说,如果希望用GPU填充数据,那么一定要使用default usage,此外,MiscFlags必须是D3D11_RESOURCE_MISC_DRAWINDIRECT_ARGS,最后,byteWidth必须是4byte的n倍。
当由GPU填充IAB数据时,比如通过stream out,render target或者UAV方式,必须和配合相应的resource view一起使用,当由CPU填充数据时,则主要通过UpdateSubresource方法和CopyStructureCount。
至此,我们介绍了DirectX 11中的所有buffer resource,下一部分将讨论texture resource。
------------------------------to be continue----------------------------------------------
再次提醒,已经发了的四篇文章对DX11的介绍都比较抽象,如果觉得看不太懂,那么应该先找一些有demo的DX11的入门教程来看。这系列的文章偏重对DX11 API原理的介绍和梳理,适合作为参考资料,而不是第一本入门教程。未来的内容在介绍完texture resource之后,还主要有以下几个部分:
Rendering Pipeline 介绍流水线的每一个阶段
Tessellation Pipeline
Computation Pipeline 着重介绍DX11中两个最重要的功能
HLSL 5.0
Multithreaded Rendering