Введение
Графический конвейер с точки зрения Vulkan API это последовательность операций, преобразующих массив вершин в пиксели.
Структура графического конвейера
Графический конвейер состоит из 12 этапов: 9 основных представлены на рисунке 1.
На рисунке 1 серым цветом обозначены непрограммируемые этапы, а белым обозначены программируемые. Пунктирная линия ведет к блоку с входными данными, под этапом изображен результат выполнения. Стоит заметить что схема является упрощенной — некоторые этапы объединены. Рассмотрим конвейер подробнее:
- входной сборщик — компонует вершины для создания полигонов из массивов индексов (если используется) и вершин;
- вершинный шейдер — вызывается для каждой вершины и осуществляет преобразования координат;
- тесселяция — позволяет увеличить количество полигонов и состоит из нескольких шагов:
- управляющий шейдер — подготовка параметров тесселяции,
- генерация примитивов — использует параметры для разбиения примитивов на множество мелких,
- вычислительный шейдер — работает аналогично вершинному шейдеру и вызывается для каждой вершины, образованной на шаге генерации примитивов,
- геометрический шейдер — обрабатывает и изменяет примитивы (треугольники, линии, точки);
- сборка примитивов — группировка вершин, созданные предыдущими шейдерами в примитивы, подходящие для растеризации;
- отсечение и отбрасывание — определение частей примитивов, влияющих на результат изображения (отбрасываются невидимые примитивы);
- растеризация — преобразование примитивов к фрагментам, которые становятся пикселями;
- перфрагментные операции (не отображены на рисунке) — тесты глубины и трафарета (если включены);
- сборка фрагментов (не отображена на рисунке) — сборка фрагментов растеризации и отправка на вход фрагментного шейдера;
- фрагментный шейдер — вызывается для каждого фрагмента и определяет в какие фреймбуферы и с каким значением цвета записываются фрагменты;
- постфрагментные операции (не отображены на рисунке) — перфрагментные операции в случае, если были изменены данные глубины или трафарета фрагментным шейдером;
- смешение цветов — совмещение различных фрагментов и выходного буфера с учетом прозрачности.
Проходы рендера (renderpass)
Перед созданием графического конвейера потребуется создать объект прохода рендера — объект, содержащий информацию об используемых буферах.
Первое время будет использоваться лишь один цветовой буфер, представляющий из себя одно из изображений цепочки показа.
Важной частью прохода являются прикрепления (attachments) — структура, которая определяет одно изображение в качестве или входного, или выходного, или входного-выходного одновременно одного или нескольких подпроходов.
Примечание: автор считает, что корректно назвать attachment — буфером, но во избежание проблем с пониманием контекста будет использоваться название прикрепление.
Подпроходы (subpasses) — это последовательные операции рендера, зависящие от содержимого фреймбуферов на предыдущих проходах. Первое время в заметках будет использоваться только один подпроход.
Когда в одном проходе рендера содержатся несколько подпроходов Vulkan API способен самостоятельно установить зависимости проходов друг от друга, отслеживая ссылки на подключения и входы/выходы, которые делают их зависимыми между собой. В случае, если зависимости не могут быть представлены простым отношением вход-выход, то необходимо предоставить информацию о зависимостях (dependencies). Работа с зависимостями будет описана подробнее в будущей заметке о многопроходном рендере.
Для создания проходов рендера необходимо заполнить объект структуры о создаваемом объекте VkRenderPassCreateInfo.
Рассмотрим структуру подробнее:
- sType — определяет тип структуры, должно быть VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
- pNext — указатель на расширение структуры (не используется в заметках);
- flags — битовая маска флагов, представленная перечислением (enum) VkRenderPassCreateFlagBits (не используется в заметках);
- attachmentCount — количество прикреплений (attachment);
- pAttachments — указатель на массив структур VkAttachmentDescription, содержащий информацию об используемых прикреплениях;
- subpassCount — количество подпроходов (subpass);
- pSubpasses — указатель на массив структур VkSubpassDescription, содержащий информацию об используемых подпроходах;
- dependencyCount — количество зависимостей (dependency), в заметке не используется (= 0);
- pDependencies — указатель на массив структур VkSubpassDependency, содержащий информацию о зависимостях.
Рассмотрим структуру VkAttachmentDescription, содержащую информацию об используемых прикреплениях:
- flags — битовая маска флагов, представленная перечислением (enum) VkAttachmentDescriptionFlagBits (не используется в заметках);
- format — формат цветового буфера, соответствует формату списка показа;
- samples — число образцов (сэмплов) при мультисэмплинге (в заметках используется только 1 образец — VK_SAMPLE_COUNT_1_BIT);
- loadOp — определяет действия с данными буфера перед рендером, которые представлены перечислением (enum) VkAttachmentLoadOp:
- VK_ATTACHMENT_LOAD_OP_LOAD — буфер будет содержать те данные прошлого прохода,
- VK_ATTACHMENT_LOAD_OP_CLEAR — буфер очищается в начале прохода рендера,
- VK_ATTACHMENT_LOAD_OP_DONT_CARE — содержимое буфера не определено;
- storeOp — определяет действия с данными буфера цвета и глубины после рендера, которые представлены перечислением (enum) VkAttachmentStoreOp:
- VK_ATTACHMENT_STORE_OP_STORE — содержимое буфера сохраняется в память для дальнейшего использования,
- VK_ATTACHMENT_STORE_OP_DONT_CARE — после рендеринга буфер больше не используется;
- stencilLoadOp — определяет действия с данными буфера трафарета перед рендером (возможные значения соответствуют loadOp);
- stencilStoreOp — определяет действия с данными буфера трафарета после рендера (возможные значения соответствуют storeOp);
- initialLayout — размещение изображения перед началом рендера;
- finalLayout — размещение изображения после рендера.
Поля initialLayout и finalLayout будут рассмотрены подробнее в будущей заметке о многопроходном рендере. Сейчас им будет задано значение VK_IMAGE_LAYOUT_UNDEFINED и VK_IMAGE_LAYOUT_PRESENT_SRC_KHR.
Рассмотрим структуру VkSubpassDescription, содержащую информацию об используемых подпроходах:
- flags — битовая маска флагов, представленная перечислением (enum) VkSubpassDescriptionFlagBits (не используется в заметках);
- pipelineBindPoint — предназначение подпрохода, должно быть VK_PIPELINE_BIND_POINT_GRAPHICS;
- inputAttachmentCount — количество входных прикреплений (из которых читают данные);
- pInputAttachments — указатель на массив с информацией о входных прикреплений;
- colorAttachmentCount — количество выходных прикреплений (в которые пишут данные);
- pColorAttachments — указатель на массив с информацией о выходных прикреплений;
- pResolveAttacments — используется для мультисэмплинга, в заметке не используется (= NULL);
- pDepthStencilAttachment — указатель на прикрепление для глубины/трафарета;
- preserveAttachmentCount — количество хранимых на протяжении всего подпрохода прикреплений (не используются в процессе);
- pPreserveAttachements — указатель массив индексов хранимых на протяжении всего подпрохода прикреплений.
Примечание: pipelineBindPoint может принимать значения VK_PIPELINE_BIND_POINT_COMPUTE для конвейера вычислений и VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR для конвейера трассировки лучей.
Информация о прикреплениях в массивах рассмотренной структуры хранится в структуре VkAttachmentReference, рассмотрим её подробнее:
- attachment — либо VK_ATTACHMENT_UNUSED, если прикрепление не используется, либо индекс в массиве VkRenderPassCreateInfo::pAttachments;
- layout — перечисление (enum) VkImageLayout, определяющее макет (layout) буфера.
Примечание: слово layout в контексте структуры VkAttachmentReference определяет тип доступа к буферу, влияющее на производительность, и как следствие используется слово макет. В рамках заметки будет использоваться значение VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL.
Рассмотрим структуру VkSubpassDependency, содержащую информацию о зависимостях:
- srcSubpass — исходный подпроход, создающий данные;
- dstSubpass — целевой подпроход, использующий данные;
- srcStageMask — битовая маска флагов, представленная перечислением (enum) VkPipelineStageFlagBits и определяющая какие стадии конвейера в исходном подпроходе создают данные;
- dstStageMask — аналогично с srcStageMask битовая маска флагов, определяющая какие стадии подпрохода используют данные;
- srcAccessMask — битовая маска флагов, представленная перечислением (enum) VkAccessFlagBits и определяющая как каждый из исходных и целевых подпроходов обращается к данным;
- dstAccessMask — аналогично srcAccessMask;
- dependencyFlags — битовая маска флагов, представленная перечислением (enum) VkDependencyFlagBits и определяющая область зависимостей для подпрохода:
- VK_DEPENDENCY_BY_REGION_BIT — подпроход имеет локальные для фреймбуфера зависимости,
- VK_DEPENDENCY_VIEW_LOCAL_BIT — подпроход имеет более одного представления,
- VK_DEPENDENCY_DEVICE_GROUP_BIT — подпроход имеет не локальными для устройства.
Для создания проходов рендера добавим в приватную часть класса Vulkan поле VkRenderPass renderPass и метод createRenderpass. Функция vkCreateRenderPass предназначена для создания проходов рендера и принимает в качестве аргументов дескриптор логического устройства, адрес объекта структуры VkRenderPassCreateInfo с данными о создаваемом объекте, адрес аллокатора и адрес дескриптора VkRenderPass. В случае ошибки создадим исключение.
Ниже приведен пример создания проходов рендера:
void Vulkan::createRenderpass()
{
// Информация о прикреплении
VkAttachmentDescription colorAttachment{};
colorAttachment.format = surface.selectedFormat.format;
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
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;
// Информация о выходном прикреплении
VkAttachmentReference colorAttachmentRef{};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
// Информация о подпроходе
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
// Зависимости подпрохода
VkSubpassDependency dependency{};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.srcAccessMask = 0;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
// Информация о создаваемом проходе рендера
VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
renderPassInfo.dependencyCount = 1;
renderPassInfo.pDependencies = &dependency;
// Создание проходов рендера
if (vkCreateRenderPass(logicalDevice, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS)
{
throw std::runtime_error("Unable to create render pass");
}
}
После создания необходимо дополнить метод Vulkan::destroy вызовом функции vkDestroyRenderPass, которая принимает в качестве аргументов дескриптор логического устройства, дескриптор прохода рендера и адрес аллокатора.
Текущий вариант доступен на теге v0.1 репозитория 04.
Шейдеры
Шейдер — подпрограмма, выполняемая на стороне графического устройства в составе конвейера. В данном курсе заметок используются в основном графические шейдеры.
Шейдеры для Vulkan API представляют собой байт-код стандарта SPIR-V, который так же используется для OpenCL. Байт-код компилируется из языка GLSL, используемого для написания шейдеров для OpenGL, с помощью приложения glslc.exe, которое входит в состав Vulkan SDK, установленного в рамках первой заметки.
Приложение glslc принимает в качестве аргументов имя файла с шейдером на языке GLSL, а так же имя выходного файла с ключом -o. Если в процессе установки SDK в переменную среды Windows PATH был добавлен адрес папки с исполняемыми файлами Vulkan, то команду можно вызвать в консоли без указания пути до SDK, в противном случае необходимо писать полный путь до исполняемого файла приложения glslc. Пример компиляции вершинного и фрагментного шейдеров:
glslc.exe shader.vert -o vert.spv
glslc.exe shader.frag -o frag.spv
Важное примечание: для данного приложения важно расширение файла по которому оно определяет тип шейдера.
Для загрузки бинарного файла шейдера напишем функцию, которая принимает имя файла и буфер в который необходимо считать шейдер:
#include <fstream>
// Считывание бинарного файла, содержащего шейдер
void readFile(const char * filename, std::vector<char>& buffer)
{
// откроем файл как бинарный и установим курсор в конец файла
std::ifstream file(filename, std::ios::ate | std::ios::binary);
// если файл не открыт - генерируем исключение
if (!file.is_open())
{
throw std::runtime_error("Can't open file");
}
// определим размер файла
size_t fileSize = (size_t) file.tellg();
// создадим буфер
buffer.resize(fileSize);
// перенесем курсор в начало файла
file.seekg(0);
// считаем данные в буфер
file.read(buffer.data(), fileSize);
// закроем файл
file.close();
}
Для создания шейдерного модуля добавим метод к классу Vulkan::createShaderModule, который принимает имя файла и возвращает объект VkShaderModule.
Для создания шейдерного модуля используется функция vkCreateShaderModule, которая принимает в качестве аргументов дескриптор логического устройства, адрес объекта структуры VkShaderModuleCreateInfo, содержащей информацию о создаваемом объекте, адрес аллокатора и адрес дескриптора VkShaderModule. Рассмотрим структуру VkShaderModuleCreateInfo подробнее:
- sType — определяет тип структуры, должно быть VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
- pNext — указатель на расширение структуры (не используется в заметках);
- flags — не используется;
- codeSize — размер скомпилированного шейдера в байтах;
- pCode — адрес массива, содержащего байт-код шейдера.
Важное примечание: массив содержащий байт-код шейдера должен быть типа uint32_t и для конвертации из char используется reinterpret_cast — который определяет использование четырех элементов char как одного uint32_t.
Пример метода для создания шейдерного модуля:
VkShaderModule Vulkan::createShaderModule(const char * filename)
{
// буфер для чтения из файла
std::vector<char> buffer;
// считаем шейдер из файла
readFile(filename, buffer);
// Информация о создаваемом шейдерном модуле
VkShaderModuleCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = buffer.size();
createInfo.pCode = reinterpret_cast<const uint32_t*>(buffer.data());
// Создание шейдерного модуля
VkShaderModule shaderModule;
if (vkCreateShaderModule(logicalDevice, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
throw std::runtime_error("Unable to create shader module");
}
return shaderModule;
}
После создания графического конвейера можно удалить шейдерные модули, для этого необходимо вызвать функцию vkDestroyShaderModule, которая принимает в качестве аргументов дескриптор логического устройства, дескриптор шейдерного модуля и адрес алокатора.
Ниже приведен текст вершинного шейдера на языке GLSL:
#version 450
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 0) out vec3 fragColor;
void main() {
gl_Position = vec4(inPosition, 0.0, 1.0);
fragColor = inColor;
}
Ниже приведен фрагментный шейдер на языке GLSL:
#version 450
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
}
Разбор шейдеров осуществляется в отдельной заметке (в разработке), так как синтаксис соответствует GLSL, который применим как к Vulkan API, так и к OpenGL.
Для использования шейдеров в графическом конвейере необходимо заполнить данные в структуре VkPipelineShaderStageCreateInfo для каждого шейдера. Рассмотрим её подробнее:
- sType — определяет тип структуры, должно быть VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
- pNext — указатель на расширение структуры (не используется в заметках);
- flags — битовая маска флагов, представленная перечислением (enum) VkPipelineShaderStageCreateFlagBits (не используется в заметках);
- stage — тип шейдерного модуля:
- VK_SHADER_STAGE_VERTEX_BIT — вершинный шейдер,
- VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT — шейдер управления тесселяцией,
- VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT — шейдер вычисления тесселяции,
- VK_SHADER_STAGE_GEOMETRY_BIT — геометрический шейдер,
- VK_SHADER_STAGE_FRAGMENT_BIT — фрагментный шейдер,
- VK_SHADER_STAGE_COMPUTE_BIT — вычислительный шейдер;
- module — шейдерный модуль (образованный при загрузке методом Vulkan::createShaderModule;
- pName — адрес си-строки в кодировке UTF-8 с названием входной функции;
- pSpecializationInfo — nullptr или указатель на объект структуры VkSpecializationInfo, описывающий константы специализации (будет рассмотрено в дальнейших заметках).
Текущий вариант доступен на теге v0.2 репозитория 04.
Непрограммируемые части конвейера
Непрограммируемые части конвейера в Vulkan API представляют собой набор состояний, описываемый следующими структурами:
- входные данные вершин — структура VkPipelineVertexInputStateCreateInfo описывает формат данных вершин, которые передаются в вершинный шейдер;
- входной сборщик — структура VkPipelineInputAssemblyStateCreateInfo определяет, какая геометрия образуется из вершин и разрешен ли рестарт геометрии для таких геометрий, как line strip и triangle strip;
- состояние тесселяции — структура VkPipelineTessellationStateCreateInfo настраивает промежуточный этап тесселяции;
- состояние области просмотра — структура VkPipelineViewportStateCreateInfo содержит данные:
- область просмотра (viewport) — структура VkViewport описывает координаты от (0,0) до (width, height) и диапазон буфера глубины;
- прямоугольник отсечения (scissor rectangle) — структура VkRect2D задает прямоугольник в рамках которого остается изображение;
- растеризатор — структура VkPipelineRasterizationStateCreateInfo описывает способ преобразования примитивов из вершинного шейдера в фрагменты;
- мультисэмплинг — структура VkPipelineMultisampleStateCreateInfo настраивает мультисэмплинг (в данной заметке не рассматривается);
- тесты глубины и трафарета — структура VkPipelineDepthStencilStateCreateInfo определяет параметры для буфера глубины и трафарета (в данной заметке не рассматривается);
- смешивание цветов — структура VkPipelineColorBlendAttachmentState содержит настройки для каждого подключенного фреймбуфера, а структура VkPipelineColorBlendStateCreateInfo — глобальные настройки смешивания цветов (в данной заметке не рассматривается);
- динамическое состояние — структура VkPipelineDynamicStateCreateInfo содержит массив перечислений (enum) VkDynamicState определяет какие состояния графического конвейера можно изменить без пересоздания;
- раскладка конвейера — структура VkPipelineLayoutCreateInfo описывает какие Uniform переменные используются в графическом конвейере (в данной заметке не рассматривается).
Важное замечание! В английской версии используется слово «layout», но автор на протяжении курса заметок будет использовать слово «раскладка». В русскоязычном сообществе не используется перевод слова, автор посчитал это слово более хорошей заменой англицизму.
Рассмотрим упомянутые выше структуры, неиспользуемые типовые поля flags и pNext не упоминаются.
VkPipelineVertexInputStateCreateInfo:
- sType — определяет тип структуры, должно быть VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
- vertexBindingDescriptionCount — количество элементов в массиве с данными о вершинных привязках;
- pVertexBindingDescriptions — адрес массива структур VkVertexInputBindingDescription с данными о вершинных привязках:
- binding — индекс привязки,
- stride — шаг массива (расстояния между началами структур),
- inputRate — переход к следующему элементу:
- VK_VERTEX_INPUT_RATE_VERTEX — переход к следующему элементу после каждой вершины,
- VK_VERTEX_INPUT_RATE_INSTANCE — для instanced rendering (не затрагивается в заметках);
- vertexAttributeDescriptionCount — количество описаний атрибутов вершин;
- pVertexAttributeDescriptions — адрес массива структур VkVertexInputAttributeDescription с описаниями атрибутов вершин:
- location — индекс места нахождения используемый шейдером;
- binding — индекс привязки из которого берутся данные;
- format — размер и тип данных
- VK_FORMAT_R32_SFLOAT для float,
- VK_FORMAT_R32G32_SFLOAT для vec2,
- VK_FORMAT_R32G32B32_SFLOAT для vec3,
- VK_FORMAT_R32G32B32A32_SFLOAT для vec4;
- offset — смещение.
Для определения offset можно использовать макрос из stddef.h или cstddef под названием offsetof, который возвращает смещение в байтах до указанного поля от начала структуры
Важное замечание: индексы привязки (VkVertexInputBindingDescription::binding) не обязательно должны быть непрерывны.
VkPipelineInputAssemblyStateCreateInfo:
- sType — определяет тип структуры, должно быть VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
- topology — перечисление (enum) VkPrimitiveTopology:
- VK_PRIMITIVE_TOPOLOGY_POINT_LIST — каждая вершина используется для создания точки,
- VK_PRIMITIVE_TOPOLOGY_LINE_LIST — вершины образуют пары, соединенные линией,
- VK_PRIMITIVE_TOPOLOGY_LINE_STRIP — каждая следующая вершина образует отрезок с последней обработанной,
- VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST — вершины группируются в тройки, образующие треугольники,
- VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN — первые три вершины образуют треугольник, каждая новая вершина образует треугольник с первой и последней (выглядит как веер),
- VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY — каждые четыре вершины образуют примитив, при этом первая и последняя передаются геометрическому шейдеру, а две центральные соединяются линией,
- VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY — аналогично предыдущему, только каждая следующая вершина образует новый отрезок и использует его как информацию о связности,
- VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY — группа из шести вершин образует один примитив, где нечетные вершины образуют треугольник, а четные являются информацией о связности,
- VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY — аналогично случаю с линиями, только используются пары новых вершин, где нечетная образует с двумя старыми нечетными треугольник, а четная является информацией о связности,
- VK_PRIMITIVE_TOPOLOGY_PATCH_LIST — используется при включенной тесселяции и требует дополнительный информации при создании конвейера;
- primitiveRestartEnable — флаг, определяющий возможность разрывать рисование веером при помощи специального индекса в массиве индекса вершин.
Примечание: топологии со связностью (adjaency) используются в совокупности с геометрическим шейдером.
VkPipelineTessellationStateCreateInfo:
- sType — определяет тип структуры, должно быть VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
- patchControlPoints — число контрольных точек, которые будут сгруппированы в один примитив тесселяции (patch).
Важное примечание: структуру требуется заполнить если топология соответствует VK_PRIMITIVE_TOPOLOGY_PATCH_LIST.
VkPipelineViewportStateCreateInfo:
- sType — определяет тип структуры, должно быть VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
- viewportCount — количество элементов в массиве с данными об областях просмотра;
- pViewports — адрес массива с данными об областях просмотра
- scissorCount — количество элементов в массиве с данными о прямоугольниках отсечения;
- pScissors — адрес массива с данными о прямоугольниках отсечения.
Примечание: в заметках используется одна область просмотра и адрес массива может указывать на объект структуры VkViewport.
VkViewport:
- x, y, width, height — задают прямоугольник в пространстве буфера в который будет рендериться изображение;
- minDepth, maxDepth — диапазон значений глубины для фреймбуфера.
Пример VkViewport:
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = surface.selectedExtent.width;
viewport.height = surface.selectedExtent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
Пример Scissor Rectangle:
VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = surface.selectedExtent;
VkPipelineRasterizationStateCreateInfo:
- sType — определяет тип структуры, должно быть VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
- depthClampEnable — флаг, который включает и отключает отсечение глубины;
- rasterizerDiscardEnable — флаг, отключающий растеризацию;
- polygonMode — перечисление (enum) VkPolygonMode, определяющее режим рисования полигонов:
- VK_POLYGON_MODE_FILL — заполнение треугольников,
- VK_POLYGON_MODE_LINE — рисование линий,
- VK_POLYGON_MODE_POINT — рисование точек;
- cullMode — битовая маска флагов, представленная перечислением VkCullModeFlagBits, определяет отброшенные полигоны:
- VK_CULL_MODE_NONE — никакие полигоны не будут отброшены,
- VK_CULL_MODE_FRONT_BIT — полигоны, которые считаются лицевыми будут отброшены,
- VK_CULL_MODE_BACK_BIT — полигоны, которые считаются не лицевыми будут отброшены,
- VK_CULL_MODE_FRONT_AND_BACK — все полигоны будут отброшены;
- frontFace — перечисление (enum) VkFrontFace, определяющее направление обхода треугольника, которое влияет на отбрасывание треугольников:
- VK_FRONT_FACE_COUNTER_CLOCKWISE — треугольник с положительной площадью повернут вперед,
- VK_FRONT_FACE_CLOCKWISE — треугольник с отрицательной площадью повернут вперед;
- depthBiasEnable — флаг, который включает сдвиги по глубине;
- depthBiasConstantFactor — скалярный коэффициент, добавляемый к каждому фрагменту;
- depthBiasClamp — максимальное или минимальное смещение фрагмента по глубине;
- depthBiasSlopeFactor — скалярный коэффициент, добавляемый к наклону полигона;
- lineWidth — толщина линий.
Примечание: если depthClampEnable включена, то объекты выходящие за границы области отсечения проектируются на неё и могут использоваться для заполнения дыр в геометрии. В данной заметке не используется.
VkPipelineDynamicStateCreateInfo:
- sType — определяет тип структуры, должно быть VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
- dynamicStateCount — количество элементов в массиве перечисляющем динамические параметры;
- pDynamicStates — адрес массива содержащего динамические параметры.
Дополнение
Для работы со структурой, содержащей входные данные вершин, необходимо дополнить проект структурой Vertex, определяющей данные вершины. Вершина в первых заметках будет содержать только данные о положении и цвете.
Для хранения векторов и матриц будет использоваться библиотека GLM, скачать её можно с репозитория github. Загруженную библиотеку необходимо поместить в папку dependencies и добавить в файлы конфигурации VSCode.
Пример структуры, содержащей данные вершины:
typedef struct _Vertex
{
glm::vec3 position, color;
} Vertex;
Ниже представлен пример заполнения структур с данными непрограммируемых частей графического конвейера для этой заметки:
// Входные данные вершин
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
// Привязка
VkVertexInputBindingDescription bindingDescription{};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
// Описание атрибута
VkVertexInputAttributeDescription attributeDescriptions[2];
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, position);
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color);
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.vertexAttributeDescriptionCount = 2;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions;
// Входной сборщик
VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
// Область просмотра
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = surface.selectedExtent.width;
viewport.height = surface.selectedExtent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
// Прямоугольник отсечения
VkRect2D scissor{};
scissor.offset = {0, 0};
scissor.extent = surface.selectedExtent;
// Состояние области просмотра
VkPipelineViewportStateCreateInfo viewportState{};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;
// Растеризатор
VkPipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
// Мультисэмплинг
VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
// Смешивание цветов для буфера
VkPipelineColorBlendAttachmentState colorBlendAttachment{};
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_FALSE;
// Глобальные настройки смешивания цветов
VkPipelineColorBlendStateCreateInfo colorBlending{};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.logicOp = VK_LOGIC_OP_COPY;
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
colorBlending.blendConstants[0] = 0.0f;
colorBlending.blendConstants[1] = 0.0f;
colorBlending.blendConstants[2] = 0.0f;
colorBlending.blendConstants[3] = 0.0f;
// раскладка конвейера
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 0;
pipelineLayoutInfo.pushConstantRangeCount = 0;
Раскладку конвейера необходимо создать с помощью функции vkCreatePipelineLayout, которая принимает в качестве аргументов дескриптор логического устройства, адрес объекта структуры VkPipelineLayoutCreateInfo, адрес аллокатора и адрес дескриптора VkPipelineLayout. В случае ошибки необходимо создать исключение. Для созданного объекта добавим поле в приватной видимости класса pipelineLayout и дополним метод Vulkan::destroy вызовом vkDestroyPipelineLayout перед уничтожением проходов рендера, которая принимает дескриптор логического устройства, дескриптор раскладки конвейера и адрес аллокатора.
Пример создания раскладки:
if (vkCreatePipelineLayout(logicalDevice, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS)
{
throw std::runtime_error("Unable to create pipeline layout");
}
Текущий вариант доступен на теге v0.3 репозитория 04.
Создание графического конвейера
Для создания графического конвейера требуется заполнить объект структуры с информацией VkGraphicsPipelineCreateInfo. Рассмотрим поля подробнее:
- sType — определяет тип структуры, должно быть VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
- pNext — указатель на расширение структуры (не используется в заметках);
- flags — битовая маска флагов, представленная перечислением (enum) VkPipelineCreateFlagBits:
- VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT — отключает оптимизацию шейдера для более быстрого создания и меньшего быстродействия,
- VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT — флаг позволяет создавать новые конвейеры на основе создаваемого,
- VK_PIPELINE_CREATE_DERIVATIVE_BIT — указывает что создаваемый конвейер создается на основе другого конвейера;
- stageCount — количество шейдеров;
- pStages — указатель на массив структур VkPipelineShaderStageCreateInfo, содержащий информацию об используемых шейдерах;
- pVertexInputState — адрес объекта структуры VkPipelineVertexInputStateCreateInfo, описывающей входные данные вершин;
- pInputAssemblyState — адрес объекта структуры VkPipelineInputAssemblyStateCreateInfo, описывающей входной сборщик;
- pTessellationState — адрес объекта структуры VkPipelineTessellationStateCreateInfo, описывающей состояние тесселяции;
- pViewportState — адрес объекта структуры VkPipelineViewportStateCreateInfo, описывающей состояние области просмотра;
- pRasterizarionState — адрес объекта структуры VkPipelineRasterizationStateCreateInfo, описывающей растеризатор;
- pMultisampleState — адрес объекта структуры VkPipelineMultisampleStateCreateInfo, описывающей мультисэмплинг;
- pDepthStencilState — адрес объекта структуры VkPipelineDepthStencilStateCreateInfo, описывающей тесты глубины и трафарета;
- pColorBlendState — адрес объекта структуры VkPipelineColorBlendStateCreateInfo, описывающей смешивание цветов;
- pDynamicState — адрес объекта структуры динамическое состояние, описывающей VkPipelineDynamicStateCreateInfo;
- layout — раскладка конвейера (VkPipelineLayout);
- renderPass — проходы рендера;
- subpass — подпроходы рендера (не используются в заметке);
- basePipelineHandle — дескриптор базового конвейера, используется для создания нового конвейера на основе существующего конвейера с частичным заимствованием параметров;
- basePipelineIndex — индекс базового конвейера (аналогично дескриптору).
Примечание: basePipelineHandle и basePipelineIndex требуют значение битовой маски flags VK_PIPELINE_CREATE_DERIVATIVE_BIT. Данный способ повышает производительность при частых переключениях.
Для создания графического конвейера необходимо вызвать функцию vkCreateGraphicsPipelines, которая принимает в качестве аргументов дескриптор логического устройства, дескриптор кэша конвейеров (не используется = VK_NULL_HANDLE), количество создаваемых конвейеров, адрес массива с данными о создаваемых структурах (VkGraphicsPipelineCreateInfo), адрес аллокатора и адрес массива дескрипторов графических конвейеров. В случае возникновения ошибок создания конвейера создается исключение. Необходимо дополнить метод Vulkan::destroy вызовом функции vkDestroyPipeline, которая принимает в качестве аргументов дескриптор логического устройства, дескриптор графического конвейера и адрес аллокатора.
Примечание: функция vkCreateGraphicsPipelines может создавать одновременно несколько конвейеров, но в заметке используется один и заместо массива передается адрес одного объекта.
Пример создания графического конвейера:
// Информация о создаваемом конвейере
VkGraphicsPipelineCreateInfo pipelineInfo{};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.layout = pipelineLayout;
pipelineInfo.renderPass = renderPass;
pipelineInfo.subpass = 0;
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
// Создание графического конвейера
if (vkCreateGraphicsPipelines(logicalDevice, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS)
{
throw std::runtime_error("Unable to create graphics pipeline");
}
Текущий вариант доступен на теге v0.4 репозитория 04.
Вывод
В рамках данной заметки было рассмотрено устройство графического конвейера, его стадий и создан простой графический конвейер с двумя шейдерами (вершинным и фрагментным).
Проект доступен в публичном репозитории: 04
Библиотеки: dependencies