(Практический пример по этой статье: Проход рендеринга и прикрепления буфера кадров в GLFW)
Для создания графического конвейера нужно сообщить Vulkan о прикреплениях буфера кадров, которые будут использоваться при рендеринге.
Что такое "прикрепление" или attachment в контексте Vulkan и вообще работы с графикой? Изображение — это просто фрагмент памяти с некоторыми метаданными о макете, формате и т. д. Буфер кадров (кадровый буфер - frame buffer) — это контейнер для нескольких изображений с дополнительными метаданными для каждого изображения, такими как цель использования, идентификатор (индекс) и тип (цвет, глубина и т. д.). Эти изображения, используемые в буфере кадров, называются прикреплениями, потому что они прикреплены к буферу кадров и принадлежат ему.
Прикрепления, которые используются в качестве входных данных, называются входными прикреплениями (Input Attachment). Прикрепления с информацией о цвете (используемых компонентах RGB) называются Color Attachment или "цветовыми прикреплениями", с информацией о глубине (depth) - Depth Attachment или "прикреплениями глубины", с информацией о шаблоне (stencil) Stencil Attachment или "прикреплениями шаблона". (Stencil (шаблон/трафарет) - представляет шаблон, используемый для рисования или раскрашивания идентичных букв, символов, фигур или узоров.)
Прикрепления буфера кадров описываются структурой VkAttachmentDescription:
typedef struct VkAttachmentDescription {
VkAttachmentDescriptionFlags flags;
VkFormat format;
VkSampleCountFlagBits samples;
VkAttachmentLoadOp loadOp;
VkAttachmentStoreOp storeOp;
VkAttachmentLoadOp stencilLoadOp;
VkAttachmentStoreOp stencilStoreOp;
VkImageLayout initialLayout;
VkImageLayout finalLayout;
} VkAttachmentDescription;
Информация о прикрепления определяется через следующие поля:
flags: битовая маска VkAttachmentDescriptionFlagBits, указывающая дополнительные свойства прикрепления.
format: значение VkFormat, указывающее формат представления изображения, который будет использоваться для прикрепления.
samples: значение VkSampleCountFlagBits, указывающее количество образцов изображения.
loadOp: определяет, что делать с данными в цветовом прикреплении до рендеринга
storeOp: определяет, что делать с данными в цветовом прикреплении после рендеринга.
stencilLoadOp: определяет, что делать с данными в прикреплении шаблона до рендеринга.
stencilStoreOp: определяет, что делать с данными в прикреплении шаблона после рендеринга.
initialLayout: указывает, какой макет будет у изображения до начала прохода рендеринга.
Некоторые из наиболее распространенных макетов:
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: Изображения, используемые как цветовое прикрепление
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR: изображения, которые будут представлены в цепочке буферов
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: изображения, которые будут использоваться как место назначения для операции копирования памяти
finalLayout: указывает, какой макет будет у изображения по завершении прохода рендеринга.
В нашем случае у нас будет только одно цветовое прикрепление , представленное одним из изображений из цепочки буфера.
VkAttachmentDescription colorAttachment{};
colorAttachment.format = swapChainImageFormat; // формат изображений, полученный из цепочки буферов
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; // 1 образец на изображение
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // очистить буфер кадра до черного цвета перед рисованием нового кадра
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; // отрисованное содержимое будет сохранено в памяти и может быть прочитано позже
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // не имеет значения
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // не имеет значения
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
Формат цветового прикрепления должен соответствовать формату изображений цепочки буферов
Поля loadOp и storeOp определяют, что делать с данными в прикреплении до рендеринга и после рендеринга. Eсть следующие варианты для loadOp:
VK_ATTACHMENT_LOAD_OP_LOAD: сохраняет существующее содержимое прикрепления
VK_ATTACHMENT_LOAD_OP_CLEAR: очищает значения до константы при запуске экземпляра прохода рендеринга
VK_ATTACHMENT_LOAD_OP_DONT_CARE: cуществующее содержимое не определено
В нашем случае мы собираемся использовать операцию очистки, чтобы очистить буфер кадра до черного цвета перед рисованием нового кадра. Для storeOp есть только две возможности:
VK_ATTACHMENT_STORE_OP_STORE: отрисованное содержимое будет сохранено в памяти и может быть прочитано позже
VK_ATTACHMENT_STORE_OP_DONT_CARE: cодержимое буфера кадра будет неопределенным после операции отрисовки
В данном случае мы выполняем операцию сохранения, чтобы увидеть отрисованный треугольник на экране:
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // очистить буфер кадра до черного цвета перед рисованием нового кадра colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; // отрисованное содержимое будет сохранено в памяти и может быть прочитано позже
loadOp и storeOp применяются к данным цвета и глубины, а stencilLoadOp / stencilStoreOp применяются к данным шаблона.
Наше приложение ничего не будет делать с буфером шаблона, поэтому результаты загрузки и сохранения не имеют значения.
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // не имеет значения colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // не имеет значения
Текстуры и буферы кадров в Vulkan представлены непрозрачным указателем VkImage с определенным форматом пикселей, однако расположение пикселей в памяти может меняться в зависимости от того, что надо сделать с изображением. Но изображения необходимо перевести в определенные макеты, которые подходят для операций с этми изображениями:
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
Использование VK_IMAGE_LAYOUT_UNDEFINED для initialLayout означает, что нам все равно, в каком макете ранее находилось изображение.
Мы хотим, чтобы изображение было готово к представлению с использованием цепочки буферов после рендеринга, поэтому мы используем VK_IMAGE_LAYOUT_PRESENT_SRC_KHR в качестве finalLayout.
Один проход рендеринга может состоять из нескольких подпроходов (subpass). Подпроходы — это последовательные операции рендеринга, которые зависят от содержимого буферов кадров в предыдущих проходах. Если сгруппировать эти операции в один проход рендеринга, Vulkan сможет переупорядочить операции и оптимизировать их выполнение. И для самой простейшей графики (как вывод треугольника) достаточно только одного подпрохода.
Каждый подпроход использует одно или несколько прикреплений (attachment). Ссылки на эти прикрепления представляют структуры типа VkAttachmentReference:
typedef struct VkAttachmentReference {
uint32_t attachment; // идентификатор прикрепления
VkImageLayout layout; // используемый макет изображения
} VkAttachmentReference;
Структура хранит числовой идентификатор прикрепления (числовой индекс в массиве описаний прикреплений, представленных структурами VkAttachmentDescription). Второе поле представляет макет изображения, который прикрепление использует во время подпроходов. Vulkan автоматически переведет прикрепление в этот макет при запуске подпрохода. ПРимер определения структуры:
VkAttachmentReference colorAttachmentRef{};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
В данном случае предполагаем, что наш массив состоит из одного значения VkAttachmentDescription (из одного описания прикрепления), поэтому его индекс равен 0.
Здесь преполашается, что прикрепление будет использоваться для работы в качестве цветового прикрепления, а макет VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL обеспечит наилучшую производительность.
Подпроход описывается с помощью структуры VkSubpassDescription:
typedef struct VkSubpassDescription {
VkSubpassDescriptionFlags flags;
VkPipelineBindPoint pipelineBindPoint;
uint32_t inputAttachmentCount;
const VkAttachmentReference* pInputAttachments;
uint32_t colorAttachmentCount;
const VkAttachmentReference* pColorAttachments;
const VkAttachmentReference* pResolveAttachments;
const VkAttachmentReference* pDepthStencilAttachment;
uint32_t preserveAttachmentCount;
const uint32_t* pPreserveAttachments;
} VkSubpassDescription;
Поля структуры:
flags: битовая маска VkSubpassDescriptionFlagBits, которая определяет использование подпрохода.
pipelineBindPoint: значение VkPipelineBindPoint, которое указывает на тип конвейера, поддерживаемого для этого подпрохода. ПРинимает следующие значения:
VK_PIPELINE_BIND_POINT_COMPUTE: вычислительный конвейер.
VK_PIPELINE_BIND_POINT_GRAPHICS: графический конвейер.
VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR: конвейер трассировки лучей.
VK_PIPELINE_BIND_POINT_SUBPASS_SHADING_HUAWEI: конвейер затенения подпрохода.
VK_PIPELINE_BIND_POINT_EXECUTION_GRAPH_AMDX: конвейер графа выполнения.
inputAttachmentCount: количество входных прикреплений.
pInputAttachments: указатель на массив входных прикреплений, которые считываются шейдером.
colorAttachmentCount: количество цветовых прикреплений из поля pColorAttachments
pColorAttachments: указатель на массив цветовых прикреплений.
pResolveAttachments: NULL или указатель на массив структур VkAttachmentReference, определяющих разрешения прикреплений для этого подпрохода и их макеты.
pDepthStencilAttachment: указатель на прикрепление глубины/шаблона.
preserveAttachmentCount: количество сохраненных прикреплений.
pPreserveAttachments: указатель на массив из индексов прикреплений, которые идентифицируют прикрепления и которые не используются этим подпроходом, но содержимое которых должно
сохраняться на протяжении всего подпрохода.
Пример определения структуры:
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; // тип конвейера - графический конвейер
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef; // выше созданная ссылка на цветовое прикрепление
Здесь в массиве subpass.pColorAttachments устанавливается только одно цветовое прикрепление (с помощью ранее созданной ссылки на цветовое прикрепление).
Причем во фрагментном шейдерк с помощью директивы layout(location = 0) out vec4 outColor через индекс прикрепления мы можем напрямую ссылаться на прикрепление в этом массиве.
Проход рендеринга представляет объект VkRenderPass (непрозрачный указатель (opaque handle)).
Для создания прохода рендеринга применяется функция vkCreateRenderPass():
VkResult vkCreateRenderPass(
VkDevice device,
const VkRenderPassCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkRenderPass* pRenderPass);
В нее передаются
device: логическое устройство, создающее проход рендеринга.
pCreateInfo: указатель на структуру VkRenderPassCreateInfo, которая описывает параметры прохода рендеринга.
pAllocator: аллокатор памяти хоста
pRenderPass: указатель на дескриптор VkRenderPass, через который возвращается результирующий объект прохода рендеринга.
Итак, для определения прохода рендеринга нам необходима структура VkRenderPassCreateInfo, которая описывает параметры прохода рендеринга:
typedef struct VkRenderPassCreateInfo {
VkStructureType sType;
const void* pNext;
VkRenderPassCreateFlags flags;
uint32_t attachmentCount;
const VkAttachmentDescription* pAttachments;
uint32_t subpassCount;
const VkSubpassDescription* pSubpasses;
uint32_t dependencyCount;
const VkSubpassDependency* pDependencies;
} VkRenderPassCreateInfo;
Поля структуры:
sType: тип структуры, должен представлять значение VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO.
pNext: NULL или указатель на структуру, расширяющую эту структуру.
flags: битовая маска VkRenderPassCreateFlagBits, которая определяет дополнительные свойства для прохода рендеринга
attachmentCount: количество прикреплений, используемых этим проходом рендеринга.
pAttachments: указатель на массив прикреплений - структур VkAttachmentDescription, используемые проходом рендеринга.
subpassCount: количество подпроходов.
pSubpasses: указатель на массив структур VkSubpassDescription, описывающих каждый подпроход.
dependencyCount: количество зависимостей подпроходов.
pDependencies: указатель на массив зависимостей подпроходов - структур VkSubpassDependency.
Пример создания прохода рендеринга:
VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
throw std::runtime_error("Не удалось создать проход рендеринга!");
}
После завершения работы необходимо удалить проход рендеринга с помощью функции vkDestroyRenderPass:
void vkDestroyRenderPass(
VkDevice device, // логическое устройство
VkRenderPass renderPass, // удаляемый проход рендеринга
const VkAllocationCallbacks* pAllocator); // аллокатор памяти (если применяется)
(Практический пример по этой статье: Проход рендеринга и прикрепления буфера кадров в GLFW)