I'm beginning learning Direct3D 12 and having difficulty in understanding CPU-GPU synchronization. As far as I understand, fence (ID3D12Fence) is no more than a UINT64(unsigned long long) value used as counter. But its methods confuse me. The below is a part of source code from D3D12 example.(https://github.com/d3dcoder/d3d12book)
void D3DApp::FlushCommandQueue()
{
// Advance the fence value to mark commands up to this fence point.
mCurrentFence++;
// Add an instruction to the command queue to set a new fence point. Because we
// are on the GPU timeline, the new fence point won't be set until the GPU finishes
// processing all the commands prior to this Signal().
ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence));
// Wait until the GPU has completed commands up to this fence point.
if(mFence->GetCompletedValue() < mCurrentFence)
{
HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
// Fire event when GPU hits current fence.
ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence, eventHandle));
// Wait until the GPU hits current fence event is fired.
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}
}
As far as I understand, this part is trying to 'Flush' the command queue, which is basically making CPU wait for GPU until it reaches to given 'Fence value' so that CPU and GPU have identical fence value.
Q. If this Signal() is a function that lets GPU to update the fence value inside given ID3D12Fence, why is that mCurrentFence value needed?
According to Microsoft Doc, it says "Updates a fence to a specified value." What specified value? What I need is "Get Last Completed Command List Value", not set or specify. What is this specified value for?
To me, it seems it has to be like
// Suppose mCurrentFence is 1 after submitting 1 command list (Index 0), and the thread reached to here for the FIRST time
ThrowIfFailed(mCommandQueue->Signal(mFence.Get()));
// At this point Fence value inside mFence is updated
if (m_Fence->GetCompletedValue() < mCurrentFence)
{
...
}
if m_Fence->GetCompletedValue() is 0,
if (0 < 1)
GPU hasn't operated the command list (Index 0), then CPU has to wait until GPU follows up. Then it makes sense calling SetEventOnCompletion, WaitForSingleObject, etc.
if (1 < 1)
GPU has completed the command list (Index 0), so CPU does not need to wait.
Increment mCurrentFence somewhere where command list is executed.
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
mCurrentFence++;