Продолжим работу с кодом из прошлой темы и определим проход рендеринга:
// Файл main.cpp
// g++ main.cpp -lvulkan -lglfw -o app && ./app
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <iostream>
#include <stdexcept>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <set>
#include <cstdint> // для uint32_t
#include <limits> // для std::numeric_limits
#include <algorithm> // для std::clamp
#include <fstream> // для загрузки файлов шейдеров
const uint32_t WIDTH = 300;
const uint32_t HEIGHT = 250;
// структура для проверки возможностей поверхности
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities; // возможности поверхности
std::vector<VkSurfaceFormatKHR> formats; // доступные форматы
std::vector<VkPresentModeKHR> presentModes; // доступные режимы представления
std::vector<VkImageView> swapChainImageViews; // представления изображений
};
// структура для хранения индексов семейств очередей
struct QueueFamilyIndices {
uint32_t graphicsFamilyIndex;
uint32_t presentFamilyIndex;
VkBool32 graphicsSupport;
VkBool32 presentSupport;
bool isComplete() {
return graphicsSupport && presentSupport;
}
};
// функция загрузки файлов шейдеров
static std::vector<char> readFile(const std::string& filename) {
std::ifstream file(filename, std::ios::ate | std::ios::binary);
// открываем файл
if (!file.is_open()) {
throw std::runtime_error("Не удалось загрузить файл!");
}
size_t fileSize = (size_t) file.tellg(); // получаем размер
std::vector<char> buffer(fileSize); // определяем буфер для считывания
file.seekg(0); // перемещаемся в начало файла
file.read(buffer.data(), fileSize); // считываем файл в буфер
file.close(); // закрываем поток файла
return buffer;
}
class HelloApplication {
public:
void run() {
initWindow();
initVulkan();
mainLoop();
cleanup();
}
private:
GLFWwindow* window;
VkInstance instance;
VkSurfaceKHR surface; // поверхность рисования
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; // физическое устройство
VkDevice device; // логическре устройство
VkQueue graphicsQueue; // графическая очередь
VkQueue presentQueue; // очередь представления
VkSwapchainKHR swapChain; // цепочка буферов
VkFormat swapChainImageFormat; // формат изображений
VkExtent2D swapChainExtent; // размер изображений
std::vector<VkImage> swapChainImages; // полученные изображения
std::vector<VkImageView> swapChainImageViews; // представления изображений
VkRenderPass renderPass; // проход рендеринга
// создаем окно
void initWindow() {
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
window = glfwCreateWindow(WIDTH, HEIGHT, "METANIT.COM", nullptr, nullptr);
}
// инициализация всех аспектов Vulkan
void initVulkan() {
createInstance(); // создание VkInstance
createSurface(); // создание поверхности рисования
selectPhysicalDevice(); // выбор физического устройства
createLogicalDevice(); // создание логического устройства
createSwapChain(); // создание цепочки буферов
createImageViews(); // создание представлений изображений
createRenderPass(); // настройка конфигурации рендеринга
createGraphicsPipeline(); // создаем графический конвейер
}
// создаем проход рендеринга
void createRenderPass() {
// определяем цветовое прикрепление
VkAttachmentDescription colorAttachment{};
colorAttachment.format = swapChainImageFormat; // формат изображений, полученный из цепочки буферов
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; // 1 образец на изображение
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // очистить буфер кадра до черного цвета перед рисованием нового кадра
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; // отрисованное содержимое будет сохранено в памяти и может быть прочитано позже
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // не имеет значения
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // не имеет значения
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // не имеет значения
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // для вывода изображений на экран
// определяем ссылку на цветовое прикрепление
VkAttachmentReference colorAttachmentRef{};
colorAttachmentRef.attachment = 0; // индекс прикрепления в массиве
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
// определяем подпроход рендеринга
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; // тип конвейера - графический конвейер
subpass.colorAttachmentCount = 1; // количество цветовых прикреплений
subpass.pColorAttachments = &colorAttachmentRef; // выше созданная ссылка на цветовое прикрепление
// определяем параметры для создания прохода рендеринга
VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1; // количество применяемых прикреплений
renderPassInfo.pAttachments = &colorAttachment; // применяемые прикрепления
renderPassInfo.subpassCount = 1; // количество подпроходов
renderPassInfo.pSubpasses = &subpass; // используемые подпроходы
// создаем проход рендеринга
if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
throw std::runtime_error("Не удалось создать проход рендеринга!");
}
}
// создание модуля шейдера из байт-кода шейдера
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;
}
// создание представлений из изображений цепочки буферов
void createImageViews() {
swapChainImageViews.resize(swapChainImages.size()); // устанавливаем размер вектора
for (size_t i{}; i < swapChainImages.size(); i++) {
VkImageViewCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
createInfo.image = swapChainImages[i];
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; // изображение рассматривается как 2D-текстура
createInfo.format = swapChainImageFormat; // формат изображений
// rgba-компоненты получают значения по умолчанию
createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
createInfo.subresourceRange.baseMipLevel = 0;
createInfo.subresourceRange.levelCount = 1;
createInfo.subresourceRange.baseArrayLayer = 0;
createInfo.subresourceRange.layerCount = 1;
// создаем представление изображения
if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) {
throw std::runtime_error("Не удалось создать представление изображения!");
}
}
}
// установка формата изображений
VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
for (const auto& availableFormat : availableFormats) {
if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
return availableFormat;
}
}
return availableFormats[0];
}
// определение режима представления
VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
for (const auto& availablePresentMode : availablePresentModes) {
if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
return availablePresentMode;
}
}
return VK_PRESENT_MODE_FIFO_KHR;
}
// определение размеров для цепочки буферов
VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
// если текущая ширина не указана как максимальное значение для uint32_t
if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
// то возвращаем текущие размеры
return capabilities.currentExtent;
} else {
int width, height;
glfwGetFramebufferSize(window, &width, &height);
VkExtent2D actualExtent = {
static_cast<uint32_t>(width),
static_cast<uint32_t>(height)
};
actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
return actualExtent;
}
}
// создаение цепочки буферов
void createSwapChain() {
// получаем поддерживаемые устройством форматы, режимы представления и размеры
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);
// выбираем формат поверхности
VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
// выбираем режим представления
VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
// устанавливаем размеры
VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
// определяем минимальное количество изображения, которое требуется для работы
// и увеличиваем его на 1 для оптимизации работы драйвера
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
// проверяем, что не превышаем максимальное количество изображений,
// делая это, где 0 — это особое значение, которое означает, что максимума нет
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
imageCount = swapChainSupport.capabilities.maxImageCount;
}
// определяем конфигурацию для создания цепочки буферов
VkSwapchainCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface;
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; // изображение используется в качестве цветового прикрепления
// Указываем, как обрабатывать изображения цепочки буферов, которые будут использоваться в нескольких семействах очередей.
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
uint32_t queueFamilyIndices[] = {indices.graphicsFamilyIndex, indices.presentFamilyIndex};
// если семейство графической очереди отличается от очереди представления
if (indices.graphicsFamilyIndex != indices.presentFamilyIndex) {
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; // обе очереди совместно владеют изображением
createInfo.queueFamilyIndexCount = 2; // две очереди
createInfo.pQueueFamilyIndices = queueFamilyIndices; // массив индексов семейств очередей
} else {
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; // изображением владеет одна очередь
createInfo.queueFamilyIndexCount = 0; // Необязательно
createInfo.pQueueFamilyIndices = nullptr; // Необязательно
}
// не применяем никаких преобразований
createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
// игнорирум альфа-канал
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = presentMode; // режим представления
createInfo.clipped = VK_TRUE; // включаем обрезку скрытых пикселей
createInfo.oldSwapchain = VK_NULL_HANDLE; // нет старых цепочек буферов
// создаем цепочку буферов
if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
throw std::runtime_error("Не удалось создать цепочку буферов!");
}
// сохраняем выбранные формат и размеры изображений
swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;
// сначала получаем количество изображений
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
// на основе полученного количества определяем вектор
swapChainImages.resize(imageCount);
// получаем в вектор изображения
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
}
void mainLoop() {
while (!glfwWindowShouldClose(window)) {
glfwWaitEvents();
}
}
void cleanup() {
// удаляем проход рендеринга
vkDestroyRenderPass(device, renderPass, nullptr);
// удаляем все представления изображений
for (auto imageView : swapChainImageViews) {
vkDestroyImageView(device, imageView, nullptr);
}
// удаляем цепочку буферов
vkDestroySwapchainKHR(device, swapChain, nullptr);
// удаляем логическое устройство
vkDestroyDevice(device, nullptr);
// удаляем поверхность
vkDestroySurfaceKHR(instance, surface, nullptr);
// удаляем объект VkInstance
vkDestroyInstance(instance, nullptr);
glfwDestroyWindow(window);
glfwTerminate();
}
// создание VkInstance
void createInstance() {
VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;
// получаем расширения для GLFW
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
// устанавливаем расширения для VkInstance
createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;
if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
throw std::runtime_error("Не удалось создать VkInstance!");
}
}
// создаем поверхность
void createSurface() {
if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
throw std::runtime_error("Не удалось создать поверхность для рисования!");
}
}
// проверка поддержки цепочки буферов
SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
SwapChainSupportDetails details;
// проверяем поддерживаемые возможности
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
uint32_t formatCount;
// сначала получаем количество поддерживаемых форматов
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);
if (formatCount != 0) {
details.formats.resize(formatCount); // расширяем вектор, чтобы вместить все данные
// получаем все поддерживаемые форматы
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
}
uint32_t presentModeCount;
// получаем количество поддерживаемых режимов представления
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
if (presentModeCount != 0) {
details.presentModes.resize(presentModeCount);
// получаем все поддерживаемые режимы представления
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
}
return details;
}
// выбираем физическое устройство
void selectPhysicalDevice() {
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
if (deviceCount == 0) {
throw std::runtime_error("Подходящее устройство GPU отсутствует!");
}
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
// выбираем первое подходящее устройство
for (const auto& device : devices) {
if (isDeviceSuitable(device)) {
physicalDevice = device;
break;
}
}
if (physicalDevice == VK_NULL_HANDLE) {
throw std::runtime_error("Подходящее устройство GPU отсутствует!");
}
}
// создание логического устройства
void createLogicalDevice() {
// получаем обе очереди
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
// набор для индексов семейств очередей, если они совпадают, то остается только один индекс
std::set<uint32_t> uniqueQueueFamilies = {
indices.graphicsFamilyIndex,
indices.presentFamilyIndex
};
float queuePriority = 1.0f;
// проходим по всем индексам семейств очередей и создаем для каждого индекса структуру VkDeviceQueueCreateInfo
for (uint32_t queueFamily : uniqueQueueFamilies) {
VkDeviceQueueCreateInfo queueCreateInfo{};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamily;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back(queueCreateInfo);
}
VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
// устанавливаем обе очереди для создания устройства
createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
createInfo.pQueueCreateInfos = queueCreateInfos.data();
// подключаемые расширения
const std::vector<const char*> deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME};
// подключаем расширения
// устанавливаем количество расширений
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
// устанавливаем сами расширения
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
// устанавливаем поддерживаемые возможности
VkPhysicalDeviceFeatures deviceFeatures{};
createInfo.pEnabledFeatures = &deviceFeatures;
// создаем логическое устройство
if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
throw std::runtime_error("Не удалось создать логическое устройство!");
}
// создаем графическую очередь
vkGetDeviceQueue(device, indices.graphicsFamilyIndex, 0, &graphicsQueue);
// создаем очередь представления
vkGetDeviceQueue(device, indices.presentFamilyIndex, 0, &presentQueue);
}
// проверяем поддержку цепочки буферов
bool checkSwapchainExtensionSupport(VkPhysicalDevice device) {
uint32_t extensionCount;
// сначала получаем количество расширений
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
// получаем все доступные расширения
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
std::string extensionName {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; // строка, с которой сравниванием
for (const auto& extension : availableExtensions) {
// сравнение строк
if(extensionName == extension.extensionName) return true;
}
return false;
}
// проверка, является ли устройство подходящим
bool isDeviceSuitable(VkPhysicalDevice device) {
// получаем индексы графической очереди и очереди представления
QueueFamilyIndices indices = findQueueFamilies(device);
// если не поддерживаются графическая очередь и/или очередь представления
if(!indices.isComplete()) return false;
// если не поддерживаются расширения
if(!checkSwapchainExtensionSupport(device)) return false;
// получаем поддержку цепочки буферов
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
// считаем устройство подходящим, если поддерживается хотя бы один формат и хотя бы один режим представления
return !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
}
// получаем индексы семейства очередей
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
QueueFamilyIndices indices;
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
int i = 0;
// проходим по всем очередям
for (const auto& queueFamily : queueFamilies) {
// получаем индекс семейства с графической очередью
if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
indices.graphicsFamilyIndex = i;
indices.graphicsSupport = true;
}
// смотрим, поддерживается ли очередь представления
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &indices.presentSupport);
if (indices.presentSupport) indices.presentFamilyIndex = i;
if (indices.isComplete()) break; // если обе очереди установлены
i++;
}
return indices;
}
};
int main() {
HelloApplication app;
try {
app.run();
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
На уровне класса HelloApplication добавляется переменная, которая представляет проход рендеринга:
VkRenderPass renderPass; // проход рендеринга
Далее в коде класса определяется новая функция, которая будет создавать проход рендеринга:
void createRenderPass() {
// определяем цветовое прикрепление
VkAttachmentDescription colorAttachment{};
colorAttachment.format = swapChainImageFormat; // формат изображений, полученный из цепочки буферов
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; // 1 образец на изображение
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // очистить буфер кадра до черного цвета перед рисованием нового кадра
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; // отрисованное содержимое будет сохранено в памяти и может быть прочитано позже
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // не имеет значения
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // не имеет значения
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // не имеет значения
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // для вывода изображений на экран
// определяем ссылку на цветовое прикрепление
VkAttachmentReference colorAttachmentRef{};
colorAttachmentRef.attachment = 0; // индекс прикрепления в массиве
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
// определяем подпроход рендеринга
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; // тип конвейера - графический конвейер
subpass.colorAttachmentCount = 1; // количество цветовых прикреплений
subpass.pColorAttachments = &colorAttachmentRef; // выше созданная ссылка на цветовое прикрепление
// определяем параметры для создания прохода рендеринга
VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1; // количество применяемых прикреплений
renderPassInfo.pAttachments = &colorAttachment; // применяемые прикрепления
renderPassInfo.subpassCount = 1; // количество подпроходов
renderPassInfo.pSubpasses = &subpass; // используемые подпроходы
// создаем проход рендеринга
if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
throw std::runtime_error("Не удалось создать проход рендеринга!");
}
}
Здесь сначала создаем цветовое прикрепление, представленное одним из изображений из цепочки буфера:
VkAttachmentDescription colorAttachment{};
colorAttachment.format = swapChainImageFormat; // формат изображений, полученный из цепочки буферов
colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; // 1 образец на изображение
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // не имеет значения
colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // не имеет значения
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
Формат цветового прикрепления должен соответствовать формату изображений цепочки буферов
Поля loadOp и storeOp определяют, что делать с данными в прикреплении до рендеринга и после рендеринга. Для loadOp мы собираемся использовать операцию очистки,
чтобы очистить буфер кадра до черного цвета перед рисованием нового кадра. Для storeOp выполняем операцию сохранения, чтобы увидеть отрисованный треугольник на экране:
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // очистить буфер кадра до черного цвета перед рисованием нового кадра colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; // отрисованное содержимое будет сохранено в памяти и может быть прочитано позже
Аналогично настройки stencilLoadOp / stencilStoreOp применяются к прикреплению шаблона.
Только наше приложение ничего не будет делать с буфером шаблона, поэтому результаты загрузки и сохранения не имеют значения.
colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // не имеет значения colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // не имеет значения
Текстуры и буферы кадров в Vulkan представлены непрозрачным указателем VkImage с определенным форматом пикселей, однако расположение пикселей в памяти может меняться в зависимости от того, что надо сделать с изображением. Но изображения необходимо перевести в определенные макеты, которые подходят для операций с этми изображениями:
colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // для отображения на экране
Использование VK_IMAGE_LAYOUT_UNDEFINED для initialLayout означает, что нам все равно, в каком макете ранее находилось изображение.
Мы хотим, чтобы изображение было готово к представлению с использованием цепочки буферов после рендеринга, поэтому мы используем VK_IMAGE_LAYOUT_PRESENT_SRC_KHR в качестве finalLayout.
Далее определяем ссылку на цветовое прикрепление:
VkAttachmentReference colorAttachmentRef{};
colorAttachmentRef.attachment = 0;
colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
В данном случае наш массив прикреплений будет состоять из одного значения VkAttachmentDescription - из одного описания прикрепления, которое было ранее определено, поэтому его индекс равен 0.
Прикрепление будет использоваться для работы в качестве цветового прикрепления, а макет VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL обеспечит наилучшую производительность.
И после этого можно создать подпроход рендеринга:
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; // тип конвейера - графический конвейер
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef; // выше созданная ссылка на цветовое прикрепление
Здесь в массиве subpass.pColorAttachments устанавливается только одно цветовое прикрепление (с помощью ранее созданной ссылки на цветовое прикрепление)..
В завершении создает проход рендеринга, который состоит из ранее созданного подпрохода:
VkRenderPassCreateInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
renderPassInfo.attachmentCount = 1;
renderPassInfo.pAttachments = &colorAttachment;
renderPassInfo.subpassCount = 1;
renderPassInfo.pSubpasses = &subpass;
if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) {
throw std::runtime_error("Не удалось создать проход рендеринга!");
}
В функцию инициализации Vulkan добавляется вызов createRenderPass (перед построением графического конвейера):
void initVulkan() {
.........................................
createRenderPass(); // настройка конфигурации рендеринга
}
А в функции очистки ресурсов cleanup добавляем удаление созданного прохода рендеринга:
void cleanup() {
// удаляем проход рендеринга
vkDestroyRenderPass(device, renderPass, nullptr);
..........................................