Vulkan. Руководство разработчика. Window surface

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Я из IT-компании CGTribe и здесь я перевожу руководство к Vulkan API. Ссылка на оригинал — vulkan-tutorial.com.

Моя следующая публикация посвящена переводу главы Window surface из раздела Drawing a triangle, подраздела Presentation.

Содержание
1. Вступление

2. Краткий обзор

3. Настройка окружения

4. Рисуем треугольник

  1. Подготовка к работе
    • Базовый код
    • Экземпляр (instance)
    • Слои валидации
    • Физические устройства и семейства очередей
    • Логическое устройство и очереди
  2. Отображение на экране
    • Window surface
    • Swap chain
    • Image views
  3. Основы графического конвейера (pipeline)
  4. Отрисовка
  5. Повторное создание цепочки показа

5. Буферы вершин

  1. Описание
  2. Создание буфера вершин
  3. Staging буфер
  4. Буфер индексов

6. Uniform-буферы

  1. Дескриптор layout и буфера
  2. Дескриптор пула и sets

7. Текстурирование

  1. Изображения
  2. Image view и image sampler
  3. Комбинированный image sampler

8. Буфер глубины

9. Загрузка моделей

10. Создание мип-карт

11. Multisampling

FAQ

Политика конфиденциальности


Window surface


  • Создание 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++
Источник: https://habr.com/ru/post/539174/


Интересные статьи

Интересные статьи

Китайская компания Radxa на днях выпустила одноплатный ПК Rock Pi X. Это событие могло бы пройти никем не замеченным, если бы не особенности новинки. Дело в том, что она базируется ...
Чуть больше года назад выкладывал свой скрипт по автоматизации настройки Windows 10. Давеча переписал Windows 10 Sophia Script в виде примитивного модуля на 6 000 строк для одноразов...
Microsoft Exchange — большой комбайн, который включает в себя прием и обработку писем, а также веб интерфейс для вашего почтового сервера, доступ к корпоративным календарям и задачам. Exchange ин...
Типичный вопрос разработчиков под Windows: «Почему здесь до сих пор нет <ВСТАВЬТЕ ТУТ ЛЮБИМУЮ КОМАНДУ LINUX>?». Будь то мощное пролистывание less или привычные инструменты grep или sed, раз...
Неожиданно тёплый приём, оказанный публикой Хабра моему посту о самодельном компиляторе XD Pascal для MS-DOS, заставил меня задуматься. Не досадно ли, что любительский проект, которому я отдал не...