Queues. Queue Family Properties
Перевод этой части спецификации несколько затянулся, связано это с тем, что объем кода для примера стал слишком большим и появилось желание это все обернуть в соответствующие классы. На сколько удачным оказалось это решение покажет время, но объем кода примеров уже снизился существенно.
Ну что ж, приступим...
Ну что ж, приступим...
Как было рассказано в статье Vulkan API, Physical Device Queue Family, команда vkGetPhysicalDeviceQueueFamilyProperties изпользуется для получения детальной информации о семействах очередей и очередях, поддерживаемых устройством. Каждый индекс в массиве pQueueFamilyProperties, возвращаемый этой функцией, описывает уникальное семейство очередей на физическом устройстве. Эти индексы используются при создании очередей и непосредственно соответствуют queueFamilyIndex, переданному в команду vkCreateDevice через структуру VkDeviceQueueCreateInfo. Группировка семейств очередей на физическом устройстве зависит от конкретной реализации.
Заметка: Ожидается что физические устройства будут группировать все очереди с совпадающими свойствами в одно семейство, однако, это лишь рекомендация и возможно что физическое устройство может вернуть две отдельных очереди с одинаковыми свойствами.
Как только приложение идентифицировало физическое устройство с очередями, которые оно решило использовать, оно может создать эти очереди в сочетании с логическим устройством.
Queue Creation
Создание логического устройства так же создает и очереди, ассоциированные с этим устройством. Создаваемые очереди задаются через наборы структур VkDeviceQueueCreateInfo, которые передаются в vkCreateDevice через pQueueCreateInfos. Определение структуры VkDeviceQueueCreateInfo следующее:
typedef struct VkDeviceQueueCreateInfo {
VkStructureType sType;
const void * pNext;
VkDeviceQueueCreateFlags flags;
uint32_t queueFamilyIndex;
uint32_t queueCount;
const float * pQueuePriorities;
} VkDeviceQueueCreateInfo;
• sType - равен VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO.
• pNext - должен быть NULL.
• flags - зарезервирован, должен быть 0.
• queueFamilyIndex - индекс, задающий номер семейства очередей для создания на этом устройстве. Значение этого индекса соответствует индексу элемента в массиве pQueueFamilyProperties, возвращаемому vkGetPhysicalDeviceQueueFamilyProperties и должно быть меньше чем pQueueFamilyPropertyCount.
• queueCount - должно быть больше 0 и меньше или равно значению поля queueCount структуры VkQueueFamilyProperties[queueFamilyIndex]. Задает количество создаваемых очередей данного типа.
• pQueuePriorities - массив нормализованных флоатов (значения в интервале от 0.0 до 1.0 включительно) с размером queueCount элементов, определяющих приоритеты задач, отправляемых в каждую из создаваемых очередей.
Чтоб получить хэндл на объект VkQueue, нужно вызвать команду:
void vkGetDeviceQueue(
VkDevice device,
uint32_t queueFamilyIndex,
uint32_t queueIndex,
VkQueue * pQueue);
• device - хэндл логического устройства, владеющего очередью.
• queueFamilyIndex - индекс семейства очередей, к которому эта очередь принадлежит.
• queueIndex - индекс очереди, которую необходимо получить, внутри семейства очередей.
• pQueue - указатель на объект VkQueue, куда будет возращен хэндл запрашиваемой очереди.
Queue Family Index
Индекс семейства очередей (в дальнейшем просто индекс) используется в нескольких случаях:
- Когда мы получаем указатель на очередь через vkGetDeviceQueue, индекс используется чтоб выбрать какая из семейств очередей вернет хэндл хэндл VkQueue, описанным выше способом.
- Когда создается объект VkCommandPool, индекс указывается в структуре VkCommandPoolCreateInfo. Командные буферы из этого пула могут быть отправлены только в очереди соответствующего семейства.
- Когда создаются ресурсы VkImage и VkBuffer, набор семейства очередей включается в структуры VkImageCreateInfo и VkBufferCreateInfo, чтоб указать семейства очередей, имеющих доступ к этим ресурсам.
- Когда вставляется VkBufferMemoryBarrier или VkImageMemoryBarrier, индекс исходного и конечного семейства очередей указывает на допустимость владения буфером или изображением, передаваемыми из одного семейства в другое.
Queue Priority
За каждой очередью через структуру VkDeviceQueueCreateInfo, указываемую при создании устройства, закрепляется приоритет. Приоритет это нормированное значение (от 0 до 1), которое впоследствии транслируется в дискретное значение приоритета (зависит от реализации), при этом наинизший приоритет имеет значение 0.0, наивысший 1.0. Внутри одного логического устройства очередям с большим приоритетом выделяется больше процессорного времени чем очередям с низким приоритетом. Реализация не дает никаких гарантий в отношении порядка и расписания работы очередей с одинаковым приоритетом, кроме случаев когда явно определены примитивы планирования. Реализация не дает никаких гарантий относительно приоритетов очередей, принадлежащих разным устройствам. Реализация может позволить очереди с более высоким приоритетом истощать ресурсы очереди с более низким приоритетом на том же устройстве VkDevice, пока в очереди с более высоким приоритетом не закончатся команды. Приоритет очереди на одном устройстве никоим образом не влияет на потребление ресурсов очередей на другом устройстве. Нет никаких особых гарантий того. что очередь с более высоким приоритетом получит больше процессорного времени или лучшее качество обслуживания чем очереди с низким приоритетом.
Queue Synchronization
Чтоб дождаться завершения всех задач внутри одной очереди необходимо вызвать:
VkResult vkQueueWaitIdle(VkQueue queue);
• queue - очередь, чье завершение нужно дождаться.
vkQueueWaitIdle блокирует выполнение пока все буферы команд и операции “sparse binding” в указанной очереди не будут завершены.
Возможные коды ошибок:
Success
• VK_SUCCESS
Failure
• VK_ERROR_OUT_OF_HOST_MEMORY
• VK_ERROR_OUT_OF_DEVICE_MEMORY
• VK_ERROR_DEVICE_LOST
Синхронизация между очередями выполняется с использованием семафоров Вулкана.
Sparse Memory Binding
//Перевод отвратительный, так как пока плохо представляю контекст проблемы
Вулкан предоставляет возможность привязки распределенной (Sparse) памяти к буферам и изображениям, привязка такой памяти это операция очереди. Очередь, в которой установлен флаг VK_QUEUE_SPARSE_BINDING_BIT должна быть способна к поддержке маппинга виртуальных адресов в физическое адресное пространство устройства. Это вызывает обновление таблицы страниц отображения “page table mappings” на устройстве, которое должно быть синхронизировано в очереди для избежания повреждения таблицы страниц отображения пока выполняются графические команды. Привязывая распределенные ресурсы памяти к очереди, все команды, зависящие от обновления привязок, синхронизируются так, чтоб их выполнение было только после завершения обновления привязок.
Queue Destruction
Очереди создаются вместе с логическим устройством через vkCreateDevice. Все связанные с логическим устройством очереди удаляются вместе с устройством, когда для этого устройства вызывается vkDestroyDevice.
=====================================================================
В данной главе, а так же главе "Vulkan API. Devices" рассказывалось о создании логического устройства и очереди, связанной с этим устройством, одно без другого существовать не может.
Количество кода, необходимо для инициализации физического устройства уже слишком велико для примеров, поэтому, для сокращения примеров и избегания рутинных процедур, все рассмотренные ранее сущности были обернуты в простые классы, их сорцы прилагаются к статье.
И так, чтоб получить хэндл нашей очереди, нам необходимо вызвать функцию vkGetDeviceQueue, которой нужно скормить хэндл логического устройства. Но чтоб его создать командой vkCreateDevice необходимо заполнить две дополнительные структуры VkDeviceCreateInfo и VkDeviceQueueCreateInfo.
Структура VkDeviceCreateInfo частично нам уже знакома по созданию инстанса, мы должны обязательно указать тип данной структуры (sType), передать указатель на структуру VkDeviceQueueCreateInfo (рассмотрим чуть ниже) и указать создание как минимум одной очереди (queueCreateInfoCount = 1). Поля *LayerNames и *ExtensionNames мы пока так же оставим пустыми, пока не разберемся с этими расширениями. Отдельно стоит упомянуть свойство pEnabledFeatures. Это свойство задает какие из дополнительных возможностей будут доступны. Таких свойств очень много и рассматриваются они намного позже. Так как мы пока что ничего рисовать не будем, то это поле можно оставить пустым.
Таким образом, в простейшем случае, эта структура будет выглядеть так:
VkDeviceCreateInfo dev_info = {};
dev_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
dev_info.pNext = NULL;
dev_info.flags = 0;
dev_info.queueCreateInfoCount = 1;
dev_info.pQueueCreateInfos = &queue_info;
dev_info.enabledLayerCount = 0;
dev_info.ppEnabledLayerNames = NULL;
dev_info.enabledExtensionCount = 0;
dev_info.ppEnabledExtensionNames = NULL;
dev_info.pEnabledFeatures = NULL;
Теперь разберем вторую структуру, VkDeviceQueueCreateInfo, указатель на которую нам нужно передать в dev_info.pQueueCreateInfos. Так же как и в предыдущем случае, нам нужно первым делом указать тип этой структуры (sType), в обязательном порядке мы должны указать индекс семейства очередей (queueFamilyIndex), которую мы хотим использовать. В этой главе было подробно рассказано что это такое и где используется, о том как получить список доступных семейств и их свойств было рассказано в главе “Physical Device Queue Family”. Фактически выбор семейства сводится к перебору всех доступных и выборе того, который подходит для ваших задач - рисование, вычисления, загрузка данных или работа с разряженными ресурсами. В своем примере я эту функцию поместил в класс физического устройства, она мне вернула индекс в переменную qGCIndex (queue Graphics | Compute Index). Так же необходимо указать сколько очередей мы хотим получить, для примера нам хватит и одной, что мы и указываем в queueCount. Однако, даже если у нас одна очередь,то мы все равно вынуждены указать ее приоритет (pQueuePriorities), это указатель на массив флоатов, в котором количество элементов совпадает с количеством создаваемых очередей. Заполненная структура VkDeviceQueueCreateInfo выглядит так:
float queuePriorities[] = { 1.0f };
VkDeviceQueueCreateInfo queue_info = {};
queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_info.pNext = NULL;
queue_info.flags = 0;
queue_info.queueFamilyIndex = qGCIndex;
queue_info.queueCount = 1;
queue_info.pQueuePriorities = queuePriorities;
После того как мы заполнили эти структуры данными, мы можем попробовать создать логическое устройство:
VkDevice log_dev;
err = vkCreateDevice(*dev->getPhysicalDevice(), &dev_info, NULL, &log_dev);
Функция getPhysicalDevice() реализована в моем классе VK::PhysicalDevice, она просто возвращает хэндл физического устройства VkPhysicalDevice, созданного ранее.
Создав это логическое устройство мы можем запросить у него нашу очередь:
VkQueue GCqueue;
vkGetDeviceQueue(log_dev, qGCIndex, 0, &GCqueue);
Здесь так же все просто, передаем хэндл логического устройства, полученный выше, передаем индекс нужного нам семейства и номер созданной нами для устройства очереди (мы создавали только одну очередь, поэтому ее номер 0).
Ну вот собственно и все, теперь мы можем использовать свеже-созданную очередь для своих целей, к примеру - убедиться что очередь простаивает, прежде чем мы попытаемся ее удалить:
err = vkQueueWaitIdle(GCqueue);
Эта функция заблокирует поток до тех пор, пока все задачи, поставленные в очередь, не будут завершены. Формально, для данной задачи (удаления логического устройства), нам этого делать не нужно, так как функция vkDeviceWaitIdle делает то же самое, ожидая завершения всех очередей, связанных с устройством, но надо же было проверить что очередь создана успешно и работает :)
Теперь можно переходить к завершающей стадии, дождаться пока освободится устройство и удалить его:
err = vkDeviceWaitIdle(log_dev);
vkDestroyDevice(log_dev, NULL);
P.S. Так как для работы нам может потребоваться множество очередей и каждый раз выполнять всю эту процедуру очень громоздко, то я так же обернул это в соответствующие классы? таким образом, приведенный выше код теперь выглядит как:
float queuePriorities[] = { 1.0f };
VkDeviceQueueCreateInfo queue_info = {};
VK::FillDeviceQueueCreateInfo(queue_info, qGCIndex, 1, queuePriorities);
VkDeviceCreateInfo dev_info = {};
VK::FillDeviceCreateInfo(dev_info, 1, &queue_info);
VK::Device const *log_dev = dev->createDevice(&dev_info);
VkQueue const * GCqueue = log_dev->getDeviceQueue(qGCIndex, 0);
if (GCqueue) {
VkResult err = vkQueueWaitIdle(*GCqueue);
if (err != VK_SUCCESS) VK::ErrorCallback(err, AT, "Queue Idle failed");
else printf("\nQueue is Idle\n");
}
Комментариев нет:
Отправить комментарий