Vulkan API — урок 18. Создание swap chain (+листинг)

Итак, теперь у нас есть все необходимое, что бы создать исправно работающую swap chain.

Создадим функцию createSwapChain, которая начинается с вызова функций, написанных в предыдущих двух уроках. Сама фунция должна вызываться после создания логического устройства в initVulkan.

void initVulkan() {
    createInstance();
    setupDebugCallback();
    createSurface();
    pickPhysicalDevice();
    createLogicalDevice();
    createSwapChain();
}

void createSwapChain() {
    SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);

    VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
    VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
    VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
}

Есть еще некоторые мелочи, которые нужно настроить, но они столь незначительны, что для них не нужно создавать отдельные функции. Одна из них — количество изображений в swap chain, по сути — длина очереди. Реализация определяет минимально допустимое значение, но мы, по возможности, будем увеличивать его на единицу, для возможности реализовать тройную буферизацию

uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
    imageCount = swapChainSupport.capabilities.maxImageCount;
}

Значение 0 для maxImageCount означает, что количество изображений не ограничено, за исключением ограничений самой памяти, следовательно это должно быть проверено.

Ну и традиционно для объектов Vulkan’a создание swap chain требует заполнение структуры. Начинается все очень знакомо:

VkSwapchainCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface;

После задания параметров поверхности, к которой должна быть привязана swap chain, указываются параметры изображений цепочки:

createInfo.minImageCount = imageCount;
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

imageArrayLayers определяет количество слоев, из которых состоит каждое изображение. Обычно оно равно 1, если вы конечно не разрабатываете стереоскопическое  3D приложение.

imageUsage бит определяет для какого рода операций мы будет использовать изображения в swap chain. Данный туториал пока будет использовать рендер непосредственно изображений, что означает, что они используются для наложения цвета. Вам так же возможно понадобится рендерить изображения по отдельности, допустим для пост-обработки. В таком случае можно использовать значение VK_IMAGE_USAGE_TRANSFER_DST_BIT и использовать операции с памятью для передачи отрендеренного изображения в swap chain.

QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
uint32_t queueFamilyIndices[] = {(uint32_t) indices.graphicsFamily, (uint32_t) indices.presentFamily};

if (indices.graphicsFamily != indices.presentFamily) {
    createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
    createInfo.queueFamilyIndexCount = 2;
    createInfo.pQueueFamilyIndices = queueFamilyIndices;
} else {
    createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
    createInfo.queueFamilyIndexCount = 0; // Optional
    createInfo.pQueueFamilyIndices = nullptr; // Optional
}

Далее нам необходимо указать дескриптор изображений цепи, который будет использоваться несколькими семействами очередей. Это имеет значение, т.к. ранее мы предположили, что для отрисовки и для представления будут использоваться различные семейства. В случае, если ваша программа в будущем будет иметь очень богатый функционал, то нечто подобное придется реализовывать однозначно.

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

  • VK_SHARING_MODE_EXCLUSIVE: изображение принадлежит одному семейству в один момент времени и должно быть явно передано другому семейству. Данный вариант обеспечивает наилучшую производительность.
  • VK_SHARING_MODE_CONCURRENT: изображение может быть использовано несколькими семействами без явной передачи.

Если семейства отличаются, тогда будет использоваться concurrent в данном туториале, что бы не делать главы о «собственности» (ownership), т.к. они потянут за собой дополнительную информацию, о которой будет рассказано несколько позже. Режим concurrent следует указывать заранее, перед тем как семейства начнут совместно использовать параметры queueFamilyIndexCount и pQueueFamilyIndices. Если графическое семейство и семейство представления окажутся одним и тем же семейством, что реализовано в большинстве видеокарт, потому что concurrent требует указать как минимум два семейства.

createInfo.preTransform = swapChainSupport.capabilities.currentTransform;

Мы можем указать, что следует применять к изображениям в swap chain некоторое преобразование, если это поддерживается (supportedTransforms в capabilities), как то допустим поворот на 90º или зеркальное отражение. Если же вы не хотите использовать никаких преобразований, то просто укажите текущее преобразование.

createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;

compositeAlpha определяет должен ли альфа канал использоваться для смешения с другими окнами в оконной системе. Вы почти всегда будете игнорировать альфа канал, в таком случае ваш выбор – VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR.

createInfo.presentMode = presentMode;

presentMode говорит само за себя.

createInfo.clipped = VK_TRUE;

clipped устанавливается VK_TRUE, что позволяет не заботиться о цвете пикселей, которых мы не видим, например из-за того, что другое окно перекрыло их.

createInfo.oldSwapchain = VK_NULL_HANDLE;

 Вполне возможно, что имеющаяся swap chain станет недействительной или неоптимизированной во время работы приложения. Причиной тому может быть закрытие окна, перевод в полноэкранный режим и т.д. В таком случае новая swap chain должна быть создана с нуля, и ей будут переданы данные старой цепи. Это сложная и отдельно стоящая тема, к которой еще успеем вернуться. Но пока это у нас первая и единственная swap chain.

Теперь добавим элемент класса для хранения объекта VkSwapChainKHR, не забыв про VDeleter. Убедитесь, что добавили все это после device, что бы он был подчищен до логического устройства.

VDeleter<VkSwapchainKHR> swapChain{device, vkDestroySwapchainKHR};

Теперь создадим swap chain простым вызовом vkCreateSwapchainKHR:

if (vkCreateSwapchainKHR(device, &createInfo, nullptr, swapChain.replace()) != VK_SUCCESS) {
    throw std::runtime_error("failed to create swap chain!");
}

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

Извлечение изображений swap chain

Итак, цепочка была создана, теперь осталось получить дескрипторы изображений VkImage. Мы будем ссылаться на них во время операций рендеринга в следующих уроках. Добавим член класса для хранения дескрипторов:

std::vector<VkImage> swapChainImages;

Изображения были созданы по средствам реализации swap chain и они автоматически будут почищены во время удаления цепочки, потому здесь VDeleter нам не понадобится.

Теперь добавим код для получения дескрипторов в конец функции createSwapChain, прямо после вызова vkCreateSwapchainKHR. Ну а сам процесс покажется вам очень знакомым, он аналогичен получению других массивов в Vulkan’е и для него используем двукратный вызов vkGetSwapchainImagesKHR.

vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());

 Обратите внимание, что когда мы создали swap chain, мы указали количество желаемых изображений в поле под названием minImageCount. Т.к. видно из названия, что количество «min», то реализация позволяет создавать больше изображений, из-за чего нам и нужно явным образом запросить количество заново.

И последнее, хранение формата и размера выбранных нами для изображений в swap chain, пусть хранятся в переменных, они понадобятся нам в дальнейшем.

VDeleter<VkSwapchainKHR> swapChain{device, vkDestroySwapchainKHR};
std::vector<VkImage> swapChainImages;
VkFormat swapChainImageFormat;
VkExtent2D swapChainExtent;

...

swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;

Теперь же мы имеем набор изображений, которые могут быть отрисованы внутри программы и в дальнейшем показаны в окне. (Обратите внимания, что мы создали изображения, но они пустые, это шаблоны, и мы должны будем именно сначала их как-то создать (пусть даже и вставить из совсем стороннего источника), а потом выводить).

Ну и листинг.

Main Admin

3 Comments

  1. В статье когда писали
    createInfo.minImageCount = imageCount;
    createInfo.imageFormat = surfaceFormat.format;
    createInfo.imageColorSpace = surfaceFormat.colorSpace;
    createInfo.imageExtent = extent;
    createInfo.imageArrayLayers = 1;
    createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

    Забыли объявить VkSwapchainCreateInfoKHR createInfo = {};

  2. Нет, даже не одну строку забыли, а три:
    VkSwapchainCreateInfoKHR createInfo = {};
    createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
    createInfo.surface = surface;

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

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