Очередь команд, как ни странно, содержит в себе команды, отправленные на обработку GPU. Сами команды не помещаются в очередь непосредственно, а записываются в буферы команд, которые, в свою очередь, могут сами быть записаны в такие же. Буферы команд выделяются в пулах команд и могут быть отправлены в очередь на обработку.
План:
• Создать очередь
• Создать пул команд и обнулить его
• Создать буферы команд, для последующей работы с каждым из изображений цепочки обмена
• Записать командные буферы
Очередь команд
Очереди различаются по своим свойствам на семейства очередей, но т.к. статья носит характер лишь быстрого экскурса, мы не будем заострять внимание на этом нюансе. Вообще говоря, мы не можем создать очередь, только лишь получить уже существующую:
Стоит обратить внимание на то, что данная функция не возвращает успеха выполнения. Согласно спецификации, параметры queueFamilyIndex и queueIndex должны находится в приемлемых диапазонах семейств и очереди, для конкретного семейства.
Еще одним полезным свойством очереди является то, что можно ожидать на ее выполнение:
VkResult vkQueueWaitIdle(
VkQueue queue);
Пока это не сильно изменит поведение нашей программы, ведь в очереди ничего нет. Тем не менее, добавим ожидание в главном цикле, просто чтобы проверить работоспособность уже написанного кода:
if(vkQueueWaitIdle(vk_queue) != VkResult::VK_SUCCESS)
{
throw std::exception("failed to wait for queue");
}
Пул команд
Пул команд является объектом, из которого выделяется память на создание буферов команд. Соответственно, в зависимости от реализации, выделение нескольких буферов из одного пула может помочь сэкономить ресурсы.
В отличие от предыдущих структур, здесь поле flags составляется из битовых операций над значениями VkCommandPoolCreateFlagBits и устанавливает некоторые его свойства. Так же, после создания, нам потребуется обнулить пул.
Буферы команд хранят готовые последовательности команд и экономят время, необходимое на синхронизацию CPU-GPU. Вместо того, чтобы отправлять на обработку сотню команд, мы синхронизируемся лишь единожды, передавая за раз все необходимое.
Буферы не создаются, а выделяются из существующего пула:
Мы сознательно упускаем описание тривиальной структуры VkCommandBufferBeginInfo.
Запись буферов
Т.к. у нас пока не хватает деталей для вывода треугольника, для начала было бы неплохо хотя бы залить экран цветом. Тем не менее, есть небольшая загвоздка: для вывода в изображение и для вывода самого изображения, оно должно пребывать в разных форматах. Мы решим эту проблему, следующим образом:
1. Первое преобразование (в формат, необходимый для рисования в изображение) уже происходит в нашем пассе вывода
2. Второе преобразование (в формат, необходимый для вывода изображения на экран) будет производится в буфера команд по завершению пасса, с помощью специального объекта – барьера
Составим план:
• Выделяем необходимое количество буферов команд (по одному на каждый буфер кадра)
• Для каждого буфера команд (и, соответственно, кадра и вида):
o Обнуляем буфер
o Начинаем запись
o Начинаем вывод (для очистки экрана)
o Завершаем вывод
o Переводим изображение в необходимый для вывода поверхность формат
o Завершаем запись
Барьер является командой, устанавливающей зависимости для других команд, записанных в буфере до и после него:
С их помощью можно изменять некоторые параметры изображения прямо во время выполнения. Так мы изменим планировку на приемлемую для вывода (VkImageLayout::VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) и разрешим доступ к памяти на чтение (VkAccessFlagBits::VK_ACCESS_MEMORY_READ_BIT).