【问题标题】:In Vulkan (or any other modern graphics API), should fences be waited per queue submission or per frame?在 Vulkan(或任何其他现代图形 API)中,是否应该在每个队列提交或每帧等待栅栏?
【发布时间】:2022-11-11 16:25:56
【问题描述】:

我正在尝试以渲染始终渲染为纹理的方式设置我的渲染器,然后我只呈现我喜欢的任何纹理,只要它的格式与交换链兼容。这意味着,我需要处理一个渲染场景、ui 等的图形队列(我还没有计算);一个将渲染图像复制到交换链中的传输队列;和一个用于呈现交换链的呈现队列。这是我目前正在尝试解决的用例,但随着渲染器的成熟,我将拥有更多这样的用例(例如计算队列)。

这是我想要实现的伪代码。我也在这里添加了一些我自己的假设:

// wait for fences per frame
waitForFences(fences[currentFrame]);
resetFences(fences[currentFrame]);

// 1. Rendering (queue = Graphics)
commandBuffer.begin();
renderEverything();
commandBuffer.end();

QueueSubmitInfo renderSubmit{};
renderSubmit.commandBuffer = commandBuffer;

// Nothing to wait for
renderSubmit.waitSemaphores = nullptr;

// Signal that rendering is complete
renderSubmit.signalSemaphores = { renderSemaphores[currentFrame] };

// Do not signal the fence yet
queueSubmit(renderSubmit, nullptr);

// 2. Transferring to swapchain (queue = Transfer)

// acquire the image that we want to copy into
// and signal that it is available
swapchain.acquireNextImage(imageAvailableSemaphore[currentFrame]);

commandBuffer.begin();
copyTexture(textureToPresent, swapchain.getAvailableImage());
commandBuffer.end();

QueueSubmitInfo transferSubmit{};
transferSubmit.commandBuffer = commandBuffer;

// Wait for swapchain image to be available
// and rendering to be complete
transferSubmit.waitSemaphores = { renderSemaphores[currentFrame], imageAvailableSemaphore[currentFrame] };

// Signal another semaphore that swapchain
// is ready to be used
transferSubmit.signalSemaphores = { readyForPresenting[currentFrame] };

// Now, signal the fence since this is the end of frame
queueSubmit(transferSubmit, fences[currentFrame]);

// 3. Presenting (queue = Present)
PresentQueueSubmitInfo presentSubmit{};

// Wait until the swapchain is ready to be presented
// Basically, waits until the image is copied to swapchain
presentSubmit.waitSemaphores = { readyForPresenting[currentFrame] };

presentQueueSubmit(presentSubmit);

我的理解是需要栅栏来确保 CPU 等到 GPU 完成将上一个命令缓冲区提交到队列。

在处理多个队列的时候,让CPU只等待帧,用信号量同步不同的队列就够了吗(上面的伪代码就是基于这个)?还是每个队列应该分别等待栅栏?

进入技术细节,如果两个命令缓冲区被提交到同一个队列而没有任何信号量会发生什么?伪代码:

// first submissions
commandBufferOne.begin();
doSomething();
commandBufferOne.end();

SubmitInfo firstSubmit{};
firstSubmit.commandBuffer = commandBufferOne;
queueSubmit(firstSubmit, nullptr);

// second submission
commandBufferTwo.begin();
doSomethingElse();
commandBufferTwo.end();

SubmitInfo secondSubmit{};
secondSubmit.commandBuffer = commandBufferOne;
queueSubmit(secondSubmit, nullptr);

第二次提交会覆盖第一个,还是第一个 FIFO 队列会在第二个之前执行,因为它是第一次提交的?

【问题讨论】:

  • 如果 GPU 只有一个队列会发生什么?或者演示引擎不支持复制到交换链图像中?还是没有队列可以呈现,不能执行图形?
  • 无论如何,我目前只使用一个队列,因为在我的 GPU 中,一个队列可以进行图形、传输和演示;但是,考虑到规范没有说明应该如何定义队列这一事实,我不确定对各种硬件有什么期望。
  • 规范说所有图形队列都可以进行传输(和计算)操作。虽然 GPU 可以控制哪些队列系列可以进行演示,但这并不是真正的问题,因为演示不提供与之同步的栅栏。您只需要确保在提交图形操作后完成呈现即可。
  • 我将从这里的规范中完全引用以供将来参考(我完全错过了第一个):“如果一个实现公开了任何支持图形操作的队列族,则至少一个队列族由至少一个物理设备公开实现必须同时支持图形和计算操作。”和“支持传输操作的队列上允许的所有命令也允许支持图形或计算操作的队列上。”

标签: graphics vulkan


【解决方案1】:

这整个组织计划似乎是可疑的。

即使忽略 Vulkan 规范不要求 GPU 为所有这些事情提供单独的队列这一事实,您仍在异步执行中传播一系列操作,尽管事实上这些操作是固有顺序.在图像被渲染之前,您无法从图像复制到交换链,并且在复制完成之前,您无法呈现交换链图像。

所以把这些东西放到自己的队列里基本上没有什么好处。只需在同一个队列中执行所有这些操作(一个提交和一个vkQueuePresentKHR),在操作之间使用适当的执行和内存依赖关系。这意味着只有一件事需要等待:单次提交。

另外,提交操作真的很昂贵;如果提交是在可以同时工作的不同 CPU 线程上完成的,那么做两个提交而不是一个包含两个工作的提交只是一件好事。但是二进制信号量阻止了它的工作。在您提交一个等待信号量 A 的批次之前,您不能提交一个等待信号量 A 的批次。信号信号量 A。这意味着批处理信号必须在同一个提交命令中更早,或者必须在之前的提交命令中提交。这意味着如果您将这些提交放在不同的线程上,则必须使用互斥锁或其他东西来确保信号提交发生在等待提交之前。1

所以你不会得到队列提交操作的任何异步执行。所以 CPU 和 GPU 都不会异步执行任何这些。

1: 时间线信号量没有这个问题。


至于您的技术问题的细节,如果操作A依赖于操作B,并且您与A同步,您也与B同步。由于您的传输操作是等待来自图形队列的信号,因此等待传输操作还将等待该信号之前的图形命令。

【讨论】:

  • 我理解你的意思,目前,我有一个队列,一个提交,一次提交所有内容,然后呈现。但是,我并不总是想在不呈现它们的情况下呈现它们。我当前的系统具有一个渲染图、一个队列提交和呈现,这使得执行任何类型的“一次性”渲染操作变得非常复杂和繁琐,我需要大量使用它。这就是为什么我试图将渲染与呈现完全分开。
  • 队列提交操作的数量比您优先考虑的其他任何事情都更重要。只需弄清楚您是否需要创建一个 CB 以将结果图像复制到可呈现的图像。如果这样做,请将其添加到提交操作中。
  • 为什么队列提交很昂贵?我们谈论的是提交两个而不是一个队列提交,第二个队列提交由 3-4 个命令(屏障 + 复制命令)组成。在记录命令时,这些命令之间没有 CPU 干预;所以,据我了解,我什至不需要这里的栅栏。是什么影响了队列提交的成本?
  • @Gasim:这里的“简单”是什么?在某些时候,您会决定是否复制到交换链映像。把那个决定你的提交而不是之后。我看不出两者都“更简单”。 “灵活性”也是如此;如何更“灵活”?
  • 现在想来,我觉得你是对的!每个渲染操作(调用render)和当前操作每帧都会有不同的命令缓冲区。因此,我可以在技术上将它们批处理在一起,并在帧结束时一次性发送它们。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-20
  • 2016-02-29
  • 1970-01-01
  • 2012-07-29
  • 1970-01-01
相关资源
最近更新 更多