Рендеринг и вывод на экран

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

(Практический пример по этой статье: Рендеринг и вывод на экран в GLFW)

В Vulkan рендеринг проходит ряд этапов:

  • Ожидание завершения предыдущего кадра

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

  • Запись буфера команд, который рисует сцену на этом изображении

  • Отправка записанного буфера команд

  • Отображение изображения из цепочки кадров

Ожидание завершения предыдущего кадра, барьер и функция vkWaitForFences

Для ожидания завершения предыдущего кадра применяются барьеры. Для ожидания перехода барьера в сигнальное состояние применяется функция 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)

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