Синхронизация

Последнее обновление: 14.04.2025

(Практический пример по этой статье: Синхронизация в GLFW)

Основная философия дизайна в Vulkan заключается в том, что синхронизация выполнения на GPU является явной. Порядок операций определяется нами с помощью различных примитивов синхронизации, которые сообщают драйверу порядок, в котором мы хотим, чтобы все выполнялось. Это означает, что многие вызовы API Vulkan, которые начинают выполнение работы на GPU, являются асинхронными, функции вернутся до завершения операции. И есть ряд событий, которые нам нужно упорядочить явно, поскольку они происходят на GPU, например:

  • Получение изображения из цепочки кадров

  • Выполнение команд, которые рисуют на полученном изображении

  • Отображение этого изображения на экране, возвращение его в цепочку кадров

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

Семафор используется для упорядочивания выполнения операций очереди.

Барьер (fence) также используется для синхронизации выполнения, но предназначен для упорядочивания выполнения на CPU, также известном как хост. И если хосту нужно знать, когда GPU закончил какую-то работу, то применяется барьер.

Таким образом, основное отличие между этими двумя типами примитивов в том, что семафоры используются для указания порядка выполнения операций на графическом процессоре, а барьеры - для синхронизации центрального процессора и графического процессора друг с другом. Основные сценарии для применения синхронизации: операции цепочки кадров и ожидание завершения предыдущего кадра. Для операций цепочки кадров применяются семафоры, потому что эти операции выполняются на графическом процессоре. Для ожидания завершения предыдущего кадра применяются барьеры, потому что нам нужно, чтобы центральный процесс ожидал завершения кадра. Это нужно для того, чтобы мы не рисовали больше одного кадра за раз. Поскольку мы перезаписываем буфер команд каждый кадр, мы не можем записывать работу следующего кадра в буфер команд, пока текущий кадр не завершит выполнение, так как мы не хотим перезаписывать текущее содержимое буфера команд, пока графический процессор использует его.

В Vulkan API семафоры представлены типом VkSemaphore, а барьеры - типом VkFence. Оба типа представляют непрозрачные указатели (opaque handle).

Создание семафоров

Для описания семафоров применяется структура VkSemaphoreCreateInfo:

typedef struct VkSemaphoreCreateInfo {
    VkStructureType           sType;
    const void*               pNext;
    VkSemaphoreCreateFlags    flags;
} VkSemaphoreCreateInfo;

Фактически здесь нет никаких обязательных полей, кроме sType, которое должно иметь значение VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO. Поэтому создание структуры довольно просто:

VkSemaphoreCreateInfo semaphoreInfo{};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;

А для создания семаформа применяется функция vkCreateSemaphore, в которую передается логическое устройство, структура VkSemaphoreCreateInfo, аллокатор памяти и указатель на создаваемый семафор:

VkResult vkCreateSemaphore(
    VkDevice                                    device,
    const VkSemaphoreCreateInfo*                pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkSemaphore*                                pSemaphore);

Пример создания семафора:

VkSemaphore mySemaphore;

VkSemaphoreCreateInfo semaphoreInfo{};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;

if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &mySemaphore) != VK_SUCCESS) {
    throw std::runtime_error("Не удалось создать семафор!");
}

Создание барьера

Для описания создания барьеров применяется структура VkFenceCreateInfo:

typedef struct VkFenceCreateInfo {
    VkStructureType       sType;
    const void*           pNext;
    VkFenceCreateFlags    flags;
} VkFenceCreateInfo;

Здесь аналогично можно указать только поле sType, которое должно иметь значение VK_STRUCTURE_TYPE_FENCE_CREATE_INFO. Пример определения структуры:

VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;

Также стоит отметить, что перечисление VkFenceCreateFlags имеет одно значение - VK_FENCE_CREATE_SIGNALED_BIT, которое переводит барьер в сигнальное состояние сразу при создании (по умолчанию барьер в несигнальном состоянии):

VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;

А для создания барьера применяется функция vkCreateSemaphore, в которую передается логическое устройство, структура VkFenceCreateInfo, аллокатор памяти и указатель на создаваемый барьер:

VkResult vkCreateFence(
    VkDevice                                    device,
    const VkFenceCreateInfo*                    pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkFence*                                    pFence);

Пример создания барьера:

VkFence myFence;

VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;

if (vkCreateFence(device, &fenceInfo, nullptr, &myFence) != VK_SUCCESS) {
    throw std::runtime_error("Не удалось создать барьер!");
}

Удаление семафора и барьера

После завершения работы семафор и барьер надо удалить. Для удаления семаформа применяется функция vkDestroySemaphore():

void vkDestroySemaphore(
    VkDevice                                    device,
    VkSemaphore                                 semaphore,
    const VkAllocationCallbacks*                pAllocator);

А для удаления барьера применяется функция vkDestroyFence():

void vkDestroyFence(
    VkDevice                                    device,
    VkFence                                     fence,
    const VkAllocationCallbacks*                pAllocator);

Обе функции однотипны - меняется только второй параметр, только в первом случае передается удаляемый семафор, а во втором случае - удаляемый барьер.

(Практический пример по этой статье: Синхронизация в GLFW)

Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850