Модули шейдеров

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

(Практический пример по этой статье: Модули шейдеров в 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)

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