steps3D - Tutorials - Использование библиотеки Volk

Использование библиотеки Volk

Vulkan построен по тому же принципу, что и расширения OpenGL - все нужные нам функции (точнее указатели на них) нужно получать по имени из соответствующей разделяемой библиотеки (vulkan-1.dll для Windows или vulkan-libraries.so.1 для Linux). Обычно для получения базовых функций Vulkan (т.е. вошедших в Vulkan последней версии) используется специальная библиотека (vulkan-1.lib), которая сразу дает нам нужные указатели без необходимости их явного получения. Тем не менее ее нужно явно подключать. Кроме того, если мы хотим использовать расширения Vulkan (а мы, скорее всего, будем их использовать), то адреса функций, вводимых расширением, нужно получать явно по имени, как показано ниже.

VkDebugUtilsMessengerEXT    debugMessenger      = VK_NULL_HANDLE;
VkDebugReportCallbackEXT    msgCallback         = VK_NULL_HANDLE;

auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");

if ( func != nullptr )
    return func ( instance, pCreateInfo, pAllocator, pDebugMessenger );

auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");

if ( func != nullptr )
    func ( instance, debugMessenger, pAllocator );

Подобное получение адресов является довольно нудной работой, обычно приводящей к сплошному copy-paste. В OpenGL для подобной работы с расширениями давно уже используются специальные библиотеки, например GLEW. Важным плюсом подобных библиотек является то, что обычно они автоматически строятся при помощи специальных скриптов. Для Vulkan также есть простая и удобная библиотека Volk, которая сама получает все необходимые указатели. Более того, она получает указатели не только на вводимые расширениями функции, но и на функции самого Vulkan - тем самым пропадает необходимость подключения vulkan-1.lib - все подключение делается динамически. При этом сами исходные файлы (volk.h и volk.c) автоматически генерируются скриптом на python - он сам скачивает файлы с описанием расширений и по нему строит исходные файлы Volk.

Для работы с Vulkan и его расширениями вместо соответствующих библиотек Vulkan мы просто подключаем два файла из библиотеки Volk - volk.h и volk.c и все, мы получаем полный доступ к функциям как самого Vulkan, так и его расширений. После этого необходимо проинициализировать Volk - при этом будут загружена соответствующая динамическая библиотека и из нее будут получены указатели на функции. Обратите внимание, что это нужно сделать до начала инициализации самого Vulkan. Для инициализации Volk служит функция volkInitialize, при успехе она вернет значение VK_SUCCESS. Ряд функций Vulkan привязаны к конкретному экземпляру (instance) Vulkan, т.е. для получения указателей на функции нужно указать instance. Поэтому есть и вторая функция инициализации - volkLoadInstance, которую нужно вызвать когда будет создан экземпляр Vulkan.

VkResult volkInitialize   ();
void     volkLoadInstance ( VkInstance instance );

Vulkan поддерживает одновременную работу сразу с несколькими GPU - во время выполнения динамически будет определяться на какую функцию (обычно из драйвера) нужно передать управления. Это обеспечивает гибкость, но имеет и свою цену в виде быстродействия. Если вы работаете все время только с одним GPU (что как правило имеет место), то можно убрать эту цену, сразу загрузив указатели на функции для конкретного GPU. Для этого служит функция volkLoadDevice.

void volkLoadDevice(VkDevice device);

Уже давно библиотека Volk входит в состав VulkanSDK от LunarG, так что даже не надо отдельно скачивать.

Использование Volk совместно с VMA

Одной из популярных библиотек для Vulkan является VMA (Vulkan Memory Allocator), ее использование заметно упрощает выделение памяти GPU. Если мы хотим использовать сразу обе библиотеки - Volk и VMA, то при создании аллокатора VMA нам нужно через соответствующие поля структуры VmaAllocatorCreateInfo передать указатели на функции vkGetInstanceProcAddr и vkGetDeviceProcAddr, взятые из библиотеки Volk. Ниже приводится фрагмент кода, показывающий как это можно сделать.

VmaAllocator allocator;

VmaAllocatorCreateInfo  createInfo {
    .physicalDevice   = physicalDevice,
    .device           = device,
    .instance         = instance,
    .pVulkanFunctions = &(VmaVulkanFunctions)
        {
            .vkGetInstanceProcAddr = vkGetInstanceProcAddr,
            .vkGetDeviceProcAddr   = vkGetDeviceProcAddr
        }
};

vmaCreateAllocator ( &createInfo, &allocator );

Пример кода, иллюстрирующий использование библиотеки Volk можно скачать с моего репозитория github по следующему адресу github.com/steps3d/vulkan-with-classes.