Vulkan - синхронизация доступа к одному буферу - PullRequest
0 голосов
/ 13 октября 2018

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

Я новичок в Vulkan, но нахожу синхронизациюсамая сложная часть, чтобы получить мою голову вокруг.Я просмотрел спецификацию Vulkan, примеры синхронизации (https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples), Учебное пособие по Vulkan (https://vulkan -tutorial.com / ), а также связку постов переполнения стека.Я до сих пор не уверен, что действительно «получаю это».

Чтобы помочь моему обучению, я пытаюсь написать следующее:

  • Наличие нескольких кадров в полете, как описано в руководстве Vulkan (https://vulkan -tutorial.com / Drawing_a_triangle / Drawing / Rendering_and_presentation # page_Frames_in_flight ).
  • Пусть вершинный шейдер считывается из одного буфера хранения.
  • Обновляйте часть буфера хранения свежими данными каждый кадр через локальный и согласованный с хостом «промежуточный буфер».
  • Будет несколько промежуточных буферов - по одному для каждого кадра в полете.

Я думаю, что командный буфер для кадра N (0 <= N <Максимальное количество кадров в полете) должен выглядеть примерно так: </p>

// Many parameters omitted for brevity
vkCmdCopyBuffer(commandBuffer[N], stagingBuffer[N], storageBuffer, ...);

VkMemoryBarrier barrier = {0};
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;

vkCmdPipelineBarrier(
    commandBuffer[N],
    VK_PIPELINE_STAGE_TRANSFER_BIT,
    VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
    0,
    1,
    &barrier,
    ...
);

// begin render pass
// drawing commands
// end render pass

vkCmdPipelineBarrier(
    commandBuffer[N],
    VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
    VK_PIPELINE_STAGE_TRANSFER_BIT,
    0,
    0,
    NULL,
    ...
);

Я считаю, что первый конвейербарьер необходим, чтобы предотвратить появление графического процессораm позволяя вершинному шейдеру читать из буфера хранения во время его обновления.

Я думаю, что второй барьер конвейера необходим для предотвращения выполнения команды vkCmdCopyBuffers из следующего кадра до тех пор, пока вершинный шейдер не изпредыдущий кадр сделан чтением буфера хранения.Насколько я понимаю, здесь не нужен барьер памяти, потому что это «опасность для войны» (https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples#first-draw-samples-a-texture-in-the-fragment-shader-second-draw-writes-to-that-texture-as-a-color-attachment).

Правильно ли мое предложение? Или я что-то неправильно понял?

NB. Я знаю, что подход, который я использую выше (даже если он правильный), может быть не лучшим - например, возможно, наличие N буферов хранения для каждого кадра в полете обеспечит лучшую производительность. Однако я надеюсь,чтобы лучше понять синхронизацию, прежде чем продолжить.

Буду признателен за любую помощь, которую могут оказать вам мастера Вулкана!

1 Ответ

0 голосов
/ 15 октября 2018

Смысл учебной главы состоит в том, чтобы сказать, что Hello - это не "настоящее" приложение.Привет может иметь бесконечное количество кадров в полете.Но это не то, что могло бы произойти в «реальном» приложении.

Драйверы и слои могут использовать очистку заборов, что подразумевает, что вещи больше не находятся в полете.Если такой синхронизации никогда не будет, метаданные могут накапливаться.

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

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

Ваши конвейерные барьеры кажутся достаточными для синхронизации storageBuffer.Хотя в некоторых случаях может быть предпочтительнее использовать выделенную очередь передачи (что будет означать другую схему синхронизации).И вместо этого может быть предпочтительнее использовать эквивалентные зависимости внешнего подпроцесса.

stagingBuffer необходимо синхронизировать как с доменом хоста, так и с доменом устройства.

Как уже говорилось, может быть ненужно иметь N изstagingBuffer s.Если вы обновляете новые данные для каждого кадра, старые данные в идеале должны уже обрабатываться (что может быть проверено с помощью ограждения).

Вы определяете stagingBuffer как связное, поэтому вам не нужно делатьничего на хосте, кроме записи сопоставленного указателя. Если после этого вызывается vkQueueSubmit, то все эти записи неявно синхронизируются с помощью гарантии порядка записи хоста.

Вы также должны убедиться, что хост не начинает запись памяти, пока устройствовсе еще читает это.Должно быть какое-то Заборное ожидание, прежде чем эти записи.

...