我正在Vulkan制作2d太空入侵者克隆。
当我添加命令到我的缓冲区时,我插入一个新的push常量"Sprite"-它只包含一个2d位置和一个纹理id -然后插入一个绘制调用。
当我在构建管道时,我将所有需要的纹理添加到描述符集,然后使用Sprite的纹理id来指定使用哪个纹理。因此,我不需要在draw调用之间绑定新的管道。
我的代码是这样的:
for ( uint32_t i = 0; i < m_sprites.size(); i++ )
{
vkCmdPushConstants( blah, &m_sprites[i]);
vkCmdDrawIndexed( blah );
}
我这样设置是因为我读到"Draw"调用本身并不昂贵,是它们周围的状态变化使它们变得昂贵。https://www.reddit.com/r/vulkan/comments/g5bh31/why_are_drawcalls_setpass_calls_so_expensive/
谁能告诉我这是正确的吗?
对于这样的问题,答案几乎总是">这取决于
"。是否可接受取决于它是否满足您在目标平台上的需求。对于像《太空侵略者》这样的简单应用,在屏幕上拥有数十个甚至数百个精灵只会转化为数百个调用,这在现代硬件上是微不足道的,特别是如果你没有做任何复杂的照明。也就是说,你可能几乎不会在GPU使用方面移动指针。
也就是说,您所编写的代码实际上是执行vkCmdDrawIndexedIndirect
的确切用例。而不是每次调用
- 将整个
m_sprites
缓冲区推送或更新到GPU上。 - 将其绑定为VK_VERTEX_INPUT_RATE_INSTANCE输入速率的顶点属性
- 使用实例id来访问着色器数组中的
m_sprites
数据,而不是通过push常量。 编辑:
回答你的评论,为什么这将是一个间接的draw调用可能会更高效…首先,你传递给GPU的信息更少了。例如,如果你想绘制5000个精灵,那么你就必须记录1万个命令。如果你使用间接呼叫,你只有一个命令。
更重要的是,因为你使用push常量,每次任何精灵改变时,你每次都有效地复制整个m_sprites
缓冲区,并且你必须重新记录整个命令缓冲区,对吗?但如果您使用的是间接电话,则不必这样做。您可以一遍又一遍地使用相同的命令缓冲区,并且仅使用已更改的元素更新m_sprites
缓冲区的GPU副本。所以如果只有一个精灵移动,你只需要更新一小部分缓冲区。显然,这种优化意味着你的代码会更复杂,因为你必须跟踪缓冲区的哪些部分是"脏的"。并且需要被推送到CPU,但是有一些方法可以很容易地包装这类东西。
关于draw调用的并行性,我将在这里阅读一些管道阶段的严格顺序。