每个命令分配器一个栅栏或一个栅栏 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;
}
如果您使用多个命令分配器进行多线程处理,您将需要与命令分配器一样多的栅栏。
所以我正在尝试深入研究 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; }
如果您使用多个命令分配器进行多线程处理,您将需要与命令分配器一样多的栅栏。