[Программирование, C++, Работа с 3D-графикой, Разработка игр, CGI (графика)] Vulkan. Руководство разработчика. Window surface (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Я из IT-компании CGTribe и здесь я перевожу руководство к Vulkan API. Ссылка на оригинал — vulkan-tutorial.com.
Моя следующая публикация посвящена переводу главы Window surface из раздела Drawing a triangle, подраздела Presentation.
Содержание
SPL
1. Вступление
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++
===========
Источник:
habr.com
===========
===========
Автор оригинала: Alexander Overvoorde
===========Похожие новости:
- [Разработка веб-сайтов, Программирование, Учебный процесс в IT, DevOps] Анонс интенсива «Docker для разработчиков»
- [Разработка игр, Unreal Engine, Игры и игровые приставки] Боевая система в 9 Monkeys of Shaolin. Как заново изобрести кунг-фу в видеоигре
- [JavaScript, Node.JS, MongoDB, TypeScript] Практическое знакомство с Deno: разрабатываем REST API + MongoDB + Linux
- [Разработка под iOS, Разработка игр] HexThrees — моя первая законченная игра
- [Разработка игр, Unity, Дизайн игр] Дом в лесу. Работа с освещением в Unity 3D
- [Программирование, .NET, ASP, C#] Фильтры действий, или Как просто улучшить читаемость кода
- [Программирование, Java, Scala] Scala 3: избавление от implicit. Тайпклассы (перевод)
- [Программирование, Анализ и проектирование систем, Проектирование и рефакторинг, ООП] Symfony и Гексагональная архитектура (перевод)
- [Программирование, Отладка, Go, DevOps] Monitoring your application with distributed tracing so you actually know what it's doing
- [Программирование, Алгоритмы, Go] Algorithms in Go: Merge Intervals
Теги для поиска: #_programmirovanie (Программирование), #_c++, #_rabota_s_3dgrafikoj (Работа с 3D-графикой), #_razrabotka_igr (Разработка игр), #_cgi_(grafika) (CGI (графика)), #_tutorial, #_perevod (перевод), #_programmirovanie (
Программирование
), #_c++, #_rabota_s_3dgrafikoj (
Работа с 3D-графикой
), #_razrabotka_igr (
Разработка игр
), #_cgi_(grafika) (
CGI (графика)
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 17:45
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Я из IT-компании CGTribe и здесь я перевожу руководство к Vulkan API. Ссылка на оригинал — vulkan-tutorial.com. Моя следующая публикация посвящена переводу главы Window surface из раздела Drawing a triangle, подраздела Presentation. СодержаниеSPL1. Вступление
2. Краткий обзор 3. Настройка окружения 4. Рисуем треугольник
5. Буферы вершин
6. Uniform-буферы
7. Текстурирование
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++ =========== Источник: habr.com =========== =========== Автор оригинала: Alexander Overvoorde ===========Похожие новости:
Программирование ), #_c++, #_rabota_s_3dgrafikoj ( Работа с 3D-графикой ), #_razrabotka_igr ( Разработка игр ), #_cgi_(grafika) ( CGI (графика) ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 17:45
Часовой пояс: UTC + 5