C++ 在Vulkan中,每个交换链映像、每个帧或每个命令池是否需要专用围栏/信号灯?
我读过几篇关于CPU-GPU(使用fences)和GPU-GPU(使用信号量)同步机制的文章,但仍然难以理解如何实现简单的渲染循环 请查看下面的简单C++ 在Vulkan中,每个交换链映像、每个帧或每个命令池是否需要专用围栏/信号灯?,c++,graphics,3d,vulkan,C++,Graphics,3d,Vulkan,我读过几篇关于CPU-GPU(使用fences)和GPU-GPU(使用信号量)同步机制的文章,但仍然难以理解如何实现简单的渲染循环 请查看下面的简单render()函数。如果我没弄错的话,最起码的要求是我们通过一组可用的信号量图像和渲染完成来确保vkAcquireNextImageKHR、vkQueueSubmit和vkQueuePresentKHR之间的GPU-GPU同步 然而,这真的节省了吗?所有操作都是异步的。那么,在后续调用render()时再次“重用”可用的image\u信号量是否真的
render()
函数。如果我没弄错的话,最起码的要求是我们通过一组可用的信号量图像
和渲染完成
来确保vkAcquireNextImageKHR
、vkQueueSubmit
和vkQueuePresentKHR
之间的GPU-GPU同步
然而,这真的节省了吗?所有操作都是异步的。那么,在后续调用render()
时再次“重用”可用的image\u
信号量是否真的安全,即使之前调用的信号请求尚未触发?我认为不是这样,但另一方面,我们使用的是相同的队列(不知道图形和表示队列实际上在哪里相同是否重要),队列中的操作应该按顺序使用。。。但如果我做对了,它们可能不会“作为一个整体”被消费,并且可以重新排序
第二件事是(同样,除非我遗漏了什么),我显然应该为每个交换链映像使用一个栅栏,以确保对与调用render()
的image\u索引
对应的映像的操作已经完成。但这是否意味着我必须做一个
if (vkWaitForFences(device(), 1, &fence[image_index_of_last_call], VK_FALSE, std::numeric_limits<std::uint64_t>::max()) != VK_SUCCESS)
throw std::runtime_error("vkWaitForFences");
vkResetFences(device(), 1, &fence[image_index_of_last_call]);
编辑:如下面的答案所示,假设我们有k
“飞行中的帧”,因此上面代码中使用的信号量和栅栏的k
实例,我将用m_image_available[I]
,m_rendering_finished[I]
和m_fence[I]
表示I=0。。。,k-1
。让i
表示飞行中帧的当前索引,在每次调用render()
后,该索引增加1
,并且j
表示从j=0开始的render()
调用次数
现在,假设交换链包含三个图像
- 如果
j=0
,则i=0
,飞行中的第一帧使用交换链图像0
- 同样,如果
j=a
,则i=a
,飞行中的第a
帧使用交换链图像a
,用于a=2,3
- 现在,如果
j=3
,则i=3
,但由于交换链映像只有三个映像,飞行中的第四帧再次使用交换链映像0
。我不知道这是否有问题。我想不是,因为在调用render()
时,在调用vkAcquireNextImageKHR
,vkQueueSubmit
和vkQueuePresentKHR
时使用的等待/信号量m\u image\u可用[3]
m\u渲染完成[3]
,专用于飞行中的特定帧
- 如果我们到达
j=k
,然后再次到达i=0
,因为飞行中只有k
帧。现在,如果从render()
的第一次调用(i=0
)到vkQueuePresentKHR
的调用尚未发出m_fence[0]
的信号,我们可能会在render()的开始处等待
所以,除了上面第三个要点中描述的我的怀疑之外,剩下的唯一问题是为什么我不应该把k
取得尽可能大?理论上我可以想象的是,如果我们以比GPU能够使用的更快的方式向GPU提交工作,使用的队列可能会不断增长并最终溢出(是否存在某种“队列中的最大命令数”限制?)。首先,正如您正确提到的,信号量严格用于GPU-GPU同步,例如确保一批命令(一次提交)在另一批命令开始之前完成。这在这里用于将渲染命令与present命令同步,以便呈现引擎知道何时呈现渲染图像
围栏是CPU-GPU同步的主要工具。在提交队列中放置围栏,然后在CPU端等待围栏,然后再继续。这通常是在这里完成的,这样我们就不会在前一帧尚未完成时对任何新的渲染/呈现命令排队
但这是否意味着我必须做一个
if (vkWaitForFences(device(), 1, &fence[image_index_of_last_call], VK_FALSE, std::numeric_limits<std::uint64_t>::max()) != VK_SUCCESS)
throw std::runtime_error("vkWaitForFences");
vkResetFences(device(), 1, &fence[image_index_of_last_call]);
if(vkWaitForFences(device(),1,&fence[上次调用的图像索引]、VK\u FALSE、std::numeric\u limits::max())!=VK\u成功)
抛出std::runtime_错误(“vkWaitForFences”);
vkResetFences(device()、1和fences[image_index_of_last_call]);
在我打电话给vkAcquireNextImageKHR之前
是的,您的代码中肯定需要这个,否则您的信号量将不安全,您可能会得到验证错误
通常,如果希望CPU等待GPU完成前一帧的渲染,则只有一个围栏和一对信号灯。您还可以使用队列或设备的waitIdle命令替换围栏。
但是,实际上,您不希望暂停CPU,同时记录下一帧的命令。这是通过飞行中的帧完成的。这仅仅意味着,对于飞行中的每一帧(即,可以与GPU上的执行并行记录的帧数),您有一个围栏和一对信号灯,用于同步该特定帧
因此,本质上,为了使渲染循环正常工作,飞行中每帧需要一对信号灯+围栏,与交换链图像的数量无关。但是,请注意,当前帧索引(飞行中的帧)和图像索引(swapchain)将if (vkWaitForFences(device(), 1, &fence[image_index_of_last_call], VK_FALSE, std::numeric_limits<std::uint64_t>::max()) != VK_SUCCESS)
throw std::runtime_error("vkWaitForFences");
vkResetFences(device(), 1, &fence[image_index_of_last_call]);