(转载请注明出处)
首先得修正一个错误,前一节说到了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
- 一个指向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
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,"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还没有和其他组件对接好,可惜了….