Vulkan не включает в себя инструменты для создания окна для отображения визуализированных результатов. Поэтому также может потребоваться технология для создания оконных приложений. Для этой цели можно использовать разные технологии. В данном случае мы будем использовать библиотеку GLFW. Она удобна тем, что абстрагируется от конкретной платформы и поддерживает Windows, Linux и MacOS. Тем не менее использовать GLFW необязательно, можно воспользоваться нативными возможностями каждой операционной системы, другими библиотеками. Но в какой-то степени эта библиотека специально заточена под разработку под OpenGL, OpenGL ES и Vulkan.
На Linux для установки GLFW применяются следующие команды
# Debian/Ubuntu sudo apt install libglfw3-dev # Fedora sudo dnf install glfw-devel
Для установки на Windows можно загрузить последнюю версию GLFW с официального сайта со страницы https://www.glfw.org/download.html#windows-pre-compiled-binaries. После загрузки извлеките архив в удобное место. В извлеченном архиве можно найти предварительно скомпилированные файлы библиотеки под разные версии Visual Studio и для GCC под Windows (MinGW-W64). Например, распакуем загруженный архив в папку "C:\glfw-3.4.bin.WIN64", где у нас будет ряд каталогов:
include: папка с заголовочными файлами
lib-mingw-w64: библиотеки для Mingw-W64
lib-static-ucrt: библиотеки для GCC
lib-vc2013
lib-vc2015
lib-vc2017
lib-vc2019
lib-vc2022
Здесь нас будет интересовать прежде всего папка include, которая содержит заголовочные файлы, и папка lib-vc2022, которая содержит все необходимые файлы для
Visual Studio 2022. И теперь нам надо настроить проект, чтобы он подхватил все нужные файлы. Для этого перейдем в свойства проекта на вкладку
C++ -> General к полю Additional Include Directories и укажем в нем
путь к заголовочным файлам GLFW, которые располагаются в папке "include":
Затем перейдем к полю Linker -> General и в поле Additional Library Directories укажем путь к библиотекам GLFW для текущей версии Visual Studio (для VS 2022 папка "glfw-3.4.bin.WIN64\lib-vc2022"):
И дальше перейдем к вкладке Linker -> Input и в поле Additional Dependencies укажем путь к библиотеке glfw3.dll для текущей версии Visual Studio (для VS 2022 располагается по пути "glfw-3.4.bin.WIN64\lib-vc2022\glfw3.lib"):
И нажмем на кнопку "Применить" для применения настроек.
При необходимости нужные версии библиотек скопировать в файл проекта, например, если используется Visual Studio, то можно создать в проекте Visual Studio специальный каталог, например, каталог Libraries, и скопировать в него файлы библиотеки, и затем на эту папку настроить пути.
Определим простейшее приложение на GLFW:
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
int main() {
glfwInit(); // Инициализация GLFW
// Отключим создание контекста
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
// Создание окна
GLFWwindow* window = glfwCreateWindow(800, 600, "METANIT.COM", nullptr, nullptr);
// Жизненный цикл
while(!glfwWindowShouldClose(window)) {
// Обработка событий
glfwWaitEvents();
}
// Уничтожение окна
glfwDestroyWindow(window);
// Завершение работы с GLFW
glfwTerminate();
return 0;
}
Прежде всего, подключаем необходимые заголовки
#define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h>
Строка #define GLFW_INCLUDE_VULKAN позволяет добавить заголовки Vulkan вместо подключения стандартного заголовока Vulkan:
#include <vulkan/vulkan.h>
Хотя мы могли бы также подключить данный заголовок. Причем это делается перед подключением заголовка библиотеки "GLFW/glfw3.h"
В функции main, чтобы использовать функционал GLFW, сначала надо провести инициализацию:
glfwInit();
Далее отключаем создание контекста для OpenGL:
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
Vulkan не имеет контекста, а экземпляр Vulkan создается через сам API Vulkan. Поэтому при использовании в оконном приложении рендеринга Vulkan, нужно отключить создание контекста, установив для параметра GLFW_CLIENT_API значение GLFW_NO_API.
Далее собственно создается окно с помощью функции glfwCreateWindow():
GLFWwindow* window = glfwCreateWindow(800, 600, "METANIT.COM", nullptr, nullptr);
При создании окна в функцию glfwCreateWindow() передаются ширина, высота окна, текст заголовка. Последние два параметра -
монитор для использования в полноэкранном режиме и окно для буферов ресурсами в данном случае не нужны, поэтому им передается null.
Затем в цикле с помощью функции glfwWindowShouldClose() проверяем, пришел ли для окна сигнал закрытия от системы.
Внутри этого цикла обрабытавем события системы с помощью функции glfwWaitEvents.
while(!glfwWindowShouldClose(window)) {
glfwWaitEvents();
}
Выход из цикла означает, что получен сигнал на закрытие окна, и нам надо уничтожить окно. Для этого вызывается функция glfwDestroyWindow(). А для завершения работы с библиотекой GLFW
вызовается функция glfwTerminate()
glfwDestroyWindow(window); glfwTerminate();
При запуске проекта на Windows отобразится пустое окно:
С Linux ситуация немного сложнее - вполне возможно на данном этапе отобразится окно, которое копирует содержимое верхнего левого угла, где появляется это окно по умолчанию:
Но это нормально, поскольку у нас пока нет никакой отрисовки.
GLFW имеет удобную встроенную функцию glfwGetRequiredInstanceExtensions() которая возвращает необходимые расширения для Vulkan:
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <iostream>
int main() {
glfwInit(); // Инициализация GLFW
// Отключим создание контекста
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
// Создание окна
GLFWwindow* window = glfwCreateWindow(800, 600, "METANIT.COM", nullptr, nullptr);
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions;
VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
// получаем расширения для GLFW
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
// устанавливаем расширения для VkInstance
createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.ppEnabledExtensionNames = glfwExtensions;
std::cout << glfwExtensionCount << " расширений доступно\n";
// Жизненный цикл
while(!glfwWindowShouldClose(window)) {
// Обработка событий
glfwWaitEvents();
}
// Уничтожение окна
glfwDestroyWindow(window);
// Завершение работы с GLFW
glfwTerminate();
return 0;
}
В дальнейшем мы будем придерживаться следующей структуры:
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <cstdlib>
#include <stdexcept>
#include <iostream>
// размеры окна
const uint32_t WIDTH = 300;
const uint32_t HEIGHT = 250;
// структура приложения
class HelloApplication {
public:
void run() {
initWindow(); // инициализация окна GLFW
initVulkan(); // инициализация Vulkan
mainLoop(); // цикл приложения
cleanup(); // удаление используемых ресурсов
}
private:
GLFWwindow* window;
// создаем окно
void initWindow() {
glfwInit(); // инициализация GLFW
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
window = glfwCreateWindow(WIDTH, HEIGHT, "METANIT.COM", nullptr, nullptr);
}
// инициализация всех аспектов Vulkan
void initVulkan() {
}
// основной цикл программы, где прослушиваем пользовательские события
void mainLoop() {
while (!glfwWindowShouldClose(window)) {
glfwWaitEvents();
}
}
// удаление ресурсов
void cleanup() {
glfwDestroyWindow(window); // удаляем окно
glfwTerminate();
}
};
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, где различные этапы приложения последовательно запускаются в метое run():
initWindow(): здесь создается окно GLFW и выполняются все аспекты, связанные с настройкой окна
initVulkan(): здесь будет производиться инициализация всех аспектов, связанных с Vulkan. В примере выше эта функция пустая, но в дальнейшем сюда будет добавлено много функциональности, как получение устройств,
создание графического конвейера, отрисовка и т.д.
mainLoop(): здесь в цикле будем прослушивать пользовательские события окна
cleanup(): здесь будет выполняться очистка всех созданных ресурсов. Так, в примере выше в этой функции происходит удаление ранее созданного окна.
В функции main в конструкции try..catch запускаем метод run класса HelloApplication. Использование try..catch позволит упростить будущую обработку ошибок.
Чтобы было представление применения данной структуры, добавим в него создание объекта VkInstance:
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <cstdlib>
#include <stdexcept>
#include <iostream>
// размеры окна
const uint32_t WIDTH = 300;
const uint32_t HEIGHT = 250;
// структура приложения
class HelloApplication {
public:
void run() {
initWindow(); // инициализация окна GLFW
initVulkan(); // инициализация Vulkan
mainLoop(); // цикл приложения
cleanup(); // удаление используемых ресурсов
}
private:
GLFWwindow* window;
VkInstance instance;
// создаем окно
void initWindow() {
glfwInit(); // инициализация GLFW
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
}
void mainLoop() {
while (!glfwWindowShouldClose(window)) {
glfwWaitEvents();
}
}
// удаление ресурсов
void cleanup() {
vkDestroyInstance(instance, nullptr); // удаляем объект VkInstance
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!");
}
}
};
int main() {
HelloApplication app;
try {
app.run();
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Здесь на уровне класса определяется приватная переменная типа VkInstance для использования этого объекта в будущих операциях. Создание этого объекта вынесено в отдельную функцию - createInstance,
которая вызывается в функции initVulkan(), специально предназначеной для инициализации различных аспектов Vulkan. После завершения работы нам надо удалить VkInstance, и это делается в функции
cleanup. Такая структура упростить общее понимание приложения, и в дальнейшем мы будем придерживаться ее.