【问题标题】:Vulkan and older APIs memory usageVulkan 和旧 API 的内存使用情况
【发布时间】:2023-03-17 10:11:01
【问题描述】:

我是 vulkan 的新手。问题在于变换对象。使用 DX11 和 OpenGL 时,我曾经更新统一缓冲区,然后将绘图命令发送到 gpu,但在 vulkan 中,所有命令都是预先记录的。所以我看不出有什么办法可以做到这一点。我在网上读到,为了转换每个对象,我可以使用一组统一缓冲区并在绘制时从中索引。这是在 vulkan 中转换每个对象的唯一方法吗?

如果是,那么 vulkan 不是使用比旧 API 更多的内存吗?较旧的 API 可以有一个统一的缓冲区,并在绘制调用之前对其进行更新,但在 vulkan 中,我们为每个对象使用了一个缓冲区。 Vulkan 作为一种高性能 API 很受欢迎,但较旧的 API 使用的内存更少。

如果不是,我怎样才能更有效地做到这一点? 谢谢。

【问题讨论】:

  • 更新统一缓冲区也是一个命令,不是吗?
  • 是的,但我们可以为此使用单独的命令缓冲区。
  • 你的制服到底有多大,你甚至担心记忆?当你开始上传你的 4k 纹理或其他东西时,几 kB 不会为你节省。
  • 我们也可以使用单独的命令缓冲区来绘制对象,但是命令缓冲区会花费更多的内存
  • 现在只是转换矩阵:D

标签: vulkan


【解决方案1】:

在 OpenGL 等高级图形 API 中,统一变量也位于全局/通用统一缓冲区中。为方便起见,它只是没有向开发人员公开。但是统一变量更新的执行方式与 Vulkan 中类似 - 这是向统一缓冲区的正常数据传输。

现在,如果您想在绘制对象之前更新统一变量,您可以在 Vulkan 中执行完全相同的操作。有像 vkCmdUpdateBuffer() 或 vkCmdCopyBuffer() 这样的方法可以做到这一点。但是为什么开发人员不使用这种方法呢?由于同步而影响性能。在 OpenGL 中,这是由驱动程序自动完成的,但它与 Vulkan 中的影响相同。它只是没有暴露给开发人员。 Vulkan 表明,如果您考虑性能,这不是最好的方法。保留一组统一缓冲区(每个对象一个)或具有一组统一变量的单个统一缓冲区会更好。您也可以为此目的使用推送常量。使用它们类似于旧的、类似 OpenGL 的更新存储在全局命名空间中的统一变量,但数据量是有限的(规范保证 128 字节)。

【讨论】:

    【解决方案2】:

    因此,假设 Vulkan 是 OpenGL\immediate API 是完全正确的:

    for( int i = 0; i < N; ++i ){
        cmdbuff.begin(); cmdUpdateUniform(u[i]); cmdbuff.end();
        vkQueueSubmit( q, cmdbuff ); // lookitme ama glUniform*()
        // some sychronization omitted 
    
        cmdbuff.begin(); vkCmdDraw(obj[i]); cmdbuff.end();
        vkQueueSubmit( q, cmdbuff ); // lookitme ama glDraw*()
        vkQueueWaitIdle( q ); // lookitme ama glFinish()
    }
    

    这有一个问题。 OpenGL 驱动程序将尝试使用延迟与吞吐量权衡来优化这一点。但是在 Vulkan 中,我们希望对延迟进行一定程度的控制,因此 Vulkan 驱动程序不会(不应该)以这种方式对其进行优化。

    所以我们可以尝试猜测 OpenGL 驱动程序会做什么:

    cmdbuff.begin();
    for( int i = 0; i < N; ++i ){
        cmdUpdateUniform(u[i]);  // probably vkCmdUpdateBuffer     
        // some sychronization omitted   
        vkCmdDraw(obj[i]);
    }
    cmdbuff.end();
    
    vkQueueSubmit( q, cmdbuff );
    

    如您所见,内存使用又回来了(vkCmdUpdateBuffer 将所有制服存储在命令缓冲区中),如果 OpenGL 驱动程序希望获得高性能,它可能必须这样做(试图将所有绘图聚合到一个GPU 提交)。

    这种方法也有一个小问题。所有vkCmdDraw 都使用相同的uniform\buffer 内存,因此之前的vkCmdDraw 需要在更新之前完成使用该uniform。允许驱动程序继续进行并且不必同步vkCmdDraw 和随后的统一更新有潜在的好处。

    有你在网上阅读的信息。一种方法是拥有一组制服并使用索引访问适当的制服。 另一种方法是通过vkCmdBindDescriptorSets 绑定不同的描述符或pDynamicOffsets


    内存使用注意事项:

    4x4 sp 矩阵为 64 B。假设您有 1024 个 64 kB 的 3D 对象。在当今这个时代,对于主\GP GPU 内存而言微不足道,即使是单个纹理或您需要的其他资源也会相形见绌。

    如果您的内存使用量显着增加,则问题可能出在其他地方。

    【讨论】:

    • 优秀的答案先生。我只有一个问题。您说“如果希望获得高性能,OpenGL 驱动程序可能必须这样做(试图将所有绘图聚合到一个 GPU 提交)。”但是 OGL 驱动程序是否在一次提交中发送所有绘制命令?不是每次调用 glDrawArrays 都会提交吗?
    • @IAS0601 我必须承认我在某种程度上对 OGL 很差劲,但我认为它不会。由司机决定。否则glFlush 命令将不存在。我真的不应该将 OpenGL 称为“立即”。它介于两者之间。出于其他命令的目的,它假装是立即的,但不能保证命令的副作用在它返回之前完成(如果该调用需要,它可能在下一个命令调用期间完成,或者最迟在后续 glFlush 命令时完成返回。)
    • s/glFlush/glFinish
    • @IAS0601 据我所知,OpenGL 驱动程序构建的命令缓冲区类似于 Vulkan 的命令缓冲区,因为批量发送命令比单独发送命令要好得多。但是这些命令缓冲区的大小是未知的,并且取决于实现。我们也不知道它们是什么时候“创造”和“毁灭”的。这就是 glFlush() 和 glFinish() 命令存在的原因。
    • @Ekzuzy 谢谢。我想我已经为每个对象使用了一个统一的缓冲区?
    猜你喜欢
    • 1970-01-01
    • 2012-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-12
    • 1970-01-01
    相关资源
    最近更新 更多