The Beauty of DirectX 11 (3) --- constant buffer, buffered/structure buffer
作者:clayman
仅供个人学习使用,请勿转载,勿用于任何商业用途。
Constant buffer(cb)是DX10中引入的概念,它取代了DX9时代GPU常量寄存器的概念,允许通过一块大小可变的buffer向shader提供常量数据,而不是之前数量非常受限的n个寄存器,这也是我们遇到的第一种可在shader着色阶段由HLSL访问的资源。 管线的每个可编程阶段都能同时访问一个或者几个cb,对于shader代码来说,cb中的数据都是全局常量,作为cb而创建的资源不能绑定到其他*类型*的管线位置,但同一个cb可以同时绑定到管线的多个不同阶段。
虽然cb与DX中shader constant的用法几乎一样,但要特别注意,cb中的数据总是作为一个整体被提交给GPU,这意味着即使cb中只有一个变量改变了,也必须重新提交整个cb。因此,所有关于DX10/11的文章都会强调不要把所有变量都放到一个cb中,而是按照变量改变的频率来组织变量,以尽量减少带宽消耗。比如,把viewMatrix,viewProjMatrix,eyePosition,sunVector等per-frame数据放到cb0中,只要每帧渲染前更新一次即可;把worldMatrix,localLight,objectColor等作为per-object参数放到另外一块cb中,每次渲染物体时更新。游戏总是以每秒30fps以上的速度运行,并且每帧需要渲染大量物体,因此,合理组织cb非常重要!
创建cb的代码和上一次创建vertex buffer的代码类似,大部分参数的用途也一致,选择合适的枚举,以获得最好性能。有三点需要注意的地方,1. cb中的数据结构应该直接对应着HLSL中的数据结构,这样更新cb时,只需要直接复制数据即可;2. cb的大小必须是16byte的n倍(n个float4大小);3. cb的bind flag只能是D3D11_BIND_CONSTANT_BUFFER,不能与其他位标记组合使用。使用cb时,不需要resource view,可以直接绑定到管线。
因为cb是shader直接可访问的资源,在HLSL中,用以下语法声明cb:
{
matrix worldMatrix;
matrix worldViewProjMatrix;
matrix bones[26];
}
cbuffer View
{
float3 eyePosition;
matrix viewMatrix;
}
Buffer/Structured Buffer Resource
接下来的一种资源,根据所保存的数据类型不同,又分为standard(buffered) buffer resouce(扭曲的名字)和structured buffer resource。两者都是类似数组的结构,区别在于BBR的元素是普通内置类型,比如float4,而SBR的元素则是自定义的结构。之所以要做区分,原因是底层在处理数据映射到HLSL时的方式稍有不同。这种类型的buffer特别适合于shader需要访问大量数据的情况。也是目前为止第一种需要通过resource view绑定到管线的资源。所有可编程shader阶段都可访问b/s buffer,在pixel shader和computer shader中还允许写入操作。通过不同的resource view来控制b/s buffer的可访问性,比如shader resource view, unordered access view或者两者一起使用,任何shade都可以使用shader resource view标志,而unordered access view则只能对ps和cs使用。当b/s buffer为只读时,可以同时绑定到管线的多个阶段;允许写入时(使用了uav标志)则只能绑定到一个位置。
B/S buffer有三种典型的用途,第一种,用来保存静态数据,比如预计算的辐射传播(PRT)数据;第二种,运行时,由CPU计算出的数据,比如很多涉及到GPGPU的算法;最后一种则是由GPU计算填充的数据,比如由GPU计算的物理模拟结果。创建b/s buffer时,通常根据这三种用途,选择合适的访问参数。对于structured buffer来说,除了指定buffer大小之外,还需要提供每个结构的大小。以下是创建structured buffer的代码:
{
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]之间的数组。
{
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;
}
desc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_COUNTER;
为了在shader中访问B/S buffer数据, HLSL中也要用类似一种模板或泛型的语法声明相应的变量,下面是SBR的声明:
{
float height;
float4 flow;
}
RWStructuredBuffer<GridPoint> newWaterState :register(u0);
StructuredBuffer<GridPoint> currentWaterState :register(t0);
可以通过两种方法声明结构化buffer:RWStructuredBuffer<>和StructuredBuffer< >。区别在于前者对应着可读可写的buffer(UAV),后者则是只读的数据(SRV),注意,不同buffer对应的寄存器标识也不一样。声明了HLSL变量以后,就能用普通数组的语法,通过索引访问buffer中的数据了。HLSL中提供GetDimensions()函数,可以返回buffer大小,或者是SBR中的数组元素个数。对于使用UAV绑定的SRB来说,由于指定了D3D11_BUFFER_UAV_FLAG_COUNTER标记,还可以使用IncrementCounter()和DecrementCounter函数管理数据。
-------------------------------------to be continue----------------------------------