Как многократно обновлять единые данные для количества объектов внутри одного прохода рендеринга Vulkan и синхронизировать обновление? - PullRequest
0 голосов
/ 09 января 2019

Я пытаюсь перенести игровой движок OpenGL 3D на Vulkan. На игровой сцене имеется большое количество трехмерных объектов, и у каждого есть свои атрибуты (матрица модели, источники света и т. Д.), И объекты являются полностью динамическими, что означает, что некоторые трехмерные объекты могут входить, а другие могут быть удалены во время игры. , в OpenGL я сгруппировал атрибут 3D-объекта в единый буфер в шейдере (упрощенный код):


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

Сейчас я пытаюсь использовать этот единый буфер для всех трехмерных объектов игровой сцены, чтобы визуализировать их с помощью 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

Очевидно, что мое решение не будет работать, поскольку все команды Vulkan в буфере выполняются на GPU только после вызова vkQueueSubmit (). Но вызов _updateUniformBuffer (obj) (by memcpy (...)) «перемежается» с записью команды, и он выполняется немедленно, и поэтому последовательность перепутана, и, наконец, каждый объект не получит свои собственные атрибуты.

Таким образом, может возникнуть вопрос, каково решение для Vulkan для правильного многократного обновления унифицированного буфера для каждого объекта в течение одного прохода рендеринга и обеспечения того, чтобы каждый объект получил свои правильные данные атрибута?

Прежде чем опубликовать этот вопрос, я попытался подумать о следующих решениях, но ни одно из них не кажется удачным:

  • Использование render-pass-per-object и использования fence, чтобы убедиться, что один объект полностью отрисован, пока я не начну рендерить следующий. Если есть 1000 объектов, будет 1000 проходов рендеринга на кадр? Это невозможно.
  • Могу ли я повторно отправлять буфер команд в течение одного прохода рендеринга? Я имею в виду, что я отправляю буфер команд сразу после того, как команда draw для одного объекта построена для визуализации объекта, использую fence, чтобы убедиться, что рендеринг завершен, затем перехожу к следующему объекту. Это будет один проход рендеринга и 1000 вызовов vkQueueSubmit ()
  • Использование динамического унифицированного буфера, который создает огромный унифицированный буфер, содержит данные для 1000 объектов. Это сложно реализовать, так как номер объекта является динамическим.
  • Использование константы push? Это также невозможно, поскольку максимальный размер данных составляет всего 128 байт.

1 Ответ

0 голосов
/ 09 января 2019

Поскольку вы записываете команды рисования и их входные данные в форме униформ, для всех объектов в сцене перед тем, как какой-либо из них выполнит и прочитает свои входные данные, невозможно обойтись без хранилища для всех версий выделенный где-то единый буфер. Драйверы OpenGL ES делают это для вас: когда вы обновляете униформу, они внутренне выделяют новое пространство, записывают в нее новые униформы, а затем обновляют внутренний указатель, чтобы при следующем вызове использовались новые унифицированные данные вместо предыдущей униформы данные.

В Vulkan вы можете сделать это самостоятельно, и ваша третья идея наиболее близка к правильному. Есть несколько вариантов, но одно из самых простых:

Создайте большой VkBuffer и привяжите его к памяти. Вероятно, он должен быть достаточно большим, чтобы обрабатывать все унифицированные данные для типичного / среднего кадра. Начиная со смещения, равного нулю, для каждого отрисовки запишите новые формы с текущим смещением, повторно свяжите VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC в наборе дескрипторов с динамическим смещением, указывающим на новые унифицированные данные, а затем обновите смещение, чтобы формы следующего отрисовки были использованы будет размещен после тех, которые вы только что использовали.

В конце каждого кадра (предполагая один буфер команд на кадр), запомните, как далеко вы попали в буфер, и свяжите это с событием, которое сигнализирует о завершении этого буфера команд. Это событие сообщит вам, когда вы можете перезаписать область буфера, используемого в этом кадре. Если вам в конечном итоге понадобится больше места для униформы, прежде чем снова появится достаточно места, вы можете просто создать новый VkBuffer и начать его использовать, в конечном итоге возвращаясь к оригиналу, когда его данные будут удалены. Таким образом, вы можете получить динамический размер кольцевого буфера однородных данных, состоящих из нескольких VkBuffers.

...