Vulkan: как безопасно заменить командный буфер

#c #vulkan

#c #vulkan

Вопрос:

Когда моя сцена обновляется (например, появляется новая модель), я перезаписываю свои буферы командной строки draw в новые командные буферы ( tempBuffer ) с помощью функции buildCommandBuffers .

У меня есть массив drawCmdBuffers , который используется в основной процедуре рисования.

Чтобы немного усложнить ситуацию, моя buildCommandBuffers функция может вызываться из таймера в отдельном потоке, поэтому функция визуализации потенциально все еще зацикливается, пока происходит это обновление. Это связано с тем, что обновление моей сцены может занять некоторое время, и мне нужно заблокировать память сцены, пока я создаю буфер команд (это отдельная проблема .. но каждая добавленная сетка запускает обновление сцены, и мне нужно иметь возможность буферизировать эти обновления).

У меня есть забор для каждого командного буфера, поэтому, как только я записал в свой «tempBuffer», я делаю следующее:

         VK_CHECK_RESULT(vkEndCommandBuffer(tempBuffers[i]));        
        // wait for this command buffer to finish (we might be re-recording)
        if (imagesInFlight[_image_index] != VK_NULL_HANDLE) 
        {
            vkWaitForFences(device, 1, amp;imagesInFlight[_image_index], VK_TRUE, UINT64_MAX);
        }
        // destroy in use command buffer
        
        vkFreeCommandBuffers(device, cmdPool, 1, amp;drawCmdBuffers[i]);
        drawCmdBuffers[i] = tempBuffers[i];
 

Безопасно ли это? Или есть что-то, что я могу сделать, чтобы остановить доступ к drawCmdBuffers[i] нему во время его обновления?

Комментарии:

1. Похоже, у вас есть забор для каждого кадра, а не для каждого командного буфера, верно?

2. У меня есть буфер команд для каждого кадра, поэтому они одинаковы

3. Я опубликовал ответ ниже, но я бы также рекомендовал перепроверить, что вам нужно повторно использовать командные буферы таким образом. Большинство приложений Vulkan, включая все «профессиональные», о которых я знаю, просто перезаписывают буферы команд в каждом кадре. Если запись не требует большой обработки в вашем коде, фактические вызовы записи достаточно дешевы, поэтому нет большой пользы от повторного использования командных буферов. И повторное использование может добавить много сложностей.

4. запись в моем приложении происходит очень медленно, в нем десятки тысяч объектов. Перезапись каждого кадра нецелесообразна. Мне действительно нужно ускорить запись, но это еще одна проблема на другой день!

Ответ №1:

Похоже, вам нужна синхронизация CPU-CPU (обычно мьютекс) для доступа к imagesInFlight[] массиву и drawCommandBuffers[] / или отдельным элементам внутри него. В противном случае вы могли бы ожидать блокировки для самого последнего кадра, в то время как другой поток отправляет командные буферы для следующего кадра — и в этом случае, даже когда ваше ожидание завершится, графический процессор все равно будет использовать командный буфер.

Предполагая, что вам действительно нужно повторно использовать буферы команд, а не просто перезаписывать их каждый кадр, одним из подходов было бы заставить buildCommandBuffers поток просто создать список изменений drawCmdBuffers . Что-то вроде:

Визуализировать поток:

 for each frame:
  acquire mutex for command buffer edit list
  for (i, cmdbuf) in command buffer edit list:
    push drawCmdBuffers[i] and most recent frame fence to a deferred-destroy queue
    drawCmdBuffers[i] = cmdbuf;
  clear edit list
  release mutex
  submit render commands for frame, using drawCmdBuffers
 

Поток обновления:

 on_timer:
  acquire mutex for command buffer edit list
  while fence at front of deferred-destroy queue has signaled:
    free command buffer at front of queue
    pop queue
  release mutex
  for each command buffer that needs to be replaced:
    record command buffer
    acquire mutex for command buffer edit list
    append (cmdbuf_index, cmdbuf) to edit list
    release mutex
 

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