GLFW и Vulkan

Последнее обновление: 15.04.2025

Vulkan не включает в себя инструменты для создания окна для отображения визуализированных результатов. Поэтому также может потребоваться технология для создания оконных приложений. Для этой цели можно использовать разные технологии. В данном случае мы будем использовать библиотеку GLFW. Она удобна тем, что абстрагируется от конкретной платформы и поддерживает Windows, Linux и MacOS. Тем не менее использовать GLFW необязательно, можно воспользоваться нативными возможностями каждой операционной системы, другими библиотеками. Но в какой-то степени эта библиотека специально заточена под разработку под OpenGL, OpenGL ES и Vulkan.

Установка GLFW

На Linux для установки GLFW применяются следующие команды

# Debian/Ubuntu
sudo apt install libglfw3-dev

# Fedora
sudo dnf install glfw-devel

Установка на Windows

Для установки на 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":

Заголововчные файлы для проекта C++ для GLFW в Visual Studio

Затем перейдем к полю Linker -> General и в поле Additional Library Directories укажем путь к библиотекам GLFW для текущей версии Visual Studio (для VS 2022 папка "glfw-3.4.bin.WIN64\lib-vc2022"):

Добавление библиотек GLFW для проекта C++ в Visual Studio

И дальше перейдем к вкладке Linker -> Input и в поле Additional Dependencies укажем путь к библиотеке glfw3.dll для текущей версии Visual Studio (для VS 2022 располагается по пути "glfw-3.4.bin.WIN64\lib-vc2022\glfw3.lib"):

glfw3.lib для проекта C++ в Visual Studio

И нажмем на кнопку "Применить" для применения настроек.

При необходимости нужные версии библиотек скопировать в файл проекта, например, если используется 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 отобразится пустое окно:

Первое приложение на GLFW на C++ в Windows

С Linux ситуация немного сложнее - вполне возможно на данном этапе отобразится окно, которое копирует содержимое верхнего левого угла, где появляется это окно по умолчанию:

Первое приложение на GLFW на C++ на Linux

Но это нормально, поскольку у нас пока нет никакой отрисовки.

Расширения GLFW для Vulkan

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():

  1. initWindow(): здесь создается окно GLFW и выполняются все аспекты, связанные с настройкой окна

  2. initVulkan(): здесь будет производиться инициализация всех аспектов, связанных с Vulkan. В примере выше эта функция пустая, но в дальнейшем сюда будет добавлено много функциональности, как получение устройств, создание графического конвейера, отрисовка и т.д.

  3. mainLoop(): здесь в цикле будем прослушивать пользовательские события окна

  4. 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. Такая структура упростить общее понимание приложения, и в дальнейшем мы будем придерживаться ее.

Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850