在运行时添加新 3D 对象的最佳方式

The best way to add a new 3D object at runtime

到目前为止,我一直在启动期间创建 3D 对象。但现在我需要动态添加它们。还有什么可以更简单的,我想...

现在的主要问题是如何以最快的方式上传新对象的数据,并找出数据何时上传。

这是我的设置:

这是我的 thoughts/questions:

  1. 如何上传数据?我希望缓冲区仅对 gpu 可见,这意味着我需要一个暂存缓冲区。
  2. 如果我使用暂存缓冲区,我需要知道数据何时可以在 gpu 上使用。我不想冲洗管道并等待。我看到的唯一方法是为每个对象使用栅栏,并且仅在该栅栏准备就绪时调用 draw 命令。
  3. 如果我使用暂存缓冲区并想在短帧期间上传多个对象,我需要以某种方式确保该暂存缓冲区的部分不会被不同的对象覆盖。为此,我需要保持它的大,处理偏移量的对齐。但是有多大?

我很确定我过于复杂了。我相信应该有一个更简单的模式。你会怎么做?

I believe there should be a much simpler pattern.

这是 Vulkan;这是一个明确的低级 API。 “简单”不是它的目标。

总的来说,您的 Vulkan 代码需要编写以适应硬件的功能。这是获得性能的最佳方式。

需要做出的第一个决定是您是否需要分期。仅当您的设备的 DEVICE_LOCAL 内存不可映射时才需要暂存(用于缓冲区副本)。是的,有(集成的)GPU 可以映射 DEVICE_LOCAL 内存。如果是这样,那么你可以直接写到你需要数据去的地方。

如果需要暂存,则需要确定硬件是否支持独立的仅传输队列。如果是这样,那么您可能会通过使用它获得性能优势。并非所有硬件都支持仅传输队列,因此您的应用程序需要进行调整。此外,仅传输队列可能对发生在这些队列上的内存传输的粒度有限制,因此您需要检查您的流媒体策略是否符合该特定硬件的限制。

此外,如果没有合适的传输队列,您可以使用第二个计算或图形队列来创建传输队列的效果...如果硬件支持多个排队。能够在不同的队列上提交传输命令和渲染命令是一件好事,假设您正在利用线程(即:将批处理提交到不同线程上的不同队列)。

如果您能够使用单独的队列进行传输(无论是真正的传输队列还是单独的 compute/graphics 队列),那么您就可以使用信号量了。传输数据的批处理必须在完成时发出信号量;这是 vkQueueSubmit 调用中的批处理的一部分。主队列上为某些进程使用传输数据的批处理需要在该信号量上 wait。所以两个线程都需要使用相同的 VkSemaphore 对象。信号量上的等待应该有一个全局内存屏障,以使内存可见。

棘手的部分是这样的:您不能 提交等待信号量的批处理,直到发出信号已提交的批处理的提交调用。您不必等到完成,但必须等到传输队列上的 vkQueueSubmit 调用返回。所以你需要一种在不同线程之间传输信号量的方法,或者你可以在同一个线程上发出两个提交命令。

如果您不使用第二个队列,那么事情会稍微简单一些。

您仍然希望在不同的线程上构建传输命令缓冲区本身(以利用线程 CB 构建)。但是现在需要将该 CB 传达给负责提交渲染内容的线程。并且这个通信通道需要知道这个 CB 包含传输命令,一些渲染 CB 进程应该等待。

最简单和最灵活的方法是构建传输 CB,以便最后一个命令是 vkCmdSetEvent 命令(第一个命令是 vkCmdResetEvent 以将其从之前的命令重置使用框架)。然后提交线程只需要创建一个小的 CB,它只包含一个 vkCmdWaitEvents 命令,等待将要设置的传输事件。该命令应发出一个完整的内存屏障,并且该 CB 应在传输 CB 和任何从传输数据读取的渲染 CB 之间执行。

它的灵活性在于过程的结构。它的结构类似于多队列版本的工作方式。在这两种情况下,一个单独的线程需要与渲染提交线程进行通信(在一种情况下,一个信号量;在另一种情况下,一个 CB 和一个事件)。并且渲染提交线程需要做一些事情来等待那个“东西”,但不会中断构建渲染命令本身的过程(在一种情况下,您只需更改批处理以等待信号量;在另一种情况下,您插入等待事件的 CB)。

如果你想更聪明地了解执行依赖关系,你甚至可以让传输操作转发有关哪些管道阶段需要等待操作的信息。但这主要是一种优化。

事情是这样的:所有的暂存案例都不是性能友好的。它们是有问题的,因为在传输操作正在进行时您不能做任何事情。情况就是这样,因为......你正试图从你正在写入的同一帧中的内存中读取。那是不好

您应该努力延迟渲染任何加载未完成的对象。或者换句话说,您想在需要新对象之前加载数据,而不是在您需要它们的同一帧上。这就是流式传输系统所做的:它们先发制人地加载即将需要但不是现在需要的数据。

But how big?

只有您和您的用例可以回答这个问题。如果您以固定大小的块进行流式传输(您应该尽可能这样做),那么这很容易:您的暂存缓冲区的大小应该是一个或两个流式块。如果您的渲染系统更灵活,对更高级别的代码施加的限制更少,那么您的暂存缓冲区和流式传输系统需要更灵活。对此没有正确答案;这完全取决于它的使用方式。

欢迎使用显式、低级 APIs。