Vulkan API — урок 43. 3D, еще и движется (+листинг)

Теперь же мы можем поговорить о самом наборе дескрипторов. Для этого добавим функцию createDescriptorSet:

void initVulkan() {
    ...
    createDescriptorPool();
    createDescriptorSet();
    ...
}

...

void createDescriptorSet() {

}

Далее заполняем нужную для его создания информацию в структуре VkDescriptorSetAllocateInfo. Необходимо указать descriptor pool, в котором он будет расположен (выделен, allocate), количество наборов дескрипторов и макет дескриптора (descriptor layout):

VkDescriptorSetLayout layouts[] = {descriptorSetLayout};
VkDescriptorSetAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = descriptorPool;
allocInfo.descriptorSetCount = 1;
allocInfo.pSetLayouts = layouts;

Для всего этого добавим член класса, для этого используем vkAllocateDescriptorSets во время его вызова:

VDeleter descriptorPool{device, vkDestroyDescriptorPool};
VkDescriptorSet descriptorSet;

...

if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) {
    throw std::runtime_error("failed to allocate descriptor set!");
}

Нет необходимости использовать VDeleter для descriptorSet, он находится внутри пула и будет очищен автоматически с удалением оного. Вызов vkAllocateDescriptorSets выделит один набор дескриптора (descriptor set) с одним дескриптором uniform buffer.

Набор дескрипторов теперь был выделен, но дескрипторы внутри него все еще нуждаются в настройке. Дескрипторы, ссылающиеся на буферы, как наш uniform buffer, настраиваются при помощи структуры VkDescriptorBufferInfo. Эта структура определяет буфер и область в нем, которая содержит данные для дескриптора:

VkDescriptorBufferInfo bufferInfo = {};
bufferInfo.buffer = uniformBuffer;
bufferInfo.offset = 0;
bufferInfo.range = sizeof(UniformBufferObject);

Конфигурация дескрипторов обновляется при помощи функции vkUpdateDescriptorSets, которая принимает массив структур VkWriteDescriptorSet в качестве параметра. Первые два поля определяют набор дескрипторов для обновления и привязок. В нашем случае даем uniform buffer индекс привязки 0. Так же стоит помнить, что дескрипторы могут быть массивами, потому нужно указать первый индекс в массиве, что мы хотим обновить. В данном случае массив не используется, потому индекс просто 0.

VkWriteDescriptorSet descriptorWrite = {};
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrite.dstSet = descriptorSet;
descriptorWrite.dstBinding = 0;
descriptorWrite.dstArrayElement = 0;

Далее следует указать тип дескриптора, снова. Это позволяет обновлять несколько дескрипторов в массиве сразу, начиная с индекса dstArrayElement. Поле  descriptorCount определяет как много элементов в массиве следует обновить.

descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrite.descriptorCount = 1;

Последнее поле ссылается на массив со структурами descriptorCount, которые фактически настраивают дескрипторы. Оно зависит от типа дескриптора, нужно использовать одно из трех.

Поле pBufferInfo используется для дескрипторов, которые ссылаются на буферы данных.

pImageInfo используется для дескрипторов, ссылающихся на данные изображений.

pTexelBufferView – буферы представлений.

В нашем случае дескриптор основан на буферах, так что выбор очевиден.

descriptorWrite.pBufferInfo = &bufferInfo;
descriptorWrite.pImageInfo = nullptr; // Optional
descriptorWrite.pTexelBufferView = nullptr; // Optional

А дальше идет функция vkUpdateDescriptorSets, название которой, думаю говорит само за себя. Она принимает два вида массивов: VkWriteDescriptorSet и VkCopyDescriptorSet (последний может быть использован для копирования конфигурации дескрипторов):

vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);

Ну а теперь будет использовать все то, что написали ранее.

Следует обновить функцию, для того, что бы связать набор дескрипторов с дескрипторами в шейдере. Используем cmdBindDescriptorSets:

vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);

В отличии от буферов вершин и индексов, наборы дескрипторов не являются уникальным элементов для графического конвейера. Потому необходимо указать хотим привязать к графическому или вычислительному конвейеру. Следующий параметр – макет, на котором будет базироваться дескриптор.

Далее идут: индекс первого набора дескрипторов, количество наборов, массив наборов.

Последние два параметра задают массив смещений (для динамических дескрипторов), к чему еще вернемся.

Если сейчас запустить программу, тогда окажется, что ничего не видно. Проблема в том, что из-за «перевернутой» Y вершины отрисовываются по часовой стрелке, вместо того, что бы идти против часовой стрелки. Это вызывает отбор невидимых поверхностей, тем самым предотвращает любую отрисовку. Перейдем к функции создания графического конвейера и изменим cullFace в VkPipelineRasterizationStateCreateInfo:

rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;

Ну а теперь можно запустить программу и увидеть вращающийся квадрат, находящийся к нам под углом:

Если понадобится использовать несколько наборов дескрипторов, то определите descriptor layout для каждого набора во время создания конвейера. А в шейдере пишите примерно так:

layout(set = 0, binding = 0) uniform UniformBufferObject { ... }

Можно использовать эту фичу для наложения дескрипторов на отдельные объекты и разделять дескрипторы на отдельные наборы дескрипторов. Все это позволит  избежать перепривязки большинства дескрипторов между вызовами отрисовки, что, потенциально, гораздо более эффективно.

З.Ы. так же частично отредактировал предыдущие уроки. Убрал большую часть опечаток, периодически в тексте не отображались символы «<>» и то что между ними (были проблемы с плагином для кода), сейчас все на месте (листинги являются просто *cpp файлами, потому на них проблема не распространялась). Небольшие стилистические правки уроков 39-42.

Так же в начале урока 39 был добавлен такой абзац:

Из-за особенностей переводимых разными людьми текстов, перед этим был вынужден переводить «handle» как «дескриптор», как общепринятый вариант, что бы не внести больше путаницы в умы читателей, хотя это не совсем корректно (так же, как использовать слово «поток» для thread, но ведь мы все привыкли слышать что-то вроде «Многопоточность», а это в свою очередь путает начинающих программистов, ведь «потоки» так же используются для «streams» (потоки ввода/вывода). А еще графический конвейер на самом деле «graphics pipeline», пусть здесь и не возникает путаницы, пока. Этот список можно продолжать, но суть думаю уловили). И вот теперь, в серии уроков под названием «Uniform buffers» словом «дескриптор» будет обозначаться «descriptor» непосредственно.

И листинг.

Main Admin

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *