четверг, 17 марта 2016 г.

Vulkan API. Memory Barriers and Implicit Ordering Guarantees.

Вот я и добрался до последней главы раздела "Synchronization and Cache Control", перевод этого раздела оказался достаточно "стремным", как вообщем-то и текст оригинала. После этой статьи я приведу еще одну итоговую, где постараюсь в сжатом виде пересказать содержание последних серий, ну а пока - Memory Barriers и Implicit Ordering Guarantees.


Memory Barriers

Барьеры памяти разделяют доступ к памяти между двумя наборами команд. Вулкан предоставляет три типа барьеров памяти - для глобальной памяти, для памяти буферов и памяти изображения.

Global Memory Barriers
Барьеры данного типа определяются через инстанс структуры VkMemoryBarrier. Этот тип барьера применяется к доступу к памяти всех объектов, находящихся в памяти в момент выполнения. Структура VkMemoryBarrier определяется как:
typedef struct VkMemoryBarrier {
VkStructureType sType;
const void * pNext;
VkAccessFlags srcAccessMask;
VkAccessFlags dstAccessMask;
} VkMemoryBarrier;

• sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER.
• pNext = NULL.
• srcAccessMask - маска содержащая биты из VkAccessFlagBits, определяет доступ к памяти первым набором команд зависимости (dependency).
• dstAccessMask - маска содержащая биты из VkAccessFlagBits, определяет доступ к памяти вторым набором команд зависимости (dependency).

srcAccessMask и dstAccessMask, вместе с srcStageMask и dstStageMask из
vkCmdPipelineBarrier, определяют две половинки зависимости памяти и зависимости выполнения. Доступ к памяти, используя набор типов доступа, определенных в srcAccessMask, выполняемый первым набором команд, на стадии конвейера, указанной в srcStageMask, должен быть завершен и память должна быть доступна последующим командам с типом доступа указанным в dstAccessMask выполняющимися на стадии конвейера указанной в dstStageMask. Если барьер по-регионный, то эти требования применимы только к вызовам внутри того же региона буфера кадра, для стадий конвейера, работающих в пространстве кадра. Зависимость выполнения гарантирует что выполнение команд для стадий конвейера, определенных в dstStageMask, не начнется пока не завершится выполнение команд из исходных стадий конвейера.

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

srcAccessMask и dstAccessMask состоят из следующих битов:
typedef enum VkAccessFlagBits {
VK_ACCESS_INDIRECT_COMMAND_READ_BIT = 0x00000001,
VK_ACCESS_INDEX_READ_BIT = 0x00000002,
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT = 0x00000004,
VK_ACCESS_UNIFORM_READ_BIT = 0x00000008,
VK_ACCESS_INPUT_ATTACHMENT_READ_BIT = 0x00000010,
VK_ACCESS_SHADER_READ_BIT = 0x00000020,
VK_ACCESS_SHADER_WRITE_BIT = 0x00000040,
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT = 0x00000080,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT = 0x00000100,
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT = 0x00000200,
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT = 0x00000400,
VK_ACCESS_TRANSFER_READ_BIT = 0x00000800,
VK_ACCESS_TRANSFER_WRITE_BIT = 0x00001000,
VK_ACCESS_HOST_READ_BIT = 0x00002000,
VK_ACCESS_HOST_WRITE_BIT = 0x00004000,
VK_ACCESS_MEMORY_READ_BIT = 0x00008000,
VK_ACCESS_MEMORY_WRITE_BIT = 0x00010000,
} VkAccessFlagBits;

Биты VkAccessFlagBits имеют следующее значение:
• VK_ACCESS_INDIRECT_COMMAND_READ_BIT - указывает на то, что доступ на чтение неявный, через команды непрямого рисования.
• VK_ACCESS_INDEX_READ_BIT  - указывает что доступ это чтение индексного буфера.
• VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT - указывает что доступ это чтение через вершинные атрибуты.
• VK_ACCESS_UNIFORM_READ_BIT - указывает что доступ это чтение через буфер юниформ или через дескриптор динамического буфера юниформ..
• VK_ACCESS_INPUT_ATTACHMENT_READ_BIT - указывает что доступ это чтение через входные аттачменты.
• VK_ACCESS_SHADER_READ_BIT - указывает что доступ это чтение из шейдера через любые типы дескрипторов.
• VK_ACCESS_SHADER_WRITE_BIT - указывает что доступ это запись или атомарная операция в шейдере через тот же дескриптор VK_ACCESS_SHADER_READ_BIT.
• VK_ACCESS_COLOR_ATTACHMENT_READ_BIT - указывает что доступ это чтение через color attachment.
• VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT - указывает что доступ это запись через color или resolve аттачмент.
• VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT - указывает что доступ это чтение через depth/stencil аттачмент.
• VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT - указывает что доступ это запись через depth/stencil аттачмент.
• VK_ACCESS_TRANSFER_READ_BIT - указывает что доступ это чтение из операций передачи (copy, blit, resolve, и т.д.). Полный список операций передачи можно посмотреть в описании VK_PIPELINE_STAGE_TRANSFER_BIT.
• VK_ACCESS_TRANSFER_WRITE_BIT - указывает что доступ это запись из операций передачи (copy, blit, resolve, и т.д.).
• VK_ACCESS_HOST_READ_BIT - указывает что доступ это чтение со стороны хоста.
• VK_ACCESS_HOST_WRITE_BIT - указывает что доступ это запись со стороны хоста.
• VK_ACCESS_MEMORY_READ_BIT - указывает что доступ это чтение через неопределенный модуль, прикрепленный к памяти.
Этот модуль может быть внешним к устройству Вулкана или не являться частью ядра конвейера Вулкана. Когда этот бит включен в dstAccessMask, все операции записи, использующие типы доступа из srcAccessMask, выполняемые стадией конвейера, должны быть видимы в памяти.
• VK_ACCESS_MEMORY_WRITE_BIT - указывает что доступ это запись через неопределенный модуль, прикрепленный к памяти. Когда этот бит включен в srcAccessMask, все операции из dstAccessMask и из стадий конвейера dstStageMask будут наблюдать за результатами команд, выполнеными до барьера. Когда этот бит включен в dstAccessMask, все операции записи, использующие тип доступа, указанный в srcAccessMask, выполняемые стадиями конвейера указанынми в srcStageMask должны быть видимые в памяти.

Color и depth/stencil аттачменты читают и пишут автоматически (без зависимостей памяти или исполнения) когерентны и упорядочены относительно самих себя и друг-друга для данного сэмпла внутри subpass-а инстанса render pass-а, выполняемых в порядке указанном API.

Операции чтения/записи шейдера через две переменные (в той же или разных вызовах шейдера), помеченные как Coherent и которые используют тот же image view или buffer view, автоматически когерентны относительно друг-друга, но требуют зависимость выполнения, если нужен порядок выполнения. Аналогично, атомарные операции в шейдере когерентны друг с другом и с переменными отмеченными как Coherent. Не когерентные доступы шейдера к памяти требуют доступную зависимость памяти для записи и видимость для чтения.

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

Buffer Memory Barriers
Тип барьера буфера памяти определяется через инстанс структуры VkBufferMemoryBarrier. Этот тип барьера применяется только к доступу к памяти, включающей определенный диапазон указанного буфера. То есть, зависимость памяти формируется из барьера буфера памяти, который распространяется на весь диапазон буфера. Он так же используется при передачи владения диапазоном буфера из одного семейства очереди в другое.
Структура VkBufferMemoryBarrier определена как:
typedef struct VkBufferMemoryBarrier {
VkStructureType sType;
const void * pNext;
VkAccessFlags srcAccessMask;
VkAccessFlags dstAccessMask;
uint32_t srcQueueFamilyIndex;
uint32_t dstQueueFamilyIndex;
VkBuffer buffer;
VkDeviceSize offset;
VkDeviceSize size;
} VkBufferMemoryBarrier;

• sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER
• pNext=NULL.
• srcAccessMask - маска доступа к памяти первого набора команд, участвующего в зависимости.
• dstAccessMask - маска доступа к памяти второго набора команд, участвующего в зависимости.
• srcQueueFamilyIndex - индекс семейства очереди, которая передает владение диапазоном буфера другой очереди, или VK_QUEUE_FAMILY_IGNORED, если не будет передачи владения.
• dstQueueFamilyIndex - индекс семейства очереди, которой будет передано владение диапазоном буфера из другой очереди, или VK_QUEUE_FAMILY_IGNORED, если не будет передачи владения.
• buffer - хэндл буфера, на который действует барьер.
• offset - смещение в байтах в памяти буфера, относительно базового смещения указанного в vkBindBufferMemory.
• size - размер в байтах области буфера, с которой будет осуществляться операция, или VK_WHOLE_SIZE чтоб использовать весь диапазон от offset до конца буфера.
• Если буфер был создан с режимом VK_SHARING_MODE_CONCURRENT, то  srcQueueFamilyIndex и dstQueueFamilyIndex должны быть оба равны VK_QUEUE_FAMILY_IGNORED
• Если буфер был создан с режимом VK_SHARING_MODE_EXCLUSIVE, то srcQueueFamilyIndex и dstQueueFamilyIndex должны либо оба быть VK_QUEUE_FAMILY_IGNORED или оба быть валидным семейством очередей.
• Если буфер был создан с режимом  VK_SHARING_MODE_EXCLUSIVE, и srcQueueFamilyIndex и dstQueueFamilyIndex валидные семейства очередей, то как минимум одно из них должно быть таким же как и семейство очереди, которая будет выполнять этот барьер.


Image Memory Barriers
Барьер памяти изображения определяется через инстанс структуры VkImageMemoryBarrier. Этот тип барьера применяется только к операциям доступа к указанному диапазону ресурсов изображения указанного объекта изображения. То есть, зависимость памяти, сформированная из барьера памяти изображения распространяется на указанные ресурсы изображения. Он так же используется при передачи структуры изображения для диапазона ресурсов изображения, или для передачи владения диапазоном ресурсов изображения из одного семейства очередей другому. Структура VkImageMemoryBarrier определяется как:
typedef struct VkImageMemoryBarrier {
VkStructureType sType;
const void * pNext;
VkAccessFlags srcAccessMask;
VkAccessFlags dstAccessMask;
VkImageLayout oldLayout;
VkImageLayout newLayout;
uint32_t srcQueueFamilyIndex;
uint32_t dstQueueFamilyIndex;
VkImage image;
VkImageSubresourceRange subresourceRange;
} VkImageMemoryBarrier;

• sType=VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER
• pNext=NULL
• srcAccessMask - маска доступа к памяти первого набора команд, участвующего в зависимости.
• dstAccessMask - маска доступа к памяти второго набора команд, участвующего в зависимости.
• oldLayout - описывает текущую структуру ресурсов изображения.
• newLayout - описывает новую структуру ресурсов изображения.
• srcQueueFamilyIndex - индекс семейства очереди, которая передает владение диапазоном буфера другой очереди, или VK_QUEUE_FAMILY_IGNORED, если не будет передачи владения.
• dstQueueFamilyIndex - индекс семейства очереди, которой будет передано владение диапазоном буфера из другой очереди, или VK_QUEUE_FAMILY_IGNORED, если не будет передачи владения.
• image - хэндл изображения, на операции с которым устанавливается барьер.
• subresourceRange - описывает область памяти изображения и набор ресурсов изображения, которые будут модифицироваться.
• oldLayout - должен быть  VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PREINITIALIZED или текущая структура области изображения, на которую распространяется барьер.
• newLayout - не должен быть VK_IMAGE_LAYOUT_UNDEFINED или VK_IMAGE_LAYOUT_PREINITIALIZED
• Если image был создан с режимом VK_SHARING_MODE_CONCURRENT то  srcQueueFamilyIndex и dstQueueFamilyIndex должны быть VK_QUEUE_FAMILY_IGNORED
• Если image был создан с режимом VK_SHARING_MODE_EXCLUSIVE то  srcQueueFamilyIndex и dstQueueFamilyIndex должны быть либо VK_QUEUE_FAMILY_IGNORED либо валидными семействами очередей.
• Если image был создан с режимом VK_SHARING_MODE_EXCLUSIVE, srcQueueFamilyIndex и dstQueueFamilyIndex валидные семейства очередей и как минимум одно из них должно быть тем же что и у очереди, которая выполняет этот барьер.
• Если image это depth/stencil формат с обоими, depth и stencil компонентами, то маска  aspectMask в subresourceRange должна включать оба бита,  VK_IMAGE_ASPECT_DEPTH_BIT и VK_IMAGE_ASPECT_STENCIL_BIT
• Если oldLayout или newLayout это  VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, то image должен быть создан с битом VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
• Если oldLayout или newLayout это  VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, то image должен быть создан с битом VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
• Если oldLayout или newLayout это VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, то image должен быть создан с битом VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
• Если oldLayout или newLayout это VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL то image должен быть создан с битом VK_IMAGE_USAGE_SAMPLED_BIT или VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT
•  Если oldLayout или newLayout это VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL то image должен быть создан с битом VK_IMAGE_USAGE_TRANSFER_SRC_BIT
•  Если oldLayout или newLayout это VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL то image должен быть создан с битом VK_IMAGE_USAGE_TRANSFER_DST_BIT

Если oldLayout отличается от newLayout, то передача структуры происходит как часть барьера памяти изображения, воздействуя на данные, содержащиеся в области изображения, указанные в subresourceRange.
Если oldLayout это VK_IMAGE_LAYOUT_UNDEFINED, то данные станут неопределенными после передачи структуры. Это может позволить осуществить более эффективную передачу, так как данные могут быть отброшены. Передача структуры должна осуществляться после завершения всех операций, использующих старую структуру, и перед началом операций использующих новую структуру. Это достигается за счет того, что зависимость памяти между предыдущим доступом и передачей структуры, так же как и между передачей структуры и последующим доступом, происходит между двумя зависимостями памяти в барьере памяти изображения.
Передача структуры, выполняющаяся через барьер памяти изображения автоматически упорядочивается относительно других передач структуры, включая те, что являются частью инстанса render pass-а.

Implicit Ordering Guarantees

Отправка команд в буфер команд, операции с распределенной памятью (sparse  memory operations), сигналы fence-объектов, сигналы и ожидания на семафорах - все они реализуют неявный барьер памяти. Они предоставляют следующие гарантии:
После fence-объекта или семафора гарантируется что:
• Все команды, в любом буфере команд, оправленные перед этим в очередь, и включающие задачи отправки сигналов fence-объектам завершили выполнение.
• Результат работы этих команд доступен любой команде, следующей за ожидающим семафором. Если семафор является частью структуры VkSubmitInfo, переданной в vkQueueSubmit, то результаты так же видимы на этапах конвейера, указанных в pWaitDstStageMask, соответствующей семафору. Если семафор является частью структуры VkSubmitInfo, переданной в vkQueueBindSparse, то результаты видимы всем этапам конвейера для таких же команд.
• Все операции sparse binding, отправленные в очередь до fence-объекта и включающие задачи, отправляющие сигналы fence-объекту, были завершены.
• Биндинг, выполненный этими операциями, доступен всем командам и операциям sparse binding-а (в любой из очередей), которые следуют за семафором. Если семафор является частью структуры VkSubmitInfo, переданной в vkQueueSubmit, то результаты так же доступны указанным в pWaitDstStageMask стадиям конвейера, для команд, следующих за семафором. Если семафор является частью структуры VkSubmitInfo, переданной в vkQueueBindSparse, то результаты видимы всем этапам конвейера для таких же команд.
• Все объекты и данные, которые были перед просигналившим fence-объектом могут быть удалены, включая сам буфер команд.
• fence-объект может быть уничтожен
• семафоры могут быть уничтожены.

Эти правила определяют как сигналы и ожидающие операции формируют половинки неявной зависимости. Сигнализирующий семафор или fence-объект гарантирует что предыдущие задачи были завершены и их результат доступен последующим командам. Ожидание на семафоре или на fence-объекте, до отправки следующей порции команд, гарантирует что результаты работы, выполненной до примитива синхронизации, будут доступны для задач, выполняющихся на стадии конвейера, указанной в pWaitDstStageMask.

Команда vkQueueWaitIdle предоставляет автоматическое упорядочивание, эквивалентное использованию fence-объектов в самой последней задаче, поставленной в очередь и ожидающей этот fence-объект. vkDeviceWaitIdle предоставляет синхронизацию, эквивалентную vkQueueWaitIdle, но на всех очередях этого устройства.

Сигналы семафора или fence-объекта не гарантируют что операции записи устройства будут доступны хосту.

Когда пакет буферов команд отправляется в очередь через vkQueueSubmit, гарантируется что запись хостом в отображаемую память устройства, котрая осуществлялась до вызова vkQueueSubmit, будет видима буферам команд в этом пакете, если память устройства когерентна или указанный диапазон памяти был очищен через vkFlushMappedMemoryRanges.

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

Отправить комментарий