четверг, 25 февраля 2016 г.

Vulkan API, Physical Devices

Physical Devices


После того как Вулкан был проинициализирован, мы можем начать работу с основными основными объектами API - устройствами (devices) и очередями (queues). В этой статье представлена информация о физических устрйоствах и приведен небольшой пример инициализации Вулкана и перечисления некотрых свойств инстанса и устройства.



Вулкан разделяет концепцию физических и логических устройств. Физические устройства это обычно конкретное устройство в системе (возможно - составленное из нескольких отдельных устройств, но работающих вместе), их количество конечно. Логическое устройство это отображение этого устройства для приложения.


Чтоб получить список физических устройств в системе необходимо вызвать:


VkResult vkEnumeratePhysicalDevices(
VkInstance instance,
uint32_t * pPhysicalDeviceCount,
VkPhysicalDevice * pPhysicalDevices);


instance - хэндл инстанса Вулкана (VkInstance), созданного через vkCreateInstance.
pPhysicalDevices - если NULL, тогда количество доступных физических устройств возвращается через pPhysicalDeviceCount, иначе,
pPhysicalDeviceCount должен указывать на переменную, содержащую размер выделеннйо памяти под pPhysicalDevices, после выполнения там будет актуальное количество физических устройств, записанных в pPhysicalDevices.


Возвращаемые коды ошибок:


Success
• VK_SUCCESS
Failure
• VK_ERROR_OUT_OF_HOST_MEMORY
• VK_ERROR_OUT_OF_DEVICE_MEMORY
• VK_ERROR_INITIALIZATION_FAILED


После того как был получен список физических устройств можно получить свойства этого физического устройства вызвав:


void vkGetPhysicalDeviceProperties(
VkPhysicalDevice physicalDevice,
VkPhysicalDeviceProperties * pProperties);


physicalDevice хэндл (VkPhysicalDevice) физического устройства, чьи свойства мы хотим получить.
pProperties - указатель на структуру VkPhysicalDeviceProperties, в которую будут возвращены свойства устройства.


Структура VkPhysicalDeviceProperties определена как:


typedef struct VkPhysicalDeviceProperties {
uint32_t apiVersion;
uint32_t driverVersion;
uint32_t vendorID;
uint32_t deviceID;
VkPhysicalDeviceType deviceType;
char deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE];
uint8_t pipelineCacheUUID[VK_UUID_SIZE];
VkPhysicalDeviceLimits limits;
VkPhysicalDeviceSparseProperties sparseProperties;
} VkPhysicalDeviceProperties;


apiVersion - версия Вулкана, поддерживаемая устройством.
driverVersion - указанная вендором версия драйвера.
vendorID - уникальный идентификатор вендора физического устройства.
deviceID - уникальный идентификатор физического устройства (в рамках вендора).
Поля vendorID и deviceID позволяют приложению адаптироваться под характеристики устройства.
deviceType - VkPhysicalDeviceType - тип устройства.
deviceName - UTF-8 строка, содержащая имя устройства.
pipelineCacheUUID - массив размером VK_UUID_SIZE, содержащий 8-ми битное представление универсального уникального идентификатора устройства (UUID).
limits - структура VkPhysicalDeviceLimits, которая определяет специфические для устройства ограничения.
sparseProperties - структура VkPhysicalDeviceSparseProperties, которая определяет некоторые специфичные свойства физического устройства.


Тип физического устройства определяется как:
typedef enum VkPhysicalDeviceType {
VK_PHYSICAL_DEVICE_TYPE_OTHER = 0,
VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU = 1,
VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU = 2,
VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU = 3,
VK_PHYSICAL_DEVICE_TYPE_CPU = 4,
} VkPhysicalDeviceType;


• VK_PHYSICAL_DEVICE_TYPE_OTHER - устройство не совпадает ни с одним из доступных типов.
• VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU - устройство обычно встроенное или тесно связано с хостом.
• VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU - обычно это отдельный процессор подключенный к хосту.
• VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU - устройство обычно является виртуальной нодой в виртуальном окружении.
• VK_PHYSICAL_DEVICE_TYPE_CPU - устройство работает на том же процессоре что и хост.


Тип физического устройства имеет только информативное значение и не оказывает влияния на работу системы. Однако, тип устройства может коррелировать с другими предоставляемыми системой ресурсами, такими как количество доступной памяти.


====================================================================


Прежде чем перейти к рассмотрению/переводу остальной части спецификации, хотелось бы подвести черту под инициализацией, в виде небольшой демки с инициализацией Вулкана. Из предыдущей главы мы уже знаем что для этого необходимо создать инстанс через vkCreateInstance, заполнив соответствующие структуры. Чтоб проверить что Вулкан проинициализировался и что инстанс создался успешно попробуем получить кое-какую информацию о свойствах инстанса и имеющихся в системе устройствах.


Так как импортировать все функции вручную это долго, то воспользуюсь рекомендацией Хроноса и использую для этого их загрузчик:

Создание проекта и компиляция библиотеки в целом задача тривиальная, следуйте инструкциям. На выходе у вас будет библиотека “vulkan-1.lib”, которую нужно будет прилинковать к вашему проекту.


Для проекта вам так же потребуются заголовочные файлы Вулкана, их так же можно найти в проекте Vulkan-LoaderAndValidationLayers.


Включаем в программу заголовочные файлы:
#include <vulkan/vulkan.h>
#include <vulkan/vk_sdk_platform.h>


И переходим к инициализации Вулкана. Для того, чтоб создать инстанс через vkCreateInstance нам необходимо заполнить структуру VkInstanceCreateInfo. Поля этой структуры были детально описаны в предыдущей статье:
   VkInstanceCreateInfo inst_info = {
       .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
       .pNext = NULL,
       .pApplicationInfo = NULL,
       .enabledLayerCount = 0,
       .ppEnabledLayerNames = NULL,
       .enabledExtensionCount = 0,
       .ppEnabledExtensionNames = NULL,
   };

Как видите - все просто :) Главное правильно указать тип структуры.

При желании можно заполнить структуру VkApplicationInfo, в которой вы можете указать различную информацию о вашем приложении, но это действие не обязательно.


Отдельно стоило бы упомянуть о полях ppEnabledLayerNames и ppEnabledExtensionNames, однако в спецификации пока было лишь одно упоминание о них, молв если вы хотите использовать дополнительные лееры или расширения, то вы должны их активировать через соответствующие структуры. Мы пока понятия не имеем что это такое и нужно ли оно нам, и уж точно сейчас мы ничего из этого не будем использовать, поэтому смело указываем там NULL.


Все, этого достаточно чтоб проинициализировать вулкан и создать наш инстанс.
Останется только вызвать соответствующую команду и проверить что у нас нет ошибок:
VkInstance inst;
VkResult err = vkCreateInstance(&inst_info, NULL, &inst);


Если функция отработает успешно, то она вернет код ошибки VK_SUCCESS, в противном случае может быть одна из перечисленных ошибок:
• VK_ERROR_OUT_OF_HOST_MEMORY
• VK_ERROR_OUT_OF_DEVICE_MEMORY
• VK_ERROR_INITIALIZATION_FAILED
• VK_ERROR_LAYER_NOT_PRESENT
• VK_ERROR_EXTENSION_NOT_PRESENT
• VK_ERROR_INCOMPATIBLE_DRIVER


Так как работать с памятью мы еще не умеем, то в обоих случаях у нас в качестве аллокатора в функциях указан NULL, на этом этапе этого достаточно.


Первым желанием, после того как я создал инстанс, было узнать, какие устройства будут обнаружены в моей системе (AMD Kaveri + NVidia) и какие свойства у них оно мне покажет. Для этого воспользуемся описанной ранее функцией: vkEnumeratePhysicalDevices. Для начала нам нужно запроситьколичество физических устрйоств в системе:
err = vkEnumeratePhysicalDevices(inst, &dev_count, NULL);


Первым параметром мы передаем созданный нами инстанс, вторым параметром - переменную, куда мы будем сохранять количество устройств, в качестве третьего параметра указываем NULL, что означает что нас интересует только количество устройств.
Узнав количество устройств выделяем под их описание память, к примеру так:
VkPhysicalDevice *physical_devices = malloc(sizeof(VkPhysicalDevice) * dev_count);
И повторно вызываем эту же функцию, чтоб получить список этих устройств:
err = vkEnumeratePhysicalDevices(inst, &dev_count, physical_devices);


Полученные физические устройства пригодятся нам позже, а пока они не несут нам никакой информации (а мы любопытны). Чтоб получить информацию о типе устройства  необходимо вызвать описанную ранее функцию: vkGetPhysicalDeviceProperties.
VkPhysicalDeviceProperties dev_props;
vkGetPhysicalDeviceProperties(physical_devices[i], &dev_props);


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


Проверили? работает? Тогда этот инстанс можно удалить с чистой совестью:
vkDestroyInstance(inst, NULL);


Естественно на этом мы завершаем нашу работу с Вулканом.


Не знаю как вас, а меня смутило то, что при создании инстанса туда нужно передать список используемых лееров и расширений, мне сразу стало интересно - что это и нельзя ли получить список доступных нам лееров? Оказалось что можно, и для этого не нужно даже создавать инстанс Вулкана!


Для этих целей предназначена функция:
VkResult vkEnumerateInstanceLayerProperties(
uint32_t *pPropertyCount, VkLayerProperties* pProperties);


Предполагаю что эта функция будет описана позже, пока, забегая на перед, могу сказать что используется она так же как и описанные в этой главе vkEnumeratePhysicalDevices, где первый вызов функции с указателем на pPropertyCount и NULL вместо pProperties запрашивает количество доступных слоев, а второй вызов, с переданным pProperties нужного размера - возвращает массив строк. Если вам не жалко памяти, то сюда можно передать массив большего размера, актуальное количество элементов будет так же возвращено в pPropertyCount. Результат возвращается в виде структуры:


typedef struct VkLayerProperties {
   char        layerName[VK_MAX_EXTENSION_NAME_SIZE];
   uint32_t    specVersion;
   uint32_t    implementationVersion;
   char        description[VK_MAX_DESCRIPTION_SIZE];
} VkLayerProperties;


Аналогичным образом можно получить и список поддерживаемых расширений, для этого существует функция:
VkResult vkEnumerateInstanceExtensionProperties(
   const char *pLayerName, uint32_t *pPropertyCount, VkExtensionProperties *pProperties);


Отличие от предыдущей функции только в том, что первым параметром мы можем указать имя леера, для которого нам нужно получить список поддерживаемых расширений, если указать там NULL - получим полный(глобальный) список поддерживаемых расширений в виде VkExtensionProperties:


typedef struct VkExtensionProperties {
   char        extensionName[VK_MAX_EXTENSION_NAME_SIZE];
   uint32_t    specVersion;
} VkExtensionProperties;


Как видно из объявления структур, каждая из них имеет то самое имя слоя/расширения, которое нам нужно передать при создании инстанса + некоторую информацию о версии спецификации/имплементации. Коды ошибок в обоих случаях будут следующими:
Success
  • VK_SUCCESS
  • VK_INCOMPLETE
Failure
  • VK_ERROR_OUT_OF_HOST_MEMORY
  • VK_ERROR_OUT_OF_DEVICE_MEMORY


VulkanTest1.zip - здесь вы можете найти исходный код описанного примера и бинарник, он основан на примере cube.c из набора LoaderAndValidationLayers, предоставляемый Хроносом, который был разработан ими совместно с Valve Corporation и LunarG, Inc.
Как бы там ни было - демка подверглась некоторым переработкам :)

Комментариев нет:

Отправить комментарий