Войти
ПрограммированиеСтатьиГрафика

Синхронизации в Vulkan (4 стр)

Автор:

Здесь и далее цветами обозначено:
Зеленый - активный этап конвейера.
Серый - неактивный этап, может не использоваться, либо простаивает из-за синхронизации.
Оранжевый - этап, попадающий в srcStageMask барьера.
Красный - этап, ожидающий синхронизации, указан в dstStageMask барьера.
Зеленый после красного - этап начинает выполняться после блокировки.


Синхронизация между этапами конвейера
Здесь обычная ситуация - обновляем буфер юниформ и читаем его во всех шейдерах графического конвейера.

vkCmdUpdateBuffer( ... );  // 1
vkCmdBeginRenderPass( ... );
vkCmdDraw( ... );  // 2
vkCmdDrawIndirect( ... );  // 3
vkCmdDraw( ... );  // 4
vkCmdDrawIndirect( ... );  // 5
vkCmdEndRenderPass( ... );
Без синхронизаций получаем неопределенное поведение - рисование может произойти и перед копированием и параллельно с ним, обновленные данные могут не загрузиться в кэш шейдеров.
copy-draw-sync-1 | Синхронизации в Vulkan


Поэтому добавляем memory dependency перед началом рендер пасса.
vkCmdUpdateBuffer( ... );  // 1
VkBufferMemoryBarrier  barrier;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_UNIFORM_READ_BIT;
srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
dstStageMask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
               VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT |
               VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT |
               VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
vkCmdPipelineBarrier( srcStageMask, dstStageMask, ... &barrier );
vkCmdBeginRenderPass( ... );
vkCmdDraw( ... );  // 2
vkCmdDrawIndirect( ... );  // 3
vkCmdDraw( ... );  // 4
vkCmdDrawIndirect( ... );  // 5
vkCmdEndRenderPass( ... );
Рисование будет выполняться до вершинного шейдера, затем будет дожидаться завершения копирования. Указывать все шейдеры не обязательно, так как dstStageMask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT включает в себя и все последующие этапы рисования, а dstAccessMask = VK_ACCESS_UNIFORM_READ_BIT делает память видимой для всех последующих чтений юниформов, но не гарантирует, например, что этот же буфер, переданный как shader storage buffer, будет читать обновленные данные.
copy-draw-sync-2 | Синхронизации в Vulkan


Параллельное выполнение команд.
vkCmdDispatch( ... );  // 1
vkCmdDispatch( ... );  // 2
vkCmdDispatch( ... );  // 3
Вычисления распределяются на все свободные вычислительные ядра (compute units), на картинке показано два варианта, первый - когда все три команды занимают все вычислительные ядра и выполняются параллельно, второй - вычислительных ядер не хватает и команда (3) будет выполняться позднее. Этапы в начале и конце конвейера (top of pipe, bottom of pipe) не нагружают вычислительные ядра и добавлены только чтобы показать все активные этапы конвейера.
Рисование также может происходить параллельно, но там намного больше ограничений, поэтому здесь оно разбираться не будет.
compute-sync-1 | Синхронизации в Vulkan

// пишем в buffer
vkCmdDispatch( ... );  // 1

// делаем запись в buffer видимой для всех последующих команд
VkBufferMemoryBarrier  barrier;
barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
barrier.buffer = buffer;
vkCmdPipelineBarrier( VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
                     VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, ... &barrier );

// не используем buffer
vkCmdDispatch( ... );  // 2

// читаем из buffer
vkCmdDispatch( ... );  // 3
Барьер можно ставить сразу после записи, но это будет препятствовать параллельному выполнению других команд.
compute-sync-2 | Синхронизации в Vulkan


Правильно размещать барьер перед чтением данных.
// пишем в buffer
vkCmdDispatch( ... );  // 1

// не используем buffer
vkCmdDispatch( ... );  // 2

// делаем запись в buffer видимой для всех последующих команд
VkBufferMemoryBarrier  barrier;
barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
barrier.buffer = buffer;
vkCmdPipelineBarrier( VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
                     VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, ... &barrier );

// читаем из buffer
vkCmdDispatch( ... );  // 3
compute-sync-3 | Синхронизации в Vulkan


Синхронизации между командными буферами.
vkBeginCommandBuffer( cmd1, ... );
vkCmdDraw( cmd1, ... );
vkCmdPipelineBarrier( cmd1, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
                            VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, ... ); // 1
vkCmdDraw( cmd1, ... );
vkEndCommandBuffer( cmd1 );

vkBeginCommandBuffer( cmd2, ... );
vkCmdDraw( cmd2, ... );
vkCmdPipelineBarrier( cmd2, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
                            VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, ... ); // 2
vkCmdDraw( cmd2, ... );
vkEndCommandBuffer( cmd2 );

vkBeginCommandBuffer( cmd3, ... );
vkCmdCopyBuffer( cmd3, ... );
vkCmdTraceRaysNV( cmd3, ... );
vkEndCommandBuffer( cmd3 );

VkSubmitInfo  submit = {};
submit.pCommandBuffers = { cmd1, cmd2, cmd3 };
vkQueueSubmit( ..., &submit, ... );
В барьере dsStageMask включает в себя и все последующие этапы, в том числе и VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT который происходит при следующем запуске конвейера. Получается, что второй командный буфер (cmd2) может начать выполнение после барьера 1, а копирование в третьем командном буфере (cmd3) начнется только после барьера 2, но само копирование может выполняться параллельно с рисованием.
Команда трассировки лучей не зависит ни от одного другого этапа конвейера кроме VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_NV, поэтому может начать выполняться в любое время и параллельно всем остальным командам.
command-buffer-sync | Синхронизации в Vulkan

Страницы: 1 2 3 4 5 Следующая »

#Vulkan

26 февраля 2019 (Обновление: 29 мар. 2019)

Комментарии [36]