Direct3D 12 尝鲜(三): Pipeline State Object

(转载请注明出处)

首先得修正一个错误,前一节说到了Fence中用到帧编号,这个帧编号应该(?)要大于0,所以初始化为1就好了,否则(这一节中)第二次渲染会出一点小错误.(╯‵□′)╯︵┴─┴

第一节有提到资源绑定,一般的descriptor,还有就是一个特殊的Root Signatures,微软提到

The root signature defines what resources are bound to the graphics pipeline. A root signature is configured by the app and links command lists to the resources the shaders require. Currently,there is one graphics and one compute root signature per app.

就是说这个Root Signature是被绑定到图像渲染管线上的资源,我们可以利用D3D12SerializeRootSignature先序列化一个Root Signature,将创建的ID3DBlob(其实就是ID3D10Blob的马甲) 传给ID3D12Device::CreateRootSignature用来创建RootSignature.
不像Descriptor那样可以创建连续的多个,只能单个创建. 说完Root Signature就进入这次的主题,Pipeline State Object(PSO),这也是与D3D11的重大差别之一,在官方的博客中提到,在D3D11中,渲染管线每个阶段都可以独立控制,很方便,但是也造成了性能的浪费,驱动需要提供实时Get/Set:

在D3D12中,通过将这些糅合成一个不可再变的PSO,不过有提到:

Which PSO is in use can still be changed dynamically,but to do so the hardware only needs to copy the minimal amount of pre-computed state directly to the hardware registers,rather than computing the hardware state on the fly.

不过就认为不可变也不为过,有点类似于以前的固定管线,不过这个可以多创建几个PSO,常见的几种组合够用了:

可以使用ID3D12Device::CreateGraphicsPipelineState来创建一个PSO,最后两个参数REFIID,void**什么的就不说了,说一下第一个参数D3D12_GRAPHICS_PIPELINE_STATE_DESC,(╯‵□′)╯︵┴─┴,这个东西好大,在x86下,都有500+字节:

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_INDEX_BUFFER_PROPERTIES IndexBufferProperties;
  D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimitivetopologyType;
  UINT                          Numrendertargets;
  dxgi_FORMAT                   RTVFormats[8];
  dxgi_FORMAT                   DSVFormat;
  dxgi_SAMPLE_DESC              SampleDesc;
  UINT                          NodeMask;
  D3D12_CACHED_PIPELINE_STATE   CachedPSO;
} D3D12_GRAPHICS_PIPELINE_STATE_DESC;

我们这里创建一个简单的PSO即可,就是VS-PS组合,可以说是最简单的了. 在屏幕上画一个渐变的矩形(全窗口)

pRootSignature

VS

  • 顶点着色器字节码的地址和长度

PS

  • 像素着色器字节码的地址和长度

DS HS GS

  • 域着色器、壳着色器和几何着色器的字节码,这里不需要,0即可

StreamOutput

BlendState

SampleMask

  • 采样掩码,对于我们这样的垃圾显卡,1就足够了,不过设为0xFFFFFFFF以适应所有采样,有特殊要求可以部分位设为0

RasterizerState

Depthstencilstate

  • 深度/模板状态,0即可

InputLayout

  • 输入布局,0即可

IndexBufferProperties

PrimitivetopologyType

  • 原始拓扑类型,这里使用三角形即可

Numrendertargets

RTVFormats

DSVFormat

  • DSV格式,这里不需要

SampleDesc

  • 多重采样质量,我们的软件渲染还是设为(1,0)吧

NodeMask

  • 节点掩码,现在不需要,设为0

CachedPSO

  • 已缓存的PSO,应该就是类似于C++的replacement new,这里是第一次创建,0 即可.

说到着色器,那就要编译了,我在D2D特效中一节说到了编译方式,这个算是”离线编译”了,还有就是利用函数即时编译,这里使用的是D3DCompileFromFile函数,还有为了方便,将PS和VS写在了一起

struct VSOut {
    float4 pos              : SV_POSITION;
    float2 texcoord         : TEXCOORD;
};

VSOut VSMain(uint vertexID : SV_VERTEXID) {
    VSOut output;
    output.texcoord = float2((vertexID << 1) & 2,vertexID & 2);
    output.pos = float4(output.texcoord * float2(2,-2) + float2(-1,1),0,1);
    return output;
}

float4 PSMain(VSOut vsOut) : SV_TARGET {
    return float4(0,vsOut.texcoord.y,vsOut.texcoord.x,vsOut.texcoord.y * 0.5);
}

这就是我的创建代码:

ID3DBlob *sig = nullptr,*info = nullptr;
ID3DBlob *ps = nullptr,*vs = nullptr;
D3D12_ROOT_SIGNATURE rootSigDesc = D3D12_ROOT_SIGNATURE();
// 先序列化RootSignature
if (SUCCEEDED(hr = ::D3D12SerializeRootSignature(&rootSigDesc,D3D_ROOT_SIGNATURE_V1,&sig,&info))) {
    // 再创建RootSignature
    hr = m_pd3dDevice->CreateRootSignature(
        0,sig->GetBufferPointer(),sig->GetBufferSize(),IID_ID3D12RootSignature,reinterpret_cast<void**>(&m_prsPipeline)
        );
}
UINT flag = 0;
#if _DEBUG
flag |= D3DCOMPILE_DEBUG;
#endif
// 编译VS
if (SUCCEEDED(hr)) {
    ::SafeRelease(info);
    hr = ::D3DCompileFromFile(
        L"shader2in1.hlsl",nullptr,"VSMain","vs_5_0",flag,&vs,&info
        );
}
// 编译PS
if (SUCCEEDED(hr)) {
    ::SafeRelease(info);
    hr = ::D3DCompileFromFile(
        L"shader2in1.hlsl","PSMain","ps_5_0",&ps,&info
        );
}
// 创建PSO
if (SUCCEEDED(hr)) {
    // 配置PSO
    D3D12_GRAPHICS_PIPELINE_STATE_DESC desc = {
        // Root Signature
        m_prsPipeline,// VS
        { vs->GetBufferPointer(),vs->GetBufferSize() },// PS
        { ps->GetBufferPointer(),ps->GetBufferSize() },// DS
        { nullptr,0 },// HS
        { nullptr,// GS
        { nullptr,// SO
        { nullptr,// 合成
        CD3D12_BLEND_DESC(D3D12_DEFAULT),// 采样掩码
        UINT32_MAX,// 光栅化
        CD3D12_RASTERIZER_DESC(D3D12_DEFAULT),// 深度/模板
        { 0 },// 输入布局
        { nullptr,// 索引缓存
        D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_disABLED,// 拓扑类型设置为三角
        D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,// 1个RTV
        1,// RGBA
        { dxgi_FORMAT_R8G8B8A8_UnorM,dxgi_FORMAT_UNKNowN },// DSV
        dxgi_FORMAT_UNKNowN,// 采样
        { 1,// 节点掩码
        0,// 缓存管线
        { nullptr,0 }
    };
    // 创建PSO
    hr = m_pd3dDevice->CreateGraphicsPipelinestate(
        &desc,IID_ID3D12Pipelinestate,reinterpret_cast<void**>(&m_pPipelinestate)
        );

}
::SafeRelease(sig);
::SafeRelease(info);
::SafeRelease(ps);
::SafeRelease(vs);

好了,为了演示命令列表,这次特地创建了2个,一个负责清除,一个负责刻画,前面的命令就不说了,说一下后面的:

因为我们在输入布局(inputlayout)中没有设置,所以直接使用ID3D12GraphicsCommandList::DrawInstanced渲染,不过还得绑定裁剪矩形,还得设定RTV,现在又能最多输出到8个RT,设置PSO,所以代码长这个样子:

// 创建刻画命令
if(SUCCEEDED(hr)) {
    this->SetResourceBarrier(m_pCmdDraw,m_pTargetBuffer,D3D12_RESOURCE_USAGE_PRESENT,D3D12_RESOURCE_USAGE_RENDER_TARGET);
    auto rtv = m_pRTVDescriptor->GetcpuDescriptorHandleForHeapStart();
    m_pCmdDraw->RSSetViewports(1,&view);
    m_pCmdDraw->Setrendertargets(&rtv,true,1,nullptr);
    m_pCmdDraw->SetGraphicsRootSignature(m_prsPipeline);
    m_pCmdDraw->SetPipelinestate(m_pPipelinestate);
    m_pCmdDraw->IASetPrimitivetopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
    D3D12_RECT scissor = { 0,(LONG)m_uBufferWidth,(LONG)m_uBufferHeight };
    m_pCmdDraw->RSSetScissorRects(1,&scissor);
    m_pCmdDraw->DrawInstanced(4,0);
    this->SetResourceBarrier(m_pCmdDraw,D3D12_RESOURCE_USAGE_RENDER_TARGET,D3D12_RESOURCE_USAGE_PRESENT);
    hr = m_pCmdDraw->Close();
}

好了,开启DirectComposition的话,超过哦哦就像这个样子:

不过目前还有问题,就是”闪狗眼”,不知道是不是本机原因,还是D3D12未完善,还是其他什么原因:
不能通过ID3D12Device::QueryInterface,获取IDXGIDevice1,尝试使用D3D11on12CreateDevice(官方文档目前未收录),创建一个D3D11的设备,从中获取IDXGIDevice1,这倒是成功了,不过再调用IDXGIDevice1::SetMaximumFrameLatency居然破天荒地返回E_NOTIMP,看到到现在为止,D3D12还没有和其他组件对接好,可惜了….

代码下载地址:

点击这里

相关文章

迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图...
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,...
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定...
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个...
命令模式(Command)命令模式(Command)[Action/Transactio...
生成器模式(Builder)生成器模式(Builder)意图:将一个对...