Vulkan API — урок 12. Логическое устройство и очереди (+листинг)

Предисловие

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

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

Начнем с добавления нового члена класса, который будет хранить дескриптор логического устройства. Убедитесь, что вставляете его ниже VkInstance, это необходимо для очистки ранее instance. Ну и воспользуемся нашей любимой оберткой, используя в качестве функции очистки vkDestroyDevice:

VDeleter<VkDevice> device{vkDestroyDevice};

Далее добавим новую функцию createLogicalDevice, вызываемую из initVulkan:

void initVulkan() {
    createInstance();
    setupDebugCallback();
    pickPhysicalDevice();
    createLogicalDevice();
}

void createLogicalDevice() {

};

Настройка создания очередей

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

QueueFamilyIndices indices = findQueueFamilies(physicalDevice);

VkDeviceQueueCreateInfo queueCreateInfo = {};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = indices.graphicsFamily;
queueCreateInfo.queueCount = 1;

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

В Vulkan’e можно задавать приоритеты очередей, для этого имеется специальный параметр, принимающий значение типа float (от 0.0 до 1.0):

float queuePriority = 1.0f;
queueCreateInfo.pQueuePriorities = &queuePriority;

Настройка используемых функций устройства

Следующую информация используется для указания необходимого нам набора функций устройства. Здесь указываем функции устройства, что мы запросили в vkGetPhysicalDeviceFeatures в предыдущей главе. Сейчас нам не нужно ничего добавлять, так что просто просто оставим VK_FALSE. Ну и при необходимости еще вернемся.

VkPhysicalDeviceFeatures deviceFeatures = {};

Создание логического устройства

С предыдущими двумя структурами мы можем начать заполнять основную структуру VkDeviceCreationInfo:

VkDeviceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;

Для начала вставим указатель на queueCreateInfo и deviceFeatures:

createInfo.pQueueCreateInfos = &queueCreateInfo;
createInfo.queueCreateInfoCount = 1;

createInfo.pEnabledFeatures = &deviceFeatures;

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

Примером конкретного расширения устройства можно представить VK_KHR_swapchain, которое позволяет передавать отрендеренное изображение из устройства на окно. Возможно устройство Vulkan в системе не обладает такой возможностью, к примеру из-за того, что оно поддерживает только операции вычисления. Поговорим об этом подробнее в главе описывающей цепочки своппинга (swap chain).

Как упоминалось в главе про слои валидации, для устройств мы будем включать те же слои, что и для instance. Ни в каких дополнительных расширениях пока нет необходимости.

createInfo.enabledExtensionCount = 0;

if (enableValidationLayers) {
    createInfo.enabledLayerCount = validationLayers.size();
    createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
    createInfo.enabledLayerCount = 0;
}

Теперь мы готовы создать логическое устройство вызовом метода с соответствующим названием vkCreateDevice:

if (vkCreateDevice(physicalDevice, &createInfo, nullptr, device.replace()) != VK_SUCCESS) {
    throw std::runtime_error("failed to create logical device!");
}/code>

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

Получение дескриптора очереди

Очереди автоматически создают аналог логических устройств, но у нас нет их дескриптора для работы. Для начала добавим новый член класса для хранения дескриптора графической очереди:

VkQueue graphicsQueue;

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

Мы можем использовать функцию ю для получения дескриптораов очередей для каждого семейства. Параметры — логическое устройство, семейство, индекс очереди и указатель на переменную хранящую дескриптор. Т.к. мы создаем только лишь одно очередь из текущей семьи, то просто используем индекс «0».

vkGetDeviceQueue(device, indices.graphicsFamily, 0, &graphicsQueue);

С логическим устройством и дескриптором очереди мы сможем начать использовать видеокарту!

Код урока с учетом всех предыдущих.

Main Admin

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *