The Beauty of DirectX 11 (3) --- constant buffer, buffered/structure buffer

The Beauty of DirectX 11 (3) --- constant buffer, buffered/structure buffer

作者:clayman

仅供个人学习使用,请勿转载,勿用于任何商业用途。

 

Constant Buffer

         Constant buffer(cb)DX10中引入的概念,它取代了DX9时代GPU常量寄存器的概念,允许通过一块大小可变的buffershader提供常量数据,而不是之前数量非常受限的n个寄存器,这也是我们遇到的第一种可在shader着色阶段由HLSL访问的资源。 管线的每个可编程阶段都能同时访问一个或者几个cb,对于shader代码来说,cb中的数据都是全局常量,作为cb而创建的资源不能绑定到其他*类型*的管线位置,但同一个cb可以同时绑定到管线的多个不同阶段。

 

         虽然cbDXshader constant的用法几乎一样,但要特别注意,cb中的数据总是作为一个整体被提交给GPU,这意味着即使cb中只有一个变量改变了,也必须重新提交整个cb。因此,所有关于DX10/11的文章都会强调不要把所有变量都放到一个cb中,而是按照变量改变的频率来组织变量,以尽量减少带宽消耗。比如,把viewMatrix,viewProjMatrix,eyePosition,sunVectorper-frame数据放到cb0中,只要每帧渲染前更新一次即可;把worldMatrixlocalLightobjectColor等作为per-object参数放到另外一块cb中,每次渲染物体时更新。游戏总是以每秒30fps以上的速度运行,并且每帧需要渲染大量物体,因此,合理组织cb非常重要!

 

         创建cb的代码和上一次创建vertex buffer的代码类似,大部分参数的用途也一致,选择合适的枚举,以获得最好性能。有三点需要注意的地方,1. cb中的数据结构应该直接对应着HLSL中的数据结构,这样更新cb时,只需要直接复制数据即可;2. cb的大小必须是16byten(nfloat4大小)3. cbbind flag只能是D3D11_BIND_CONSTANT_BUFFER,不能与其他位标记组合使用。使用cb时,不需要resource view,可以直接绑定到管线。

 

         因为cbshader直接可访问的资源,在HLSL中,用以下语法声明cb

cbuffer Tramsform
{
    matrix worldMatrix;
    matrix worldViewProjMatrix;
    matrix bones[ 26];
}

cbuffer View
{
   float3 eyePosition;
   matrix viewMatrix;
}

 

 

         cbuffer 关键字声明 cb cb 的名称一般对 HLSL 没太多用处,而是让 C++ 应用程序端来识别特定 cb 。可以通过 shader reflection API 获得每个 cb 中的数据结构信息,在介绍 HLSL 的部分会详细讨论。

 

Buffer/Structured Buffer Resource

         接下来的一种资源,根据所保存的数据类型不同,又分为standard(buffered) buffer resouce(扭曲的名字)structured buffer resource。两者都是类似数组的结构,区别在于BBR的元素是普通内置类型,比如float4,而SBR的元素则是自定义的结构。之所以要做区分,原因是底层在处理数据映射到HLSL时的方式稍有不同。这种类型的buffer特别适合于shader需要访问大量数据的情况。也是目前为止第一种需要通过resource view绑定到管线的资源。所有可编程shader阶段都可访问b/s buffer,在pixel shadercomputer shader中还允许写入操作。通过不同的resource view来控制b/s buffer的可访问性,比如shader resource view, unordered access view或者两者一起使用,任何shade都可以使用shader resource view标志,而unordered access view则只能对pscs使用。当b/s buffer为只读时,可以同时绑定到管线的多个阶段;允许写入时(使用了uav标志)则只能绑定到一个位置。

 

         B/S buffer有三种典型的用途,第一种,用来保存静态数据,比如预计算的辐射传播(PRT)数据;第二种,运行时,由CPU计算出的数据,比如很多涉及到GPGPU的算法;最后一种则是由GPU计算填充的数据,比如由GPU计算的物理模拟结果。创建b/s buffer时,通常根据这三种用途,选择合适的访问参数。对于structured buffer来说,除了指定buffer大小之外,还需要提供每个结构的大小。以下是创建structured buffer的代码:

 

ID3D11Buffer* CreateStructuredBuffer( uint count, uint structSize, bool CPUWritable, bool GPUWritable,D3D11_SUBRESOURCE_DATA* pData)
{
         D3D11_BUFFER_DESC desc;
         desc.ByteWidth = count * structSize;
         desc.MiscFlags = D3D11_RESOURCE_MISC_MUFFER_STRUCTURED;
         desc.StructureByteStride = structSize;

          if(!CPUWritable && !GPUWriteable)
         {
             desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
             desc.Usage = D3D11_USAGE_IMMUTABLE;
             desc.CPUAccessFlags =  0;
         }
         else  if( CPUWritable && !GPUWritable)
         {
             desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
             desc.Usage = D3D11_USAGE_DYNAMIC;
             desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
         }
         else  if( !CPUWritable && GPUWritable )
         {
             desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_UNORDERED_ACCESS;
             desc.Usage = D3D11_USAGE_DEFAULT;
             desc.CPUAccessFlags =  0;
         }
         else  if ( CPUWritable && GPUWritable)
         {
            // error handling
            
// resource acan’t be write by both CPU and GPU simultaneously
         }

         ID3D11BUFFER* pBuffe =  0;
         HRESULT hr = pDevice->CreateBuffer( &desc,pData,&pBuffer);
         if(FAILED(hr))
         {
            return  0;
         }
         return pBuffer;
}

 

         B/S buffer需要通过RV才能绑定到管线上,并且只能是shader resource view或者unordered access view。需要在创建RV时提供buffer的数据格式,BBR的格式必须是DXGI中定义的格式之一,SBR的格式总是DXGI_FORMAT_UNKNOWN。创建RV时,可以指定开始元素(ElementOffset)的位置和元素个数(ElementWidth),可以选择把整个或者一部分buffer数据包含在view中。对于HLSL来说,绑定到管线的buffer则总是一个下表标从在[0,ElementWidth]之间的数组。

 

ID3D11ShaderResourceVIew* CreateBufferSRV ( ID3D11Rsource* pResource)
{
         D3D11_SHADER_RESOURCE_VIEW_DESC desc;
         desc.Format = DXGI_FORMAT_R32G32B32_FLOAT;
         desc.ViewDimension = D3D11_SRV_DIMENSION_BUFFER;
         desc.Buffer.ElementOffset =  0;
         desc.Buffer.ElementWidth =  100;

         ID3D11ShaderResourceView* view =  0;
         HRESULT hr = pDevice->CreateShaderResourceView(pResource, &desc, &pView);

          return pView;
}

 

         使用 ElementOffset ElementWidth 参数,可以把同一个 B/S buffer 分成多个子 buffer ,每个子 buffer 都可以有独立的 RV 。创建 UAV 的方法和 SRV 类似,但需要额外加一个属性:

 

desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_COUNTER;

 

         为了在shader中访问B/S buffer数据, HLSL中也要用类似一种模板或泛型的语法声明相应的变量,下面是SBR的声明:

struct GridPoint
{
     float height;
    float4 flow;
}

RWStructuredBuffer<GridPoint> newWaterState  :register(u0);
StructuredBuffer<GridPoint> currentWaterState  :register(t0);

 

         可以通过两种方法声明结构化bufferRWStructuredBuffer<>StructuredBuffer< >。区别在于前者对应着可读可写的bufferUAV),后者则是只读的数据(SRV),注意,不同buffer对应的寄存器标识也不一样。声明了HLSL变量以后,就能用普通数组的语法,通过索引访问buffer中的数据了。HLSL中提供GetDimensions()函数,可以返回buffer大小,或者是SBR中的数组元素个数。对于使用UAV绑定的SRB来说,由于指定了D3D11_BUFFER_UAV_FLAG_COUNTER标记,还可以使用IncrementCounter()DecrementCounter函数管理数据。

 

-------------------------------------to be continue----------------------------------

 

你可能感兴趣的:(The Beauty of DirectX 11 (3) --- constant buffer, buffered/structure buffer)