D3D12渲染技术之根签名

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jxw167/article/details/82705103

我们在前面的博客中介绍了根签名, 根签名定义在发出绘制调用之前需要将哪些资源绑定到管道以及这些资源如何映射到着色器输入寄存器, 需要绑定哪些资源取决于当前着色器程序所期望的资源, 创建PSO时,将验证根签名和着色器程序组合。

根参数

回想一下,根签名是由一组根参数定义的, 到目前为止,我们只创建了一个存储描述符表的根参数, 但是,root参数实际上可以是以下三种类型之一:
1、描述符表:期望描述符表引用堆中的连续范围,该范围标识要绑定的资源。
2、根描述符(内联描述符):期望直接设置的描述符,用于标识要绑定的资源;描述符不需要在堆中。 只有CBV到常量缓冲区,SRV / UAV到缓冲区可以绑定为根描述符, 特别是,这意味着SRV到纹理不能绑定为根描述符。
3、根常量:直接绑定的32位常量值列表。
对于性能,可以将64个DWORD限制在根签名中, 这三种根参数具有以下成本:
描述符表:1 DWORD
根描述符:2个DWORD
根常量:每32位常量1个DWORD
我们可以创建一个任意的根签名,只要我们不超过64个DWORD限制, 根常量非常方便,但它们的成本很快就会增加。 例如,如果我们需要的唯一常量数据是世界视图投影矩阵,我们可以使用16个根常量来存储它,这将使我们不需要打扰常量缓冲区和CBV堆。 但是,这会占我们根签名预算的四分之一。 使用根描述符只能是两个DWORD。 随着我们的应用程序变得越来越复杂,我们的常量缓冲区数据将变得越来越大,并且我们不太可能只使用根常量。 在实际应用程序中,可能会使用所有三种类型的根参数的组合。

在代码中,通过填写CD3DX12_ROOT_PARAMETER结构来描述根参数, CD3DX12_ROOT_PARAMETER扩展了D3D12_ROOT_PARAMETER并添加了一些辅助初始化函数。

typedef struct D3D12_ROOT_PARAMETER
{
  D3D12_ROOT_PARAMETER_TYPE ParameterType;
  union 
  {
    D3D12_ROOT_DESCRIPTOR_TABLE DescriptorTable;
    D3D12_ROOT_CONSTANTS Constants;
    D3D12_ROOT_DESCRIPTOR Descriptor;
  };
  D3D12_SHADER_VISIBILITY ShaderVisibility;
}D3D12_ROOT_PARAMETER;

在代码中,通过填写CD3DX12_ROOT_PARAMETER结构来描述根参数, 正如我们在CD3DX代码中看到的那样,CD3DX12_ROOT_PARAMETER扩展了D3D12_ROOT_PARAMETER并添加了一些辅助初始化函数。

typedef struct D3D12_ROOT_PARAMETER
{
  D3D12_ROOT_PARAMETER_TYPE ParameterType;
  union 
  {
    D3D12_ROOT_DESCRIPTOR_TABLE DescriptorTable;
    D3D12_ROOT_CONSTANTS Constants;
    D3D12_ROOT_DESCRIPTOR Descriptor;
  };
  D3D12_SHADER_VISIBILITY ShaderVisibility;
}D3D12_ROOT_PARAMETER;

参数的具体含义在这里就不一一说明了,大家可以查看DX帮助文档。。。。。。

描述表

通过填写D3D12_ROOT_PARAMETER的DescriptorTable成员来进一步定义描述符表根参数。

typedef struct D3D12_ROOT_DESCRIPTOR_TABLE
{
  UINT NumDescriptorRanges;
  const D3D12_DESCRIPTOR_RANGE *pDescriptorRanges;
}    D3D12_ROOT_DESCRIPTOR_TABLE;

这只是指定D3D12_DESCRIPTOR_RANGE的数组和数组中的范围数。D3D12_DESCRIPTOR_RANGE结构的定义如下:

typedef struct D3D12_DESCRIPTOR_RANGE
{
D3D12_DESCRIPTOR_RANGE_TYPE RangeType;
  UINT NumDescriptors;
  UINT BaseShaderRegister;
  UINT RegisterSpace;
  UINT OffsetInDescriptorsFromTableStart;
}    D3D12_DESCRIPTOR_RANGE;

初始化为描述符表的槽参数采用D3D12_DESCRIPTOR_RANGE实例的数组,因为我们可以在一个表中混合各种类型的描述符, 假设我们按顺序通过以下三个范围定义了六个描述符的表:两个CBV,三个SRV和一个UAV, 这个表的定义如下:

// Create a table with 2 CBVs, 3 SRVs and 1 UAV.
CD3DX12_DESCRIPTOR_RANGE descRange[3];
descRange[0].Init(
  D3D12_DESCRIPTOR_RANGE_TYPE_CBV, // descriptor type
  2, // descriptor count
  0, // base shader register arguments are bound to for this root 
     // parameter
  0, // register space
  0);// offset from start of table
descRange[1].Init(
  D3D12_DESCRIPTOR_RANGE_TYPE_SRV, // descriptor type
  3, // descriptor count
  0, // base shader register arguments are bound to for this root 
     // parameter
  0, // register space
  2);// offset from start of table
descRange[2].Init(
  D3D12_DESCRIPTOR_RANGE_TYPE_UAV, // descriptor type
  1, // descriptor count
  0, // base shader register arguments are bound to for this root 
  // parameter
  0, // register space
  5);// offset from start of table
 
slotRootParameter[0].InitAsDescriptorTable(
  3, descRange, D3D12_SHADER_VISIBILITY_ALL);

有一个继承自D3D12_DESCRIPTOR_RANGE的CD3DX12_DESCRIPTOR_RANGE变体,我们使用以下初始化函数:

void CD3DX12_DESCRIPTOR_RANGE::Init(
    D3D12_DESCRIPTOR_RANGE_TYPE rangeType,
    UINT numDescriptors,
    UINT baseShaderRegister,
    UINT registerSpace = 0,
    UINT offsetInDescriptorsFromTableStart =
    D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND);

该表涵盖六个描述符,并且应用程序期望在描述符堆中绑定连续范围的描述符,其包括两个CBV,随后是三个SRV,后跟一个UAV。 我们看到所有范围类型都从寄存器0开始,但没有“重叠”冲突,因为CBV,SRV和UAV都绑定到不同的寄存器类型,每个寄存器类型从寄存器0开始。

我们可以通过指定D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND让Direct3D为我们计算OffsetInDescriptorsFromTableStart值;指示Direct3D使用表中先前的范围描述符计数来计算偏移量。 请注意,CD3DX12_DESCRIPTOR_RANGE :: Init方法默认为寄存器空间为0,OffsetInDescriptorsFromTableStart默认为D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND。

根描述符

通过填写D3D12_ROOT_PARAMETER的描述符成员来进一步定义根描述符根参数。

typedef struct D3D12_ROOT_DESCRIPTOR
{
  UINT ShaderRegister;
  UINT RegisterSpace;
}D3D12_ROOT_DESCRIPTOR;

与需要我们在描述符堆中设置描述符句柄的描述符表不同,要设置根描述符,我们只需直接绑定资源的虚拟地址。

UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof
 (ObjectConstants));
 
D3D12_GPU_VIRTUAL_ADDRESS objCBAddress =  objectCB->GetGPUVirtualAddress();
 
// Offset to the constants for this object in the buffer.
objCBAddress += ri->ObjCBIndex*objCBByteSize;
 
cmdList->SetGraphicsRootConstantBufferView(
   0, // root parameter index
   objCBAddress);

根常量

通过填写D3D12_ROOT_PARAMETER的常量成员来进一步定义描述符表根参数。

  typedef struct D3D12_ROOT_CONSTANTS
  {
    UINT ShaderRegister;
    UINT RegisterSpace;
    UINT Num32BitValues;
   }    D3D12_ROOT_CONSTANTS;

设置根常量仍然从着色器的角度将数据映射到常量缓冲区。 以下示例说明:

// Application code: Root signature definition.
CD3DX12_ROOT_PARAMETER slotRootParameter[1];
slotRootParameter[0].InitAsConstants(12, 0);
 
// A root signature is an array of root parameters.
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1, slotRootParameter,
  0, nullptr,
  D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
 
// Application code: to set the constants to register b0.
auto weights = CalcGaussWeights(2.5f);
int blurRadius = (int)weights.size() / 2;
 
cmdList->SetGraphicsRoot32BitConstants(0, 1, &blurRadius, 0);
cmdList->SetGraphicsRoot32BitConstants(0, (UINT)weights.size(), weights.data(), 1);
 
// HLSL code.
cbuffer cbSettings : register(b0)
{
   // We cannot have an array entry in a constant buffer that gets
   // mapped onto root constants, so list each element. 
   
   int gBlurRadius;
 
   // Support up to 11 blur weights.
   float w0;
   float w1;
   float w2;
   float w3;
   float w4;
   float w5;
   float w6;
   float w7;
    float w8;
   float w9;
   float w10;
};

ID3D12GraphicsCommandList :: SetGraphicsRoot32BitConstants方法具有以下原型:

void ID3D12GraphicsCommandList::SetGraphicsRoot32BitConstants( 
  UINT RootParameterIndex,
  UINT Num32BitValuesToSet,
  const void *pSrcData,
  UINT DestOffsetIn32BitValues);

与根描述符一样,设置根常量可以绕过描述符堆的需要,下面通过案例的方式给读者展示,考虑一个需要以下资源的着色器:

Texture2D gDiffuseMap : register(t0);
cbuffer cbPerObject : register(b0)
{
  float4x4 gWorld;
  float4x4 gTexTransform;
};
 
cbuffer cbPass : register(b1)
{
  float4x4 gView;
  float4x4 gInvView;
  float4x4 gProj;
  float4x4 gInvProj;
  float4x4 gViewProj;
  float4x4 gInvViewProj;
  float3 gEyePosW;
  float cbPerObjectPad1;
  float2 gRenderTargetSize;
  float2 gInvRenderTargetSize;
  float gNearZ;
  float gFarZ;
  float gTotalTime;
  float gDeltaTime;
  float4 gAmbientLight;
  Light gLights[MaxLights];
};
 
cbuffer cbMaterial : register(b2)
{
  float4  gDiffuseAlbedo;
  float3  gFresnelR0;
   float  gRoughness;
  float4x4 gMatTransform;
};

此着色器的根签名将描述如下:

CD3DX12_DESCRIPTOR_RANGE texTable;
  texTable.Init(
  D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 
  1, // number of descriptors
  0); // register t0
 
  // Root parameter can be a table, root descriptor or root constants.
  CD3DX12_ROOT_PARAMETER slotRootParameter[4];
 
  // Perfomance TIP: Order from most frequent to least frequent.
  slotRootParameter[0].InitAsDescriptorTable(1, 
    &texTable, D3D12_SHADER_VISIBILITY_PIXEL);
  slotRootParameter[1].InitAsConstantBufferView(0); // register b0
  slotRootParameter[2].InitAsConstantBufferView(1); // register b1
  slotRootParameter[3].InitAsConstantBufferView(2); // register b2
  // A root signature is an array of root parameters.
  CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(4, slotRootParameter,
    0, nullptr,
    D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

根参数版本控制

根参数指的是我们传递给根参数的实际值, 考虑以下代码,我们在绘制调用之间更改根参数(在本例中仅为描述符表):

for(size_t i = 0; i < mRitems.size(); ++i)
{
  const auto& ri = mRitems[i];
 
  …
 
  // Offset to the CBV for this frame and this render item.
  int cbvOffset = mCurrFrameResourceIndex*(int)mRitems.size();
  cbvOffset += ri.CbIndex;
  cbvHandle.Offset(cbvOffset, mCbvSrvDescriptorSize);
  // Identify descriptors to use for this draw call.
  cmdList->SetGraphicsRootDescriptorTable(0, cbvHandle);
 
  cmdList->DrawIndexedInstanced(
    ri.IndexCount, 1, 
    ri.StartIndexLocation, 
    ri.BaseVertexLocation, 0);
}

每次绘制调用都将在绘制调用时使用当前设置的根参数状态执行, 因为硬件会自动保存每个绘制调用的根参数的当前状态的快照。
请注意,根签名可以提供比着色器使用的字段更多的字段, 例如,如果根签名指定根参数2中的根CBV,但着色器不使用该常量缓冲区,则只要根签名确实指定了着色器使用的所有资源,此组合就有效。

为了提高性能,我们应该保持根签名小, 其中一个原因是每个绘制调用自动版本化根参数, 根签名越大,根参数的这些快照就越大。 此外,SDK文档建议根参数应从最常更改到最不频繁更改, Direct3D 12文档还建议尽可能避免切换根签名,因此最好在创建的许多PSO上共享相同的根签名, 特别是,具有与多个着色器程序一起工作的“超级”根签名,即使并非所有着色器都使用根签名定义的所有参数。

阅读更多

扫码向博主提问

海洋_

博客专家

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • 3D引擎架构
  • 服务器架构
  • GPU渲染
  • 客户端架构
  • 引擎优化
去开通我的Chat快问
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页