Синхронизации в Vulkan (3 стр)
Автор: /A\
Примеры барьеров.
vkCmdDispatch(... ); // читаем из buffer vkCmdPipelineBarrier( VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, ... ); vkCmdDispatch( ... ); // пишем в buffer
Здесь глобальный барьер (global execution barrier), этого достаточно в случае записи после чтения, но глобальный барьер повлияет на другие команды, которые не используют buffer, поэтому перепишем код так:
vkCmdDispatch(... ); // читаем из buffer VkBufferMemoryBarrier barrier; barrier.srcAccessMask = 0; barrier.dstAccessMask = 0; barrier.buffer = buffer; barrier.offset = 0; barrier.size = VK_WHOLE_SIZE; vkCmdPipelineBarrier( VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, ..., 1, &barrier, ... ); vkCmdDispatch( ... ); // пишем в buffer
Теперь барьер затронет только эти две команды, а также последующие команды, если они используют buffer.
vkCmdDispatch(... ); // пишем в buffer VkBufferMemoryBarrier barrier; barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; barrier.buffer = buffer; barrier.offset = 0; barrier.size = VK_WHOLE_SIZE; vkCmdPipelineBarrier( VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, ..., 1, &barrier, ... ); vkCmdDispatch( ... ); // читаем buffer
Чтение после записи требует инвалидации кэша (memory dependency), в srcAccessMask указываем, что запись была в шейдере, в dstAccessMask указываем, что эти данные нужны нам для чтения в шейдере. Если неправильно указать dstAccessMask, то инвалидация кэша может произойти позднее, например на последнем этапе конвеера (bottom of pipe), где обычно происходят все синхронизации.
vkCmdDispatch(... ); // пишем в buffer VkBufferMemoryBarrier barrier; barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT; barrier.buffer = buffer; barrier.offset = 0; barrier.size = VK_WHOLE_SIZE; vkCmdPipelineBarrier( VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, ..., 1, &barrier, ... ); vkCmdDispatch( ... ); // перезаписываем buffer
Здесь мы указываем в каком порядке будет происходить запись, то есть сначала должен быть записан результат первого вызова vkCmdDispatch, а потом данные могут быть перезаписаны следующим вызовом vkCmdDispatch. В этом случае драйвер может произвести запись из кэша в глобальную память только после второго вызова, уже учитывая перезапись. Чтение данных буфера в шейдере приведет к неопределенному поведению.
vkCmdDispatch(... ); // читаем и пишем в buffer VkBufferMemoryBarrier barrier; barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; // | VK_ACCESS_SHADER_READ_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; barrier.buffer = buffer; barrier.offset = 0; barrier.size = VK_WHOLE_SIZE; vkCmdPipelineBarrier( VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, ..., 1, &barrier, ... ); vkCmdDispatch( ... ); // читаем и пишем в buffer
Здесь обе команды и читают и записывают данные. Для srcAccessMask указывать флаг VK_ACCESS_SHADER_READ_BIT не требуется. Для dstAccessMask обязательно указать флаг чтение и записи, чтобы был виден результат предыдущей записи.
Ообенности рисования в текстуру.
Если включен блендинг или логические операции, то обязательно в dstAccessMask указывать VK_ACCESS_COLOR_ATTACHMENT_READ_BIT и VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, в остальных случаях достаточно только VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT.
Если включен тест глубины, но выключена запись, то мы можем указать image layout как VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, такой image layout позволяет одновременно с тестом глубины читать текстуру в шейдере.
Избавляемся от ненужного разжатия текстуры.
Допустим, у нас есть временная текстура, которая используется в рендер пассе и других операциях, но содержимое ее часто перезаписывается.
// рисуем в image vkCmdBeginRenderPass(...); ... vkCmdEndRenderPass( ...); ... // пишем в image VkImageMemoryBarrier barrier = {}; barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT; barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; vkCmdPipelineBarrier( ..., 1, &barrier, …); vkCmdDispatch( ...);
Сделали правильный image layout transition, но получили долгое разжатие. Драйвер не знает будет ли перезаписана текстура полностью или только ее часть, поэтому обязан произвести разжатие данных при переходе на новый layout.
// рисуем в image vkCmdBeginRenderPass(...); ... vkCmdEndRenderPass( ...); ... // пишем в image, предыдущие данные не важны VkImageMemoryBarrier barrier = {}; barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_OUTPUT_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT; barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; vkCmdPipelineBarrier( ..., 1, &barrier, …); vkCmdDispatch( ...);
Предыдущий layout не определен, поэтому драйвер пропустит разжатие, также может отбросить предыдущую запись из кэша в память.
26 февраля 2019 (Обновление: 29 мар 2019)