需要多少栅栏才能在 DirectX 12 中使用多个命令队列支持多个飞行帧?

How many fences are necessary to support multiple frames in flight each using multiple command queues in DirectX 12?

假设我有一个框架,它按顺序使用 2 个复制队列、1 个图形和 1 个计算队列:

1) 使用帧开头的第一个复制队列(网格顶点等)将数据从 CPU 上传到 GPU。那将是第一个复制队列中的 ExecuteCommandLists,然后是 SignalFence

2) 在异步计算队列上构建光线追踪加速结构。 WaitFence 等待我们刚刚上传的数据,然后 ExecuteCommandLists 构建加速度。结构,然后 SignalFence.

3) WaitFence 在图形队列上等待 AS 构建然后 ExecuteCommandLists 渲染帧。然后发出另一个SignalFence

4) WaitFence 然后 ExecuteCommandLists 在第二个复制队列上执行数据回读 (GPU -> CPU), 让我们说将地形和物理恢复到 CPU。然后我们为帧调用最终的SignalFence

现在,我希望始终缓冲 3 帧,以避免 CPU/GPU 在未执行任何工作时出现气泡。

实现此目标的正确围栏设置是什么?

到目前为止,我已经实现了 2 个变体,其中 1 个应该可以工作(除非我完全错了)但它没有,第二个可以工作,但我不确定为什么。请帮我弄清楚。

1)2 个栅栏(AB) 对于所有帧和队列:

第一帧:

CopyQueue1.ExecuteCommands();
CopyQueue1.SignalFence(A, 1);

AsyncComputeQueue.Wait(A, 1);
AsyncComputeQueue.ExecuteCommands();
AsyncComputeQueue.Signal(A, 2);

GraphicsQueue.Wait(A, 2);
GraphicsQueue.ExecuteCommands();
GraphicsQueue.Signal(A, 3);

CopyQueue2.Wait(A, 3);
CopyQueue2.ExecuteCommands();
CopyQueue2.Signal(B, 1);

除了 AB 的值将递增:3、4、5 和 6 之外,下一帧也一样,第 2 帧和第 3 帧中 A 的 7、8,以及 B.

的第 2 帧和第 3 帧中的值 2、3

在渲染循环结束时,我执行检查以保持最多 3 帧在飞行中:

if (CurrentFrameBValue - B.SignalledValue() >= 3)
{
    StallCurrentCPUThread();
} 

ReleaseCommandListsForThisFrame();
// GoToNextRenderLoop

此代码有一个问题,即 B 的信号发送速度非常快,我不会拖延 CPU 并继续为相应的帧重置命令列表并进行调试图层错误,表示我 在 GPU 仍在使用命令列表时重置命令列表 。 据我了解,提交给 GPU 的所有工作都保证按提交顺序执行。所以我希望栅栏按如下方式前进:A - 1、2、3,然后 B 到 1,然后 A 到 4、5、6,然后 B 到 2,依此类推。为什么 B 在帧的所有工作完成之前发出信号?

2) 不发出错误的方法。每个队列有 4 个 fences A, B, C, D,每帧将它们的值增加一个,就像我们在案例 1 中对 B 所做的那样。

我能看到第一种情况失败的一个原因是 GPU 上的工作并没有按照我期望的顺序真正完成,并且 fence A 可以以不可预测的顺序发出信号,搞乱依赖关系,而第二种情况对每种情况都有单独的围栏..

我还应该注意,我在帧之间没有依赖关系:CopyQueue1 不依赖于通过栅栏的 CopyQueue2,我通过保持不超过 3 个帧的飞行来确保正确性,上面显示 CPU 停顿。

有什么想法吗?

我认为问题出在为 3 个不同的队列使用 1 个栅栏。让我们看一下案例 1。 Copy1(Frame 1) -> AsyncCompute(Frame 1) -> Graphics(Frame 1) - > Copy2(Frame 1) 然后 Copy1(Frame 2) -> AsyncCompute(Frame 2) - > Graphics(Frame 2) -> Copy2(Frame 2) 都具有相同的围栏对象,但值不同。

我认为,Copy1(Frame 2) 是在 AsyncCompute(Frame 1) 甚至 Graphics(Frame 1), 没关系,因为它发出的围栏值高于第 1 帧中预期的任何值,搞乱了第 1 帧的依赖关系并开始 Copy2(Frame 1) 太早导致帧完成和命令列表重置,而异步 and/or 图形工作实际上仍然 运行。