Practical Rendering and Computation with Direct3D 11
+ -

2.2.1缓冲区资源-Append/Consume Buffers

2024-06-17 9 0

Append和comsume buffer都是SBV的变体。本质上,他们都是需要UAV绑定的资源,但他们在HLSL中实现了一种类似堆栈的访问行为:使用append()函数把元素push到buffer中,或者用consume()函数拉出元素。对于这两种buffer来说,添加元素的顺序并不重要,但所添加元素的数量很重要。UAV内部会记录添向buffer中添加或者删除的元素数量,因此当不同的GPU线程同时操作这样的buffer时,并不需要同步,大大提高了效率。

UAC:Unordered Access Views,即无序访问视图

我们通过一个简单的例子来介绍这种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来说却不行,这样的资源只能作为一个整体。

这个功能可以用一个例子来解释。在这种情况下,可以使用着色器程序来实现基于GPU的粒子系统。事实上,这是本书后面提供的示例算法之一。粒子系统将使用两个缓冲资源来保存其粒子信息。一个缓冲区将保存粒子的当前状态,而另一个将用于接收更新的粒子。运行时,计算着色器程序将使用Consume功能从当前状态缓冲区读取每个线程一个粒子,然后对粒子执行更新过程,最后将生成的粒子状态Append到输出缓冲区。由于每个粒子都是与其他粒子隔离更新的,因此缓冲区内的粒子顺序与更新过程完全无关。此外,由于缓冲区保持缓冲区中包含的元素数量的计数,因此更新过程可以根据需要添加或删除粒子。缓冲区的内部计数器跟踪当前驻留在缓冲区中的元素总数。因此,不需要在GPU线程之间同步以访问正确数量的粒子元素。
103354395698

创建append/consume缓冲区

要创建一个可以用作append/consume耗缓冲区的缓冲区,我们需要一个稍微更严格的创建过程。由于缓冲区用于可编程着色器阶段的读/写访问,因此我们需要使用D3D11_USAGE_DEFAULT使用标志。此外,绑定标志必须包括D3D11_BIND_UN0RDERED_ACCESS标志,以允许使用无序访问视图,并且通常还包括D3D11_BIND_SHADER_RES0URCE 标志。这也表示由于默认使用标志,CPU不能直接访问缓冲区内容。

ID3DllBuffer*CreateAppendConsumeBuffer(UINT size,UINT structsize, D3D11_SUBRES0URCE_DATA* pData )
{
    D3D11_BUFFER_DESC desc;
    desc.ByteWidth = size * structsize ;
    desc.MiscFlags = D3D11_RES0URCE_MISC_BUFFER_STRUCTURED;
    desc.StructureByteStride=structsize;
    //Select the appropriate usage and CPU access flags based on the passed in flags
    desc.BindFlagS = D3Dll_BIND_SHADER_RESOURCE | D3Dll_BIND_UNORDERED_ACCESS;
    desc.Usage = D3D11_USAGE_DEFAULT;
    desc.CPUAccessFlags = 0;
    // Create the buffer with the specified configuration
    ID3DllBuffer* pBuffer = 0;
    HRESULT hr = g_pDevice->CreateBuffer( &desc, pData, SpBuffer );
    if ( FAILED( hr ) )
    {
    // Handle the error here...
    return( 0 );
    }
    return( pBuffer );
}

可以看出,这或多或少是已经可用于创建缓冲区/结构化缓冲区资源的配置之一,但强制使用默认使用标志除外。这意味着Append/Consume缓冲区的使用不需要专门创建的缓冲区资源,而是必须对无序访问视图进行专门配置才能提供额外的功能。用于将缓冲区资源绑定到管道作为Append/Consume缓冲区的UAV必须使用其描述结构中指定的D3D11_BUFFER_UAV_FLAG_APPEND标志创建。

资源视图要求

在创建要与Append/Consume缓冲区一起使用的资源视图时,与前面讨论的资源视图有一些不同。资源视图的格式参数必须设置为DXGI_F0RMAT_R32_UNKNOWN才能使用Append/Consume功能。此外,必须使用D3Dll_BUFFER_UAV_FLAG_APPEND为APPEND和consumerbuffers创建无序访问视图。这些要求是创建无序访问视图以使用缓冲区资源作为Append/Consume缓冲区的唯一附加设置。

正如我们在前面的缓冲区讨论中所提到的,可以在同一资源上同时使用多个资源视图。不幸的是,对于将整个缓冲区资源视为由单个子资源组成的情况,情况并非如此。无序访问视图允许对其资源进行读写访问,并且不允许同时将多个可写资源视图附加到一个子资源。这意味着,例如,不可能使用具有两个无序访问视图的单个缓冲区同时从缓冲区的不同区域显示和消费。在这种情况下,Append/Consume UAVs需要附加到单独的缓冲区资源,以便同时使用。图2.18以图形方式显示了一个缓冲区如何被多个资源视图同时引用。
104451723402

HLSL Append/Consume缓冲区资源对象

下面是在HLSL中声明A/C buffer的代码

struc Particle
{
         float3 position;
         float3 velocity;
         float time;
}
AppendStructuredBuffer<Particle> newSimulationState : register(u0);
ConsumeStrucuredBuffer<Particle> currentSimulationState : register (u1);

接下来最常使用的append和consume缓冲区方法可能是实际的append()和consumer()资源对象方法。这些操作正如预期的那样,在consume()的情况下返回所需的值,在append()的情形下被推入缓冲区。我们还可以在列表中看到,GetDimensions()方法也可以用于append和consume缓冲区。这可以在HLSL中用于读取缓冲区中可用的元素数量,并随后确保数据不会被多次附加到缓冲区,这将导致额外数据的丢失

0 篇笔记 写笔记

作者信息
站长漫谈
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!