如何在单个Vulkan渲染过程中重复更新统一数量的对象数据,并使更新同步?

时间:2019-01-09 04:47:38

标签: glsl shader vulkan

我正在尝试将OpenGL 3D游戏引擎移植到Vulkan。游戏场景中有大量3D对象,每个对象都有其自己的属性(模型矩阵,灯光等),并且这些对象是完全动态的,这意味着某些3D对象可能会进入,而其他3D对象可能会在游戏过程中被移除。使用OpenGL,我将3D对象的属性分组到着色器中的统一缓冲区中(简化了代码):


    layout(std140, set = 0, binding = 0) uniform object_attrib 
    {
        vec3 light_pos;
        vec3 light_color;
        mat4 model;
        mat4 view_projection;
        ...
    } params;

我现在想做的是为游戏场景中的每个3D对象使用单个统一缓冲区,以通过Vulkan渲染它们。

我正在使用单个Vulkan渲染过程,在begin-render-pass和end-render-pass中,我使用了for-each循环遍历每个3D对象并执行以下操作来渲染它们。请参见下面的伪代码。


    vkBeginCommandBuffer(cmdBuffer, ...);
        vkCmdBeginRenderPass(cmdBuffer, ...);
            for(object3D obj : scene->objects)
            { 
                // Step 1 - update object's uniform data by memcpy()
                _updateUniformBuffer(obj); 

                // Step 2 - build draw command for this object
                // bind vertex buffer, bind index buffer, bind pipeline, ..., draw
                _buildDrawCommands(obj);
            }
        vkCmdEndRenderPass(cmdBuffer, ...);
    vkEndCommandBuffer(cmdBuffer, ...);
    vkQueueSubmit(...); // Finally, submit the commands to queue to render the scene

很显然,我的解决方案将无法工作,因为仅在调用vkQueueSubmit()之后,缓冲区中的所有Vulkan命令才在GPU上执行。但是对_updateUniformBuffer(obj)的调用(由mem​​cpy(...))与命令记录是“交错的”,并且立即执行,因此序列被弄乱了,最终每个对象都不会获得自己的属性。

因此,可能有一个问题是Vulkan在一次渲染过程中为每个对象重复正确更新统一缓冲区并确保每个对象获得正确属性数据的解决方案是什么?

在发布此问题之前,我曾尝试考虑以下解决方案,但似乎都不是一个好的解决方案:

  • 使用render-pass-per-object并使用fence来确保一个对象被完全渲染,直到我开始渲染下一个对象为止。如果有1000个对象,那么每帧有1000个渲染通道?这是不可能的。
  • 我可以在一个渲染过程中重复提交命令缓冲区吗?我的意思是我在构建一个对象的绘制命令后立即提交命令缓冲区以渲染该对象,使用围栅确保渲染完成,然后转到下一个对象。这将有一个渲染通道和1000个vkQueueSubmit()调用
  • 使用动态统一缓冲区创建一个巨大的统一缓冲区,其中包含1000个对象的数据。由于对象号是动态的,因此难以实现。
  • 使用推常数吗?由于最大数据大小仅为128个字节,因此也是不可能的。

1 个答案:

答案 0 :(得分:2)

因为要记录绘制命令及其输入数据的制服形式,所以对于场景中的所有对象,在它们执行和读取其输入数据之前,都无法存储所有版本的分配在某处的统一缓冲区。 OpenGL ES驱动程序为您执行此操作:更新制服时,它们会在内部分配新的空间,将新的制服写入其中,然后更新内部指针,以便下一次调用将使用新的制服数据而不是先前的制服数据数据。

在Vulkan中,您可以自己做,而第三个想法最接近正确的方法。有一些变化,但是最直接的变化之一是:

创建一个大的VkBuffer并将其绑定到内存。它可能应该足够大以处理典型/平均帧的所有统一数据。从零偏移开始,对于每个绘图,在当前偏移处写入新的制服,将描述符集中的VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC重新绑定为动态偏移指向新的制服数据,然后更新偏移,以便下一张绘图的制服将放置在您刚刚使用过的位置之后。

在每帧结束时(假设每帧有一个命令缓冲区),记住您进入缓冲区的距离,并将其与表示该命令缓冲区已完成的事件相关联。该事件将告诉您何时可以覆盖该帧中使用的缓冲区区域。如果在再次有足够的空间可用之前最终需要更多空间用于制服,则可以创建一个新的VkBuffer并开始使用它,最终在退回其数据时恢复到原始状态。这样,您就可以得到一个动态大小的,由多个VkBuffer组成的统一数据环形缓冲区。