需要多少栅栏才能在 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 个栅栏(A 和 B) 对于所有帧和队列:
第一帧:
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);
除了 A 和 B 的值将递增: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 图形工作实际上仍然 运行。
假设我有一个框架,它按顺序使用 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 个栅栏(A 和 B) 对于所有帧和队列:
第一帧:
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);
除了 A 和 B 的值将递增: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 图形工作实际上仍然 运行。