利用 Direct3D 绘制几何体—9.流水线状态对象

到目前为止展示过编写输入布局描述、创建顶点着色器和像素着色器,以及配置光栅器状态组这 3 个步骤。接下来讲如何将这些对象绑定到图形流水线上,用以实际绘制图形。大多数控制图形流水线状态的对象被统称为流水线状态对象 (Pipeline State Object,PSO),用 ID3D12PipelineState 接口来表示。要创建 PSO,我们首先要填写一份描述其细节的 D3D12_GRAPHICS_PIPELINE_STATE_DESC 结构体实例。

cpp 复制代码
typedef struct D3D12_GRAPHICS_PIPELINE_STATE_DESC
{
  ID3D12RootSignature *pRootSignature;
  D3D12_SHADER_BYTECODE VS;
  D3D12_SHADER_BYTECODE PS;
  D3D12_SHADER_BYTECODE DS;
  D3D12_SHADER_BYTECODE HS;
  D3D12_SHADER_BYTECODE GS;
  D3D12_STREAM_OUTPUT_DESC StreamOutput;
  D3D12_BLEND_DESC BlendState;
  UINT SampleMask;
  D3D12_RASTERIZER_DESC RasterizerState;
  D3D12_DEPTH_STENCIL_DESC DepthStencilState;
  D3D12_INPUT_LAYOUT_DESC InputLayout;
  D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimitiveTopologyType;
  UINT NumRenderTargets;
  DXGI_FORMAT RTVFormats[8];
  DXGI_FORMAT DSVFormat;
  DXGI_SAMPLE_DESC SampleDesc;
} D3D12_GRAPHICS_PIPELINE_STATE_DESC;
  1. pRootSignature:指向一个与此 PSO 相绑定的根签名的指针。该根签名一定要与此 PSO 指定的着色器相兼容。

  2. VS:待绑定的顶点着色器。此成员由结构体 D3D12_SHADER_BYTECODE 表示,这个结构体存有指向已编译好的字节码数据的指针,以及该字节码数据所占的字节大小。

cpp 复制代码
 typedef struct D3D12_SHADER_BYTECODE {
   const void *pShaderBytecode;
   SIZE_T   BytecodeLength;
  } D3D12_SHADER_BYTECODE;
  1. PS:待绑定的像素着色器。

  2. DS:待绑定的域着色器(我们将在后续章节中讲解此类型的着色器)。

  3. HS:待绑定的外壳着色器(我们将在后续章节中讲解此类型的着色器)。

  4. GS:待绑定的几何着色器(我们将在后续章节中讲解此类型的着色器)。

  5. StreamOutput:用于实现一种称作流输出(stream-out)的高级技术。目前我们仅将此字段清零。

  6. BlendState:指定混合(blending)操作所用的混合状态。我们将在后续章节中讨论此状态组,目前仅将此成员指定为默认的 CD3DX12_BLEND_DESC(D3D12_DEFAULT)。

  7. SampleMask:多重采样最多可采集 32 个样本。借此参数的 32 位整数值,即可设置每个采样点的采集情况(采集或禁止采集)。例如,若禁用了第 5 位(将第 5 位设置为 0),则将不会对第 5 个样本进行采样。当然,要禁止采集第 5 个样本的前提是,所用的多重采样至少要有 5 个样本。假如一个应用程序仅使用了单采样(single sampling),那么只能针对该参数的第 1 位进行配置。一般来说,使用的都是默认值 0xffffffff,即表示对所有的采样点都进行采样。

  8. RasterizerState:指定用来配置光栅器的光栅化状态。

  9. DepthStencilState:指定用于配置深度/模板测试的深度/模板状态。我们将在后续章节中对此状态进行讨论,目前只把它设为默认的 CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT)。

  10. InputLayout:输入布局描述,此结构体中有两个成员:一个由 D3D12_INPUT_ELEMENT_DESC 元素构成的数组,以及一个表示此数组中元素数量的无符号整数。

cpp 复制代码
 typedef struct D3D12_INPUT_LAYOUT_DESC
  {
    const D3D12_INPUT_ELEMENT_DESC *pInputElementDescs;
    UINT NumElements;
  } D3D12_INPUT_LAYOUT_DESC;
  1. PrimitiveTopologyType:指定图元的拓扑类型。
cpp 复制代码
typedef enum D3D12_PRIMITIVE_TOPOLOGY_TYPE { 
   D3D12_PRIMITIVE_TOPOLOGY_TYPE_UNDEFINED = 0,
   D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT   = 1,
   D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE    = 2,
   D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE  = 3,
   D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH   = 4
  } D3D12_PRIMITIVE_TOPOLOGY_TYPE
  1. NumRenderTargets:同时所用的渲染目标数量(即 RTVFormats 数组中渲染目标格式的数量)。

  2. RTVFormats:渲染目标的格式。利用该数组实现向多渲染目标同时进行写操作。使用此 PSO 的渲染目标的格式设定应当与此参数相匹配。

  3. DSVFormat:深度/模板缓冲区的格式。使用此 PSO 的深度/模板缓冲区的格式设定应当与此参数相匹配。

  4. SampleDesc:描述多重采样对每个像素采样的数量及其质量级别。此参数应与渲染目标的对应设置相匹配。

在 D3D12_GRAPHICS_PIPELINE_STATE_DESC 实例填写完毕后,我们即可用 ID3D12Device::CreateGraphicsPipelineState方法来创建 ID3D12PipelineState 对象。

cpp 复制代码
// BoxApp.cpp 58行
ComPtr mRootSignature;
std::vector mInputLayout;
ComPtr mvsByteCode;
ComPtr mpsByteCode;
...
// BoxApp.cpp 436行 BuildPSO()
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;
ZeroMemory(&psoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
psoDesc.InputLayout = { mInputLayout.data(), (UINT)mInputLayout.size() };
psoDesc.pRootSignature = mRootSignature.Get();
psoDesc.VS = 
{ 
  reinterpret_cast(mvsByteCode->GetBufferPointer()),
  mvsByteCode->GetBufferSize() 
};
psoDesc.PS = 
{ 
  reinterpret_cast(mpsByteCode->GetBufferPointer()), 
  mpsByteCode->GetBufferSize() 
};
psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = mBackBufferFormat;
psoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
psoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
psoDesc.DSVFormat = mDepthStencilFormat;

ComPtr mPSO;
md3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&mPSO)));

ID3D12PipelineState 对象集合了大量的流水线状态信息。为了保证性能,我们将所有这些对象都集总在一起,一并送至渲染流水线。通过这样的一个集合,Direct3D 便可以确定所有的状态是否彼此兼容,而驱动程序则能够据此而提前生成硬件本地指令及其状态。

注意:由于 PSO 的验证和创建操作过于耗时,所以应在初始化期间就生成 PSO。除非有特别的需求,例如,在运行时创建 PSO 伊始就要当即对它进行第一次引用的这种情况。随后,我们就可将它存于如散列表(哈希表)这样的集合里,以便在后续使用时快速获取。

并非所有的渲染状态都封装于 PSO 内,如视口(viewport)和裁剪矩形(scissor rectangle)等属性就独立于 PSO。由于将这些状态的设置与其他的流水线状态分隔开来会更有效,所以把它们强行集中在 PSO 内也并不会为之增添任何优势。

Direct3D 实质上就是一种状态机(state machine),里面的事物会保持它们各自的状态,直到我们将其改变。如果我们以不同的 PSO 去绘制不同物体,则需要像下面那样来组织代码:

cpp 复制代码
// 重置命令列表并指定初始 PSO
mCommandList->Reset(mDirectCmdListAlloc.Get(), mPSO1.Get());
/* ......使用 PSO 1绘制物体...... */

// 改变 PSO
mCommandList->SetPipelineState(mPSO2.Get());
/* ......使用 PSO 2绘制物体...... */

// 改变 PSO
mCommandList->SetPipelineState(mPSO3.Get());
/* ......使用 PSO 3绘制物体...... */

换句话说,如果把一个 PSO 与命令列表相绑定,那么,在我们设置另一个 PSO 或重置命令列表之前,会一直沿用当前的 PSO 绘制物体。

考虑到程序的性能问题,我们应当尽可能减少改变 PSO 状态的次数。为此,若能以一个 PSO 绘制出所有的物体,绝不用第二个 PSO。切记,不要在每次绘制调用时都修改 PSO。

相关推荐
红龙创客8 分钟前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin11 分钟前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos2 小时前
c++---------数据类型
java·jvm·c++
数据的世界012 小时前
.NET开发人员学习书籍推荐
学习·.net
四口鲸鱼爱吃盐2 小时前
CVPR2024 | 通过集成渐近正态分布学习实现强可迁移对抗攻击
学习
十年一梦实验室2 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0012 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我582 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
fpcc3 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存
呆萌很3 小时前
C++ 集合 list 使用
c++