Having play around MSFT dx12 samples for sometime and looking through the msdn documents, there are still lots of stuffs confuse me. One of which is the ID3D12CommandAllocator, and ID3D12GraphicsCommandList stuffs.
If (as mentioned in the msdn) the id3d12commandallocator is the memory allocator on gpu command buffer, and id3d12graphicscommandlist is the container object which contains sequence of commands why we need to reset the allocator and build the sequence of commands from scratch again and again every frame(at least MSFT examples does). Especially there are only [backbuffer count] of different commandlist.
I have modified the code for D3D12HelloConstBuffers sample, where I create 3 commandlist object from one allocator(backbuffer count is 3), the only difference between these three commandlist is that the
"m_commandList[frameidx]->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);"
is different since rtvHandle is different from 3 backbuffer.
Then on the OnRender() function I get rid of the allocator reset, PopulateCommandLIst stuffs. Basically I just execute the right commandlist I create earlier based on current backbuffer index.
Here are relevant code snippet
// Fill the command list with all the render commands and dependent state.
void D3D12HelloConstBuffers::PopulateCommandList(int frameidx)
{
// Command list allocators can only be reset when the associated
// command lists have finished execution on the GPU; apps should use
// fences to determine GPU execution progress.
//ThrowIfFailed(m_commandAllocator->Reset());
// However, when ExecuteCommandList() is called on a particular command
// list, that command list can then be reset at any time and must be before
// re-recording.
ThrowIfFailed(m_commandList[frameidx]->Reset(m_commandAllocator.Get(), m_pipelineState.Get()));
// Set necessary state.
m_commandList[frameidx]->SetGraphicsRootSignature(m_rootSignature.Get());
ID3D12DescriptorHeap* ppHeaps[] = { m_cbvHeap.Get() };
m_commandList[frameidx]->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
m_commandList[frameidx]->SetGraphicsRootDescriptorTable(0, m_cbvHeap->GetGPUDescriptorHandleForHeapStart());
m_commandList[frameidx]->RSSetViewports(1, &m_viewport);
m_commandList[frameidx]->RSSetScissorRects(1, &m_scissorRect);
// Indicate that the back buffer will be used as a render target.
m_commandList[frameidx]->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[frameidx].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), frameidx, m_rtvDescriptorSize);
m_commandList[frameidx]->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
// Record commands.
const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
m_commandList[frameidx]->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
m_commandList[frameidx]->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_commandList[frameidx]->IASetVertexBuffers(0, 1, &m_vertexBufferView);
m_commandList[frameidx]->DrawInstanced(3, 1, 0, 0);
// Indicate that the back buffer will now be used to present.
m_commandList[frameidx]->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[frameidx].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
ThrowIfFailed(m_commandList[frameidx]->Close());
}
// Render the scene.
void D3D12HelloConstBuffers::OnRender()
{
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
// Execute the command list.
ID3D12CommandList* ppCommandLists[] = { m_commandList[m_frameIndex].Get() };
m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
// Present the frame.
ThrowIfFailed(m_swapChain->Present(1, 0));
WaitForPreviousFrame();
}
And it turned out it works as the original code.
So my question here is that: what the point of spending all the cpu cycles to recreate the commandlist every frame, or even reset the allocator every frame. And why not to create bunch of commandlists ahead of time, and use the proper one on render function?