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:
Direct3D 12 尝鲜(三): Pipeline State Object_第1张图片

在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, 常见的几种组合够用了:

Direct3D 12 尝鲜(三): Pipeline State Object_第2张图片

可以使用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

  • 一个指向ID3D12RootSignature的指针, 就是前面创建的

VS

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

PS

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

DS HS GS

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

StreamOutput

  • 流输出, 这里不需要, 0即可

BlendState

  • 混合状态, 可以使用D3D12提供的helper用默认的即可: CD3D12_BLEND_DESC(D3D12_DEFAULT)

SampleMask

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

RasterizerState

  • 光栅化状态, 使用Helper用默认的即可: CD3D12_RASTERIZER_DESC(D3D12_DEFAULT)

DepthStencilState

  • 深度/模板状态, 这里不需要, 0即可

InputLayout

  • 输入布局, 这里不需要, 0即可

IndexBufferProperties

  • 索引缓存属性, 这里不需要, D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED 即可

PrimitiveTopologyType

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

NumRenderTargets

  • 目标呈现器数量, 这里就一个

RTVFormats

  • RTV输出像素格式, 我们只有一个RTV, 第一个设为RGBA, 其他设为0

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, nullptr, 
        "VSMain", "vs_5_0", flag, 0,
        &vs, &info
        );
}
// 编译PS
if (SUCCEEDED(hr)) {
    ::SafeRelease(info);
    hr = ::D3DCompileFromFile(
        L"shader2in1.hlsl", nullptr, nullptr, 
        "PSMain", "ps_5_0", flag, 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, 0 },
        // GS
        { nullptr, 0 },
        // SO
        { nullptr, 0, nullptr, 0, 0 },
        // 合成
        CD3D12_BLEND_DESC(D3D12_DEFAULT),
        // 采样掩码
        UINT32_MAX,
        // 光栅化
        CD3D12_RASTERIZER_DESC(D3D12_DEFAULT),
        // 深度/模板
        { 0 },
        // 输入布局
        { nullptr, 0 },
        // 索引缓存
        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 },
        // 节点掩码
        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, 0, (LONG)m_uBufferWidth, (LONG)m_uBufferHeight };
    m_pCmdDraw->RSSetScissorRects(1, &scissor);
    m_pCmdDraw->DrawInstanced(4, 1, 0, 0);
    this->SetResourceBarrier(m_pCmdDraw, m_pTargetBuffer, D3D12_RESOURCE_USAGE_RENDER_TARGET, D3D12_RESOURCE_USAGE_PRESENT);
    hr = m_pCmdDraw->Close();
}

好了,开启DirectComposition的话, 超过哦哦就像这个样子:
Direct3D 12 尝鲜(三): Pipeline State Object_第3张图片

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


代码下载地址:

点击这里

你可能感兴趣的:(C++,Direct3D)