首先得修正一个错误, 前一节说到了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组合, 可以说是最简单的了. 在屏幕上画一个渐变的矩形(全窗口)
说到着色器, 那就要编译了, 我在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的话, 超过哦哦就像这个样子:
不过目前还有问题, 就是”闪狗眼”, 不知道是不是本机原因, 还是D3D12未完善, 还是其他什么原因:
不能通过ID3D12Device::QueryInterface, 获取IDXGIDevice1, 尝试使用D3D11on12CreateDevice(官方文档目前未收录),创建一个D3D11的设备, 从中获取IDXGIDevice1, 这倒是成功了, 不过再调用IDXGIDevice1::SetMaximumFrameLatency居然破天荒地返回E_NOTIMP, 看到到现在为止, D3D12还没有和其他组件对接好, 可惜了….
点击这里