每个命令分配器一个栅栏或一个栅栏 DirectX12

A Fence Per Command Allocator or just one fence DirectX12

所以我正在尝试深入研究 DirectX12 同步。我很难理解为什么有些程序需要多个栅栏来进行 GPU/CPU 同步,而不是一个。

在这个guide中,它们只有一个栅栏和一个栅栏值(按帧存储,以便可以检查特定帧的值)。

而在这个 guide 中,他们为每一帧设置一个栅栏,并为每一帧设置一个当前值数组。 (本指南未解释其为每一帧设置围栏的原因)

正确的做法是什么? (为简单起见,假设它是一个单线程程序,在交换链中有 3 个后台缓冲区,但如果您也了解多线程程序,那么额外的信息将会有所帮助)。如果它们都是有效的方法,每种方法的优缺点是什么?

另外,为什么我们对每一帧都使用一个栅栏事件而不是一个栅栏事件?如下所示:

    if (fence->GetCompletedValue() < fenceValue)
    {
        ThrowIfFailed(fence->SetEventOnCompletion(fenceValue, fenceEvent)); 
        ::WaitForSingleObject(fenceEvent, static_cast<DWORD>(duration.count()));
    }

(我知道信号被附加到命令队列中命令列表的末尾)那么栅栏事件是否在 Windows OS 中排队?这就是我们可以重用它的原因,还是因为我们一次只会等待一个事件,这就是为什么我们只需要 1.

感谢您的帮助。在我继续进行更复杂的同步之前,我试图深入了解基础知识。

编辑:发现这个 link 谈论更多的同步:link 以防人们感兴趣。虽然没有回答问题

Edit2:以下 link 与 Vulkan 一起使用,但也在飞行中每帧使用栅栏。但也没有给出理由。

#directxtk 使用单个栅栏,每帧都有一个栅栏值。

我相信您需要每个帧的围栏值,因为您想知道下一帧何时准备就绪,而不是上一帧何时完全完成(这样您就可以使用单个全局围栏值)。调用栅栏的 GetCompletedValue() 会告诉您栅栏是否达到了考虑下一帧准备好渲染所必需的栅栏值。

由于帧随后台缓冲区一起旋转,因此您需要与后台缓冲区一样多的栅栏值。但是您不一定需要多个栅栏。对于给定的命令分配器,您可以使其与单个栅栏一起工作。

任何时候你需要围栏的东西,你会得到你之前为当前后台缓冲区存储在 m_fenceValues[m_backBufferIndex] 中的当前围栏值。 然后你递增 1,并通过 Signal() 告诉 GPU 请在执行所有当前命令时将围栏设置为此围栏值。当您想检查它们是否已被执行时,您可以与此围栏值进行比较。如果它转到了新的数字,那么 GPU 已经完成了它需要的工作。 将新的围栏值存储回 m_fenceValues[m_backBufferIndex] 就大功告成了。

这可以在任何时候完成,在一帧内或移动到下一帧。当你想移动到下一帧时,你做同样的事情,除了你将 (fence value +1) 存储在当前后台缓冲区的 fence 值中,到现在为止,它已经成为新的后台缓冲区。

这里是移动到下一帧的相关代码:

// Prepare to render the next frame.
void DeviceResources::MoveToNextFrame()
{
    // Schedule a Signal command in the queue.
    const UINT64 currentFenceValue = m_fenceValues[m_backBufferIndex];
    ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), currentFenceValue));

    // Update the back buffer index.
    m_backBufferIndex = m_swapChain->GetCurrentBackBufferIndex();

    // If the next frame is not ready to be rendered yet, wait until it is ready.
    if (m_fence->GetCompletedValue() SetEventOnCompletion(m_fenceValues[m_backBufferIndex], m_fenceEvent.Get()));
        WaitForSingleObjectEx(m_fenceEvent.Get(), INFINITE, FALSE);
    }

    // Set the fence value for the next frame.
    m_fenceValues[m_backBufferIndex] = currentFenceValue + 1;
}

如果您使用多个命令分配器进行多线程处理,您将需要与命令分配器一样多的栅栏。