Я из IT-компании CGTribe и здесь я перевожу руководство к Vulkan API. Ссылка на оригинал — vulkan-tutorial.com.
Моя следующая публикация посвящена переводу главы Window surface из раздела Drawing a triangle, подраздела Presentation.
Поскольку Vulkan API полностью независим от платформы, он не может напрямую взаимодействовать с оконной системой. Чтобы Vulkan мог выводить результат на экран, необходимо использовать стандартизованные расширения WSI (Window System Integration). В этой главе мы расскажем про одно из них —
Window surface нужно создать сразу после VkInstance, поскольку это может повлиять на выбор физического устройства. Нужно помнить, что window surfaces — полностью опциональный компонент Vulkan. Вы можете обойтись без него, если вам нужен offscreen рендеринг. Это позволяет избежать таких хаков, как, например, создание невидимого окна для OpenGL.
Начнем с того, что добавим новый член класса
Процесс создания объекта
Мы покажем, как можно использовать это расширение для создания surface в Windows, однако в руководстве оно нам не понадобится. В библиотеке GLFW, которую мы используем, есть функция-обертка
Window surface – это объект Vulkan, поэтому мы должны заполнить структуру
Здесь для получения сырого
После этого мы можем создать surface с помощью функции
Для других платформ подход аналогичен. Для Linux, например, используется функция
Функция
Вместо структуры для вызова GLFW нужны простые параметры, что упрощает реализацию функции:
В функцию передаются следующие параметры:
У GLFW нет специальной функции для уничтожения surface, но это легко можно сделать непосредственно с помощью Vulkan:
Не забудьте уничтожить surface до VkInstance.
Хотя конкретная реализация Vulkan может поддерживать интеграцию с оконной системой, это не значит что каждое из устройств в системе это тоже поддерживает. Поэтому нам нужно расширить
Вполне возможно, что семейства, поддерживающие команды рисования, и семейства, поддерживающие отображение, не будут совпадать. Поэтому мы должны изменить структуру
Изменим функцию
Затем проверим значение типа
Обратите внимание, что очень вероятно, что в конечном итоге это будет одно и то же семейство очередей, но мы будем рассматривать их, как если бы они были отдельными очередями. Однако вы можете отдать предпочтение физическому устройству с поддержкой графических операций и с поддержкой отображения в одной очереди.
Осталось изменить процесс создания логического устройства, чтобы создать очередь с поддержкой отображения и получить дескриптор
Нам нужно несколько
Изменим структуру
В результате, если семейство для рисования и отображения одно и тоже, то его индекс будет передан один раз.
Наконец добавим вызов для получения дескриптора очереди. Если семейство очередей одно, дескрипторы должны иметь одинаковое значение.
В следующей главе мы рассмотрим swap chains и расскажем, как они помогают выводить изображения на экран.
Код C++
			  Моя следующая публикация посвящена переводу главы Window surface из раздела Drawing a triangle, подраздела Presentation.
                        Содержание
                        
1. Вступление
2. Краткий обзор
3. Настройка окружения
4. Рисуем треугольник
5. Буферы вершин
6. Uniform-буферы
7. Текстурирование
8. Буфер глубины
9. Загрузка моделей
10. Создание мип-карт
11. Multisampling
FAQ
Политика конфиденциальности
                    2. Краткий обзор
3. Настройка окружения
4. Рисуем треугольник
- Подготовка к работе
 
- Отображение на экране
 - Window surface
- Swap chain
- Image views
 
- Основы графического конвейера (pipeline)
- Отрисовка
- Повторное создание цепочки показа
5. Буферы вершин
- Описание
- Создание буфера вершин
- Staging буфер
- Буфер индексов
6. Uniform-буферы
- Дескриптор layout и буфера
- Дескриптор пула и sets
7. Текстурирование
- Изображения
- Image view и image sampler
- Комбинированный image sampler
8. Буфер глубины
9. Загрузка моделей
10. Создание мип-карт
11. Multisampling
FAQ
Политика конфиденциальности
Window surface
Поскольку Vulkan API полностью независим от платформы, он не может напрямую взаимодействовать с оконной системой. Чтобы Vulkan мог выводить результат на экран, необходимо использовать стандартизованные расширения WSI (Window System Integration). В этой главе мы расскажем про одно из них —
VK_KHR_surface. Расширение предоставляет объект VkSurfaceKHR — абстрактный тип поверхности для показа отрендеренных изображений. Эта поверхность будет создана при поддержке окна GLFW, полученного нами ранее. VK_KHR_surface – это расширение Vulkan уровня экземпляра. У нас оно уже подключено, поскольку находится в списке расширений, возвращаемых функцией glfwGetRequiredInstanceExtensions. В списке есть и другие расширения WSI, которые мы будем использовать в следующих главах.Window surface нужно создать сразу после VkInstance, поскольку это может повлиять на выбор физического устройства. Нужно помнить, что window surfaces — полностью опциональный компонент Vulkan. Вы можете обойтись без него, если вам нужен offscreen рендеринг. Это позволяет избежать таких хаков, как, например, создание невидимого окна для OpenGL.
Создание window surface
Начнем с того, что добавим новый член класса
surface сразу после вызова debugMessenger. VkSurfaceKHR surface;Процесс создания объекта
VkSurfaceKHR зависит от платформы. Так, например, для создания в Windows нужны дескрипторы HWND и HMODULE. Для разных платформ у Vulkan есть платформенно-зависимое дополнение к расширению, которое в Windows называется VK_KHR_win32_surface. Оно также автоматически включено в список расширений, возвращаемых функцией glfwGetRequiredInstanceExtensions.Мы покажем, как можно использовать это расширение для создания surface в Windows, однако в руководстве оно нам не понадобится. В библиотеке GLFW, которую мы используем, есть функция-обертка
glfwCreateWindowSurface, содержащая специфичный для платформы код. Но было бы не плохо увидеть, что происходит за кулисами.Window surface – это объект Vulkan, поэтому мы должны заполнить структуру
VkWin32SurfaceCreateInfoKHR, чтобы создать его. В ней есть два важных параметра: hwnd и hinstance — дескрипторы окна и текущего процесса.VkWin32SurfaceCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
createInfo.hwnd = glfwGetWin32Window(window);
createInfo.hinstance = GetModuleHandle(nullptr);Здесь для получения сырого
HWND используется функция glfwGetWin32Window, а для получения HINSTANCE - функция GetModuleHandle. После этого мы можем создать surface с помощью функции
vkCreateWin32SurfaceKHR, в которую передаются следующие параметры: экземпляр Vulkan, информация о surface, кастомный аллокатор и указатель для записи результата. Технически это функция расширения WSI, но она используется так часто, что была включена в стандартный загрузчик Vulkan, поэтому вам не нужно загружать ее явно. if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) {
    throw std::runtime_error("failed to create window surface!");
}Для других платформ подход аналогичен. Для Linux, например, используется функция
vkCreateXcbSurfaceKHR. Функция
glfwCreateWindowSurface делает именно эту работу, но имеет свою реализацию для каждой платформы. Интегрируем ее в нашу программу. Для этого добавим функцию createSurface, которая вызывается из initVulkan сразу после createInstance и setupDebugMessenger. void initVulkan() {
    createInstance();
    setupDebugMessenger();
    createSurface();
    pickPhysicalDevice();
    createLogicalDevice();
}
void createSurface() {
}Вместо структуры для вызова GLFW нужны простые параметры, что упрощает реализацию функции:
void createSurface() {
    if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
        throw std::runtime_error("failed to create window surface!");
    }
}В функцию передаются следующие параметры:
VkInstance, указатель на окно GLFW, кастомный аллокатор и указатель для записи результата. Функция возвращает VkResult.У GLFW нет специальной функции для уничтожения surface, но это легко можно сделать непосредственно с помощью Vulkan:
void cleanup() {
        ...
        vkDestroySurfaceKHR(instance, surface, nullptr);
        vkDestroyInstance(instance, nullptr);
        ...
    }Не забудьте уничтожить surface до VkInstance.
Проверка поддержки отображения
Хотя конкретная реализация Vulkan может поддерживать интеграцию с оконной системой, это не значит что каждое из устройств в системе это тоже поддерживает. Поэтому нам нужно расширить
isDeviceSuitable, чтобы быть уверенными, что устройство может отображать изображения на surface, которую мы создали. Поскольку отображение — это процесс, происходящий через очереди команд, то задача заключается в том, чтобы найти семейство очередей, которое поддерживает отображение в созданную surface. Вполне возможно, что семейства, поддерживающие команды рисования, и семейства, поддерживающие отображение, не будут совпадать. Поэтому мы должны изменить структуру
QueueFamilyIndices, чтобы учитывать этот факт. struct QueueFamilyIndices {
    std::optional<uint32_t> graphicsFamily;
    std::optional<uint32_t> presentFamily;
    bool isComplete() {
        return graphicsFamily.has_value() && presentFamily.has_value();
    }
};Изменим функцию
findQueueFamilies, чтобы найти семейство очередей с поддержкой отображения на surface нашего окна. Для проверки используем функцию vkGetPhysicalDeviceSurfaceSupportKHR, которая принимает следующие параметры: физическое устройство, индекс семейства очередей и surface. Добавим вызов функции в тот же цикл, в котором находится проверка VK_QUEUE_GRAPHICS_BIT: VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);Затем проверим значение типа
VkBool32 и сохраним индекс нужного нам семейства: if (presentSupport) {
    indices.presentFamily = i;
}Обратите внимание, что очень вероятно, что в конечном итоге это будет одно и то же семейство очередей, но мы будем рассматривать их, как если бы они были отдельными очередями. Однако вы можете отдать предпочтение физическому устройству с поддержкой графических операций и с поддержкой отображения в одной очереди.
Создание очереди отображения
Осталось изменить процесс создания логического устройства, чтобы создать очередь с поддержкой отображения и получить дескриптор
VkQueue. Добавим переменную класса:VkQueue presentQueue; Нам нужно несколько
VkDeviceQueueCreateInfo, чтобы создать очередь для каждого из семейств. Элегантный способ это сделать — использовать std::set, чтобы выделить уникальные семейства из найденных: #include <set>
...
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()};
float queuePriority = 1.0f;
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.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
createInfo.pQueueCreateInfos = queueCreateInfos.data();В результате, если семейство для рисования и отображения одно и тоже, то его индекс будет передан один раз.
Наконец добавим вызов для получения дескриптора очереди. Если семейство очередей одно, дескрипторы должны иметь одинаковое значение.
vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);В следующей главе мы рассмотрим swap chains и расскажем, как они помогают выводить изображения на экран.
Код C++
 
          