(Практический пример по этой статье: Модули шейдеров в GLFW)
В Vulkan шейдеры инкапсулированы в шейдерные модули, которые загружаются в GPU и могут использоваться для создания конвейеров. И прежде чем передать код шейдера в конвейер, его нужно обернуть в шейдерный модуль - объект VkShaderModule (данный тип представляет непрозрачный указатель). Для создания модуля шейдера применяется функция vkCreateShaderModule():
VkResult vkCreateShaderModule(
VkDevice device,
const VkShaderModuleCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkShaderModule* pShaderModule);
Функция принимает следующие параметры:
device: логическое устройство, которое создает модуль шейдера.
pCreateInfo: указатель на структуру VkShaderModuleCreateInfo с конфигурацией модуля шейдера.
pAllocator: аллокатор памяти, который управляет распределением памяти хоста
pShaderModule: указатель на дескриптор VkShaderModule, в котором возвращается результирующий объект модуля шейдера.
Итак, чтобы создать модуль шейдера, нам надо вначале определить структуру VkShaderModuleCreateInfo с конфигурацией:
typedef struct VkShaderModuleCreateInfo {
VkStructureType sType;
const void* pNext;
VkShaderModuleCreateFlags flags;
size_t codeSize;
const uint32_t* pCode;
} VkShaderModuleCreateInfo;
Структура определяет следующие поля:
sType: тип структуры, должен иметь значение VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO.
pNext: NULL или указатель на структуру, расширяющую эту структуру.
flags: не используется и зарезервирован для будущего использования.
codeSize: размер в байтах кода, на который указывает pCode.
pCode: указатель на код, который используется для создания модуля шейдера. Тип и формат кода определяются из содержимого памяти, адресуемой pCode.
Рассмотрим пример загрузки модуля:
VkShaderModule createShaderModule(const std::vector<char>& code) {
VkShaderModuleCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = code.size(); // устанавливаем размер байт-кода
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data()); // приводим указатель к нужному типу
VkShaderModule shaderModule; // создаваемый модуль шейдера
if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
throw std::runtime_error("Не удалось создать модуль шейдера!");
}
return shaderModule;
}
Эта функция принимает буфер с байт-кодом в качестве параметра и создает из него объект VkShaderModule.
Для создания модуля шейдера нужен указатель на буфер с байт-кодом модуля и длина этого буфера. Эта информация указывается в структуре VkShaderModuleCreateInfo.
Но так как размер байт-кода указывается в байтах, а указатель байт-кода — это указатель типа uint32_t, а не указатель char, то необходимо привести указатель к нужному типу с помощью
reinterpret_cast:
VkShaderModuleCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = code.size(); // устанавливаем размер байт-кода
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data()); // приводим указатель к нужному типу
Далее собственно создаем модуль VkShaderModule с помощью вызова vkCreateShaderModule():
VkShaderModule shaderModule; // создаваемый модуль шейдера
if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
throw std::runtime_error("Не удалось создать модуль шейдера!");
}
После завершения работы с модулями шейдеров их надо явным образом удалить с помощью функции vkDestroyShaderModule():
void vkDestroyShaderModule(
VkDevice device,
VkShaderModule shaderModule,
const VkAllocationCallbacks* pAllocator);
В функцию передается логическое устройство, которое удаляет модули, сам удаляемый модуль шейдера и необязательный аллокатор памяти (если он применяется).
Простейший пример получения и удаления модулей шейдеров:
void createGraphicsPipeline() {
// загрузка файлов шейдеров
auto vertShaderCode = readFile("shaders/vert.spv");
auto fragShaderCode = readFile("shaders/frag.spv");
// преобразование кода шейдеров в модули шейдеров
VkShaderModule vertShaderModule = createShaderModule(vertShaderCode);
VkShaderModule fragShaderModule = createShaderModule(fragShaderCode);
// удаление модулей шейдеров
vkDestroyShaderModule(device, fragShaderModule, nullptr);
vkDestroyShaderModule(device, vertShaderModule, nullptr);
}
Чтобы использовать шейдеры, нужно передать их на определенный этап конвейера через структуру VkPipelineShaderStageCreateInfo, которая имеет следующее определение:
typedef struct VkPipelineShaderStageCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineShaderStageCreateFlags flags;
VkShaderStageFlagBits stage;
VkShaderModule module;
const char* pName;
const VkSpecializationInfo* pSpecializationInfo;
} VkPipelineShaderStageCreateInfo;
Структура определяет следующие поля:
sType: тип структуры, должен быть равен значению VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO.
pNext: NULL или указатель на структуру, расширяющую эту структуру.
flags: битовая маска VkPipelineShaderStageCreateFlagBits, которая указывает, как будет сгенерирован этап конвейерного шейдера.
stage: значение VkShaderStageFlagBits, которая указывает на этап конвейера:
typedef enum VkShaderStageFlagBits {
VK_SHADER_STAGE_VERTEX_BIT = 0x00000001, // вершинный шейдер
VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT = 0x00000002, // этап тесселяции
VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT = 0x00000004, // этап оценки тесселяции
VK_SHADER_STAGE_GEOMETRY_BIT = 0x00000008, // этап геометрического шейдера
VK_SHADER_STAGE_FRAGMENT_BIT = 0x00000010, // этап фрагментного шейдера
VK_SHADER_STAGE_COMPUTE_BIT = 0x00000020, // вычислительный этап
..............................................
}
module: необязательный объект VkShaderModule,который содержит код шейдера для этого этапа.
pName: строка - имя точки входа шейдера для этого этапа.
pSpecializationInfo: указатель на структуру VkSpecializationInfo или NULL, позволяет указывать значения для констант шейдера
Возьмем создание структуры для вершинного шейдера:
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; // этап вершинного шейдера
vertShaderStageInfo.module = vertShaderModule; // модуль шейдера
vertShaderStageInfo.pName = "main"; // точка входа в шейдер - функция main
Сначала определяем тип этапа - этап вершинного шейдера. Далее определяем модуль шейдера, содержащий код, и функцию для вызова - точку входа в шейдер:
Аналогично выглядит структура для фрагментного шейдера:
VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; // этап фрагментного шейдера
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";
Определение этапов для структур происходит при построении графического конвейера:
void createGraphicsPipeline() {
// загрузка файлов шейдеров
auto vertShaderCode = readFile("shaders/vert.spv");
auto fragShaderCode = readFile("shaders/frag.spv");
// преобразование кода шейдеров в модули шейдеров
VkShaderModule vertShaderModule = createShaderModule(vertShaderCode);
VkShaderModule fragShaderModule = createShaderModule(fragShaderCode);
// определение этапа для вершинного шейдера
VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; // этап вершинного шейдера
vertShaderStageInfo.module = vertShaderModule; // модуль шейдера
vertShaderStageInfo.pName = "main"; // точка входа в шейдер - функция main
// определение этапа для фрагментного шейдера
VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";
// определяем массив для дальнейшего использования
VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
// удаление модулей шейдеров
vkDestroyShaderModule(device, fragShaderModule, nullptr);
vkDestroyShaderModule(device, vertShaderModule, nullptr);
}
(Практический пример по этой статье: Модули шейдеров в GLFW)