(Практический пример по этой статье: Рендеринг и вывод на экран в GLFW)
В Vulkan рендеринг проходит ряд этапов:
Ожидание завершения предыдущего кадра
Получение изображения из цепочки кадров
Запись буфера команд, который рисует сцену на этом изображении
Отправка записанного буфера команд
Отображение изображения из цепочки кадров
Для ожидания завершения предыдущего кадра применяются барьеры. Для ожидания перехода барьера в сигнальное состояние применяется функция vkWaitForFences:
VkResult vkWaitForFences(
VkDevice device,
uint32_t fenceCount,
const VkFence* pFences,
VkBool32 waitAll,clean
uint64_t timeout);
Она принимает следующие параметры:
device: логическое устройство, которое владеет барьерами.
fenceCount: количество барьеров, которые нужно ждать.
pFences: указатель на массив барьеров.
waitAll: условие, которое должно быть выполнено для успешной разблокировки ожидания.
Если waitAll равно VK_TRUE, то условие заключается в том, что все барьеры в pFences находятся в сигнальном состоянии. В противном случае условие заключается в том, что хотя бы
один барьер в pFences находится в сигнальном состоянии
timeout: период ожидания в наносекундах
Функция vkWaitForFences принимает массив барьеров и ждет на хосте, пока какой-либо или все барьеры не перейдут в сигнальное состояние, и после этого функция завершает свое выполнение.
После завершения ожидания нам необходимо вручную сбросить барьер в несигнальное состояние с помощью вызова vkResetFences():
VkResult vkResetFences(
VkDevice device,
uint32_t fenceCount,
const VkFence* pFences);
В функцию передаются логическое устройство, массив барьеров, сбрасываемых в несигнальное состояние, и количество барьеров в этом массиве.
Общий принцип выглядит седующим образом:
VkDevice device; // ранее полученное логическое устройство VkFence inFlightFence; // барьер для гарантии, что одновременно рендерится только один кадр ........................... // ожидаем получения сигнала от барьера vkWaitForFences(device, 1, &inFlightFence, VK_TRUE, UINT64_MAX); // сбрасываем барьер в несигнальное состояние vkResetFences(device, 1, &inFlightFence); // далее работаем с новым кадром
Когда обраьатывается предыдущий кадр, на вызове функции vkWaitForFences() ожидаем завершения обработки. Когда предыдущий кадр обработан, барьер inFlightFence получает сигнал, а функция
завершает выполнения. Сбрасываем барьер в несигнальное состояние и начинаем обработку нового кадра.
Для получения изображений из цепочки кадров применяется функция
VkResult vkAcquireNextImageKHR(
VkDevice device,
VkSwapchainKHR swapchain,
uint64_t timeout,
VkSemaphore semaphore,
VkFence fence,
uint32_t* pImageIndex);
Функция принимает следующие параметры:
device: устройство, связанное с цепочкой кадров.
swapchain: цепочка кадров, из которой извлекается изображение.
timeout: тайм-аут в наносекундах, который указывает, как долго функция ждет, если изображение недоступно.
semaphore: VK_NULL_HANDLE или семафор для подачи сигнала.
fence: VK_NULL_HANDLE или барьер для подачи сигнала.
pImageIndex: указатель на uint32_t, в котором возвращается индекс следующего изображения для использования (т. е. индекс в массиве изображений, возвращаемых функцией
vkGetSwapchainImagesKHR()).
Функция vkResetCommandBuffer() сбрасывает буфер команд в начальное состояние:
VkResult vkResetCommandBuffer(VkCommandBuffer commandBuffer, VkCommandBufferResetFlags flags);
В функцию передается сбрасываемый буфер команд и флаг перечисления VkCommandBufferResetFlags. Это перечисление определяет только один флаг:
typedef enum VkCommandBufferResetFlagBits {
VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT = 0x00000001,
} VkCommandBufferResetFlagBits;
Флаг VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT указывает, что большинство или все ресурсы памяти, которыми в данный момент владеет буфер команд,
должны быть возвращены в родительский пул команд.
Вместо этого флага мы можем передать число 0. В этом случае буфер команд может удерживать ресурсы памяти и повторно использовать их при записи команд.
Но вне зависимости от того, какое значение передается - VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT или 0, буфер команд сбрасывается в начальное состояние
Отправка и синхронизация очереди настраивается с помощью параметров в структуре VkSubmitInfo:
typedef struct VkSubmitInfo {
VkStructureType sType;
const void* pNext;
uint32_t waitSemaphoreCount;
const VkSemaphore* pWaitSemaphores;
const VkPipelineStageFlags* pWaitDstStageMask;
uint32_t commandBufferCount;
const VkCommandBuffer* pCommandBuffers;
uint32_t signalSemaphoreCount;
const VkSemaphore* pSignalSemaphores;
} VkSubmitInfo;
Поля структуры:
sType: тип структуры VK_STRUCTURE_TYPE_SUBMIT_INFO.
pNext: NULL или указатель на структуру, расширяющую эту структуру.
waitSemaphoreCount: количество семафоров, которые следует ожидать перед выполнением буфера команд.
pWaitSemaphores: указатель на массив семафоров, которые следует ожидать перед выполнением буфера команд
pWaitDstStageMask: указатель на массив стадий конвейера, в которых будет происходить каждое соответствующее ожидание семафора. Каждая стадия конвейера фактически описывается одной из констант перечисления
VkPipelineStageFlags, которое содержит несколько десятков стадий, поэтому приведу лишь некоторые:
VK_PIPELINE_STAGE_NONE: не указывает ни на какие этапы выполнения.
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT: этап конвейера, на котором потребляются буферы вершин и индексов.
VK_PIPELINE_STAGE_VERTEX_SHADER_BIT: этап вершинного шейдера.
VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT: этап шейдера управления тесселяцией.
VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT: этап шейдера оценки тесселяции.
VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT: этап геометрического шейдера.
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT: этап фрагментного шейдера.
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT: стадия конвейера после смешивания цветов, на которой из конвейера выводятся окончательные значения цвета.
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT: выполнение вычислительного шейдера.
Каждая запись в массиве pWaitDstStageMask соответствует семафору с тем же индексом в pWaitSemaphores
commandBufferCount: количество буферов команд для выполнения
pCommandBuffers: указатель на массив буферов команд для выполнения
signalSemaphoreCount: количество семафоров, которые будут сигнализированы после того, как команды, указанные в pCommandBuffers, завершат выполнение.
pSignalSemaphores: указатель на массив дескрипторов VkSemaphore, которые будут сигнализированы после того, как буферы команд завершат выполнение
Для отправки буфера команд в очередь применяется функция vkQueueSubmit():
VkResult vkQueueSubmit(
VkQueue queue,
uint32_t submitCount,
const VkSubmitInfo* pSubmits,
VkFence fence);
В функцию передаются следующие параметры:
queue: очередь, в которую будут отправлены буферы команд.
submitCount: количество элементов в массиве pSubmits.
pSubmits: указатель на массив структур VkSubmitInfo, каждая из которых указывает пакет отправки буфера команд.
fence: необязательный дескриптор барьера, который будет сигнализирован, когда все отправленные буферы команд завершат выполнение. Если fence не равен VK_NULL_HANDLE, он определяет операцию сигнала барьера.
Пример отправки буфера команд в очередь:
VkSemaphore imageAvailableSemaphore; // семафор для отслеживания готовности изображения
VkSemaphore renderFinishedSemaphore; // семафор для отслеживания завершения рендеринга
VkFence inFlightFence; // барьер для отслеживания выполнения буфера команд
VkQueue graphicsQueue ; // очередь, в которую отправляем буфер команд
.....................
// определяем информацию для отправки буфера команд в очередь
VkSubmitInfo submitInfo{};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; // ожидаем один семафор
submitInfo.waitSemaphoreCount = 1; // ожидаем один семафор
submitInfo.pWaitSemaphores = waitSemaphores;
// этапы конвейера
// VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT - этап получения окончательного цвета
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
submitInfo.pWaitDstStageMask = waitStages;
// устанавливаем выполняемые команды
submitInfo.commandBufferCount = 1; // один буфер команд
submitInfo.pCommandBuffers = &commandBuffer; // выполняемый буфер команд
// устанавливаем, какие семафоры следует сигнализировать после завершения выполнения буфера команд
// renderFinishedSemaphore будет сигнализировать о том, что команды завершили выполнение
VkSemaphore signalSemaphores[] = {renderFinishedSemaphore};
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
// отправляем буфер команд в графическую очередь
// graphicsQueue - индекс ранее полученной очереди
if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) {
throw std::runtime_error("Не удалось отправить буфер команд!");
}
Последний шаг рисования кадра представляет отправка результата обратно в цепочку кадров, чтобы он в конечном итоге появился на экране. Отображение кадров настраивается через структуру VkPresentInfoKHR:
typedef struct VkPresentInfoKHR {
VkStructureType sType;
const void* pNext;
uint32_t waitSemaphoreCount;
const VkSemaphore* pWaitSemaphores;
uint32_t swapchainCount;
const VkSwapchainKHR* pSwapchains;
const uint32_t* pImageIndices;
VkResult* pResults;
} VkPresentInfoKHR;
Поля структуры:
sType: тип структуры - значение VK_STRUCTURE_TYPE_PRESENT_INFO_KHR.
pNext: NULL или указатель на структуру, расширяющую эту структуру.
waitSemaphoreCount: количество семафоров, которые нужно ожидать перед запросом на отображение. Число может быть равно 0.
pWaitSemaphores: NULL или указатель на массив семафоров, которые нужно ожидать перед запросом на отображение
swapchainCount: количество цепочек кадров, представляемых этой командой.
pSwapchains: указатель на массив цепочек кадров.
pImageIndices: указатель на массив индексов в массиве представляемых изображений каждой цепочки кадров. Каждая запись в этом массиве идентифицирует изображение в массиве pSwapchains.
pResults: необязательный параметр - указатель на массив значений VkResult для проверки успешности показа изображений для каждой отдельной цепочки кадров.
И непосредственно для показа изображения на экране применяется функция vkQueuePresentKHR
VkResult vkQueuePresentKHR(
VkQueue queue,
const VkPresentInfoKHR* pPresentInfo);
Функция принимает два параметра: очередь представления и указатель на структуру VkPresentInfoKHR с информацией для отображения. Пример:
VkSwapchainKHR swapChain; // ранее полученная цепочка кадров
VkQueue presentQueue; // ранее полученная очередь представления
VkSemaphore renderFinishedSemaphore; // семафор для ожидания завершения рендеринга
VkSemaphore signalSemaphores[] = {renderFinishedSemaphore};
uint32_t imageIndex; // индекс изображения для показа
...........................................
VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
// указываем, какие семафоры ждать, прежде чем может произойти отображение
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
VkSwapchainKHR swapChains[] = {swapChain}; // цепочки кадров с изображениями для отображения
presentInfo.swapchainCount = 1; // одна цепочка кадров
presentInfo.pSwapchains = swapChains; // цепочки кадров, из которых показываем изображения
presentInfo.pImageIndices = &imageIndex; // индекс изображения
// отправляем запрос на показ изображения
vkQueuePresentKHR(presentQueue, &presentInfo);
(Практический пример по этой статье: Рендеринг и вывод на экран в GLFW)