Jump to content
  • Advertisement
Sign in to follow this  
hiya83

[D3D12] Command Queue Fence Synchronization

This topic is 1027 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi,

 

I am going through the d3d12 samples from microsoft; the ones at https://github.com/Microsoft/DirectX-Graphics-Samples.git, and have a question about the synchronization they are doing for their command queues. Basically in all their samples inside ::WaitForPreviousFrame(), they are doing the following:

 

const UINT64 fence = m_fenceValue;
ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), fence));
m_fenceValue++;
 
// Wait until the previous frame is finished.
if (m_fence->GetCompletedValue() < fence)
{
    ThrowIfFailed(m_fence->SetEventOnCompletion(fence, m_fenceEvent));
    WaitForSingleObject(m_fenceEvent, INFINITE);
}
 
Isn't there a possible race condition here, where the signal command on the gpu pipe is run exactly after the CPU finishes checking the if (which succeeds) but before it sets the event on completion for the fence? wouldn't CPU just wait infinitely then? Am I missing something glaring here since all the samples seem to use this pattern?
 
Even if SetEventOnCompletion would fire the event if the fence value is already the trigger value, wouldn't there be a race now between that event firing and CPU running WaitForSingleObject instruction?
 
Thanks

Share this post


Link to post
Share on other sites
Advertisement

Sorry was distracted by other stuff and just got back to this... 

 

So: say 'fence' is 2, and 'm_fence->GetCompletedValue()' is 1, but is immediately updated to 2 right after entering the if-block.
I would assume that 'm_fence->SetEventOnCompletion(fence, m_fenceEvent))' would immediately signal m_fenceEvent, because m_fence now contains 2, and 'fence' is 2.

 

I don't think you can guarantee that m_fence is immediately updated to 2 right after entering the if-block since GetCompletedValue is a CPU call, but Signal is a command queued in the GPU cmd buffer? 

 

 

 

Even if SetEventOnCompletion would fire the event if the fence value is already the trigger value, wouldn't there be a race now between that event firing and CPU running WaitForSingleObject instruction?

Why would there be a race? Both those calls happen on a single CPU thread.

 

Isn't m_fenceEvent set by the GPU driver/kernel thread but WaitForSingleObject being done by the CPU userland thread? I don't think they are the same CPU thread?

 

 

Conceptually, SetEventOnCompletion works like this:
 

HRESULT SetEventOnCompletion(UINT64 Value, HANDLE hEvent)
{
    while(fenceValue != Value);
    return SetEvent(hEvent);
}

 

I thought SetEventOnCompletion simply sets a callback function/event for when fence value equals the value, does it really wait for the fence value to equal the value before setting that function/event?

 

Thanks

Edited by hiya83

Share this post


Link to post
Share on other sites

Maybe there is a lock of some sort in the kernel that serializes the synchronization functionality of the kernel.  That would allay any fears you have right?

Share this post


Link to post
Share on other sites


I thought SetEventOnCompletion simply sets a callback function/event for when fence value equals the value, does it really wait for the fence value to equal the value before setting that function/event?
I don't think it waits. Pretend that MJP's code runs in a new background thread.


Isn't m_fenceEvent set by the GPU driver/kernel thread but WaitForSingleObject being done by the CPU userland thread? I don't think they are the same CPU thread?
SetEventOnCompletion and WaitForSingleObject both run on the same CPU thread.

The first call says, if/when this fence is signalled by the GPU, then pass the signal on to this event. The second call waits for the event (which means, it waits for the GPU to signal the fence).

 


I don't think you can guarantee that m_fence is immediately updated to 2 right after entering the if-block since GetCompletedValue is a CPU call, but Signal is a command queued in the GPU cmd buffer? 
That wasn't a guarantee, that was a hypothetical situation where you would enter the if condition even though the GPU has already signalled the fence... which I thought is the situation you were concerned about. I was pointing out that even if this unlikely event does happen, everything is still safe.

Share this post


Link to post
Share on other sites

I thought this was a race too, but from my testing I'm pretty sure the documentation is just not well specified.

 

Specifies an event that should be fired when the fence reaches a certain value.

from: https://msdn.microsoft.com/en-us/library/windows/desktop/dn899190(v=vs.85).aspx

 

The word reaches there actually means the fence value is greater than or equal to the "certain value".

And it seems the event is set immediately in the case where the fence value >= certain value before SetEventOnCompletion is called.

 

This is relatively easy to test out by modifying D3D12HelloTriangle.cpp.

 

Modify the CreateFence call to set the initial value to 100.

 

Then run some tests by hacking up WaitForPreviousFrame.

void D3D12HelloTriangle::WaitForPreviousFrame()
{
    ThrowIfFailed(m_fence->SetEventOnCompletion(75, m_fenceEvent));
    WaitForSingleObject(m_fenceEvent, INFINITE);
    /*** ALWAYS REACHES HERE ***/
    m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
}
void D3D12HelloTriangle::WaitForPreviousFrame()
{
    ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), 50));

    ThrowIfFailed(m_fence->SetEventOnCompletion(75, m_fenceEvent));
    WaitForSingleObject(m_fenceEvent, INFINITE);
    /*** NEVER REACHES HERE, although presumably could if you fill up the queue enough ***/
    m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
}
void D3D12HelloTriangle::WaitForPreviousFrame()
{
    ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), 75));

    ThrowIfFailed(m_fence->SetEventOnCompletion(50, m_fenceEvent));
    WaitForSingleObject(m_fenceEvent, INFINITE);
    /*** ALWAYS REACHES HERE ***/
    m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
}
void D3D12HelloTriangle::WaitForPreviousFrame()
{
    ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), 200));

    ThrowIfFailed(m_fence->SetEventOnCompletion(150, m_fenceEvent));
    WaitForSingleObject(m_fenceEvent, INFINITE);
    /*** ALWAYS REACHES HERE ***/
    m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
}
void D3D12HelloTriangle::WaitForPreviousFrame()
{
    ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), 99));

    ThrowIfFailed(m_fence->SetEventOnCompletion(100, m_fenceEvent));
    WaitForSingleObject(m_fenceEvent, INFINITE);
    /*** NEVER REACHES HERE, although presumably could if you fill up the queue enough ***/
    m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
}

Since the sample only ever increments the fence the greater than or equal behavior protects against a race.

Edited by MikeJM

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!