实践总结
1 常量缓冲区的使用
DX11创建常量缓冲区后,用设备上下文将其绑定到管线。刷新时用设备上下文更新常量缓冲区。绘制时用设备上下文下绘制命令。
DX12依次创建根签名(绑定到PSO)、描述符堆、常量缓冲区视图(绑定到描述符堆)。刷新时用资源接口更新常量缓冲区。绘制时用命令列表设置根签名、绑定根参数和描述符堆、下绘制命令。
DX11中的设备上下文(ID3D11DeviceContext)被DX12废弃。DX12用PSO(ID3D12PipelineState)代替其进行管线设置,用命令列表(ID3D12GraphicsCommandList)代替其装配管线和下绘制命令,用资源接口(ID3D12Resource)代替其进行资源的map和unmap。
DX12提出了根签名(ID3D12RootSignature),用以将着色器需要用到的数据绑定到对应的寄存器槽上供着色器访问。根签名是根参数数组,根参数(CD3DX12_ROOT_PARAMETER)可以是描述符表、根描述符、根常量。根签名会绑定到PSO。
设常量缓冲区:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| struct ConstantBuffer { DirectX::XMMATRIX world; DirectX::XMMATRIX view; DirectX::XMMATRIX proj; DirectX::XMFLOAT4 color; uint32_t useCustomColor; uint32_t pads[3]; };
cbuffer ConstantBuffer : register(b0) { matrix g_World; matrix g_View; matrix g_Proj; vector g_Color; uint g_UseCustomColor; }
|
DX11做法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| D3D11_BUFFER_DESC cbd; ZeroMemory(&cbd, sizeof(cbd)); cbd.Usage = D3D11_USAGE_DYNAMIC; cbd.ByteWidth = sizeof(ConstantBuffer); cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; cbd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
HR(m_pd3dDevice->CreateBuffer(&cbd, nullptr, m_pConstantBuffer.GetAddressOf()));
m_pd3dImmediateContext->VSSetConstantBuffers(0, 1, m_pConstantBuffer.GetAddressOf()); m_pd3dImmediateContext->PSSetConstantBuffers(0, 1, m_pConstantBuffer.GetAddressOf());
D3D11_MAPPED_SUBRESOURCE mappedData; HR(m_pd3dImmediateContext->Map(m_pConstantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedData)); memcpy_s(mappedData.pData, sizeof(m_CBuffer), &m_CBuffer, sizeof(m_CBuffer)); m_pd3dImmediateContext->Unmap(m_pConstantBuffer.Get(), 0);
m_pd3dImmediateContext->(IndexCount, 1, StartIndexLocation, BaseVertexLocation, 0);
|
DX12做法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| CD3DX12_ROOT_PARAMETER slotRootParameter[1];
CD3DX12_DESCRIPTOR_RANGE cbvTable0; cbvTable0.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0); slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable0);
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1, slotRootParameter, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
ComPtr<ID3DBlob> serializedRootSig = nullptr; ComPtr<ID3DBlob> errorBlob = nullptr; HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1, serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());
ThrowIfFailed(md3dDevice->CreateRootSignature( 0, serializedRootSig->GetBufferPointer(), serializedRootSig->GetBufferSize(), IID_PPV_ARGS(mRootSignature.GetAddressOf())));
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc; cbvHeapDesc.NumDescriptors = numDescriptors; cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; cbvHeapDesc.NodeMask = 0; ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&cbvHeapDesc, IID_PPV_ARGS(&mCbvHeap)));
auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(mCbvHeap->GetCPUDescriptorHandleForHeapStart()); handle.Offset(heapIndex, mCbvSrvUavDescriptorSize);
UINT CBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ConstantBuffer)); D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc; cbvDesc.BufferLocation = CBAddress; cbvDesc.SizeInBytes = CBByteSize; md3dDevice->CreateConstantBufferView(&cbvDesc, handle);
D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePsoDesc; opaquePsoDesc.pRootSignature = mRootSignature.Get();
ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);
mCommandList->SetGraphicsRootSignature(mRootSignature.Get());
auto cbvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(mCbvHeap->GetGPUDescriptorHandleForHeapStart()); cbvHandle.Offset(cbvIndex, mCbvSrvUavDescriptorSize); mCommandList->SetGraphicsRootDescriptorTable(0, cbvHandle);
mCommandList->DrawIndexedInstanced(IndexCount, 1, StartIndexLocation, BaseVertexLocation, 0);
|
2 渲染方式
Direct3D 11支持两种渲染方式:立即渲染(immediate
rendering,利用immediate
context实现)以及延迟渲染(deferred
rendering,利用deferred
context实现)。立即渲染将缓冲区中的命令直接借驱动层发往GPU执行,延迟渲染与命令列表模型相似(但执行命令列表时仍然要依赖immediate
context)。前者延续了Direct3D 11之前一贯的绘制方式,而后者则为Direct3D
11中新添加的绘制方式。
Direct3D
12取消了立即渲染方式,完全采用“命令列表->命令队列”模型,使多个命令列表同时记录命令,借此充分发挥多核心处理器的性能。
3 资源转换
为了实现常见的渲染效果,经常会通过GPU对某个资源按顺序进行先写后读这两种操作。当GPU的写操作还没有完成抑或甚至还没有开始,却开始读取资源,便会导致资源冒险(resource
hazard)。
为此,Direct3D专门针对资源设计了一组相关状态。资源在创建伊始会处于默认状态,该状态将一直持续到应用程序通过Direct3D将其转换(transition)为另一种状态为止。这就使GPU能够针对资源状态转换与防止资源冒险作出适当的行为。例如,如果要对某个资源(比如纹理)执行写操作时,需要将它的状态转换为渲染目标状态;而要对该纹理进行读操作时,再把它的状态变为着色器资源状态。根据Direct3D给出的转换信息,GPU就可以采取适当的措施避免资源冒险的发生。譬如,在读取某个资源之前,它会等待所有与之相关的写操作执行完毕。
Direct3D
11中由驱动管理资源转换。一个自动跟踪状态转换的系统会强行增加程序的额外开销。
Direct3D 12中,通过命令列表设置转换资源屏障(transition resource
barrier,
D3D12_RESOURCE_BARRIER
)数组,即可指定资源的转换。