(Практический пример по этой статье: Очередь представления в GLFW)
В прошлых темах было рассмотрено создание графической очереди, которая позволяет рисовать графику на устройстве. Но, если мы работаем с оконной системой и хотим вывести графику на экран в окно, то нам также потребуется очередь, которая поддерживает отображение изображений на созданной нами поверхности - так называемая "очередь представления" (presentation queue). Нередко графическая очередь и очередь представления совпадают. Но тем не менее нужно учитывать вероятность того, что семейство очередей может поддерживать команды рисования, но не поддерживает отображение изображений. Поэтому могут быть две отдельные очереди - одна для вывода графики (очередь представления), а другая - для рисования. Для проверки возможностей очередей можно использовать функцию vkGetPhysicalDeviceSurfaceSupportKHR:
VkResult vkGetPhysicalDeviceSurfaceSupportKHR(
VkPhysicalDevice physicalDevice,
uint32_t queueFamilyIndex,
VkSurfaceKHR surface,
VkBool32* pSupported);
В функцию передаются следующие данные:
physicalDevice: физическое устройство.
queueFamilyIndex: индекс семейство очередей
surface: поверхность рисования
pSupported: указатель на значение VkBool32. VK_TRUE указывает на поддержку, а VK_FALSE - на отсутствие поддержки.
Получение семейства очереди с поддержкой отображения выглядит примерно так:
VkBool32 presentSupport = false; // поддерживается ли отображение // device - физическое устройство // i - индекс семейства очередей // surface - поверхность рисования vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
Так как установка очереди представления вовлекает поверхность, которая является платформо-зависимым компонентом, соответственно полный код приложения будет отличаться для разных платфор. Поэтому приведу обобщенный пример получения индексов обоих очередей:
// структура для хранения индексов
typedef struct QueueFamilyIndices {
uint32_t graphicIndex; // индекс семейства очередей с поддержкой графики
uint32_t presentIndex; // индекс семейства очередей с поддержкой отображения
bool graphicSupported; // есть ли поддержка графики
bool presentSupported; // есть ли поддержка отображения
} QueueFamilyIndices;
// выбираем семейство очередей с графической очередью и поддержкой графики
QueueFamilyIndices selectQueueFamilyIndices(VkPhysicalDevice device, VkSurfaceKHR surface){
QueueFamilyIndices result{};
result.graphicSupported = false;
result.presentSupported = false;
uint32_t familyCount = 0;
// сначала получаем количество поддерживаемых семейств
vkGetPhysicalDeviceQueueFamilyProperties(device, &familyCount, nullptr);
if (!familyCount) return result;
// определяем вектор для хранения семейств
VkQueueFamilyProperties families[familyCount];
vkGetPhysicalDeviceQueueFamilyProperties(device, &familyCount, families);
// перебираем полученные семейства очередей
for (int32_t i{}; i < familyCount; ++i) {
// проверяем, поддерживает ли семейство очередей графику
if (families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
result.graphicSupported = true;
result.graphicIndex = i;
}
// проверяем, поддерживает ли семейство очередей отображение
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
if (presentSupport) {
result.presentSupported = true;
result.presentIndex = i;
}
// если оба индекса найдены, выходим из цикла
if(result.graphicSupported && result.presentSupported) break;
}
return result;
}
Здесь оба индекса определены в одну структуру - QueueFamilyIndices, которая собственно хранит индексы и также хранит булевые значения, указывающие, найдены ли соответствующие семейства очередей. В функции selectQueueFamilyIndices при переборе семейств очередей сначала проверяем, поддерживает ли текущее семейство графические возможности:
if (families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
result.graphicSupported = true;
result.graphicIndex = i;
}
И затем проверяем, поожерживает ли семейство очередей отображение:
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
if (presentSupport) {
result.presentSupported = true;
result.presentIndex = i;
}
В конце возвращаем индексы. Затем в программе, вызвав эту функцию и получив результат, мы можем понять, есть ли соответствующие семейства очередей, которые поддерживают возможности, и какие у них индексы. Весьма вероятно, что оба индекса будут представлять одно и то же семейство очередей, тем не менее это не надо воспринимать как некую данность. Но также можно добавить логику, чтобы явно предпочесть физическое устройство, которое поддерживает рисование и отображение в одной очереди, что улучшает производительность.
Если мы хотим использовать очередь представления, то нам нужно соответствующим образом создавать логическое устройство, чтобы потом получить очередь представления. В прошлых темах было рассмотрено создание логического устройство, где в функцию создания устройства передавался индекс графической очереди. Но теперь у нас два индекса, которые теоретически могут не совпадать (хотя, как правило, совпадают). И нам надо этот момент учитывать. Примерный код создания логического устройства в этом случае могу бы выглядеть следующим образом:
#include <set>
.....................................
// функция создания логического устройства
VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice, QueueFamilyIndices indices){
// если найдено семейство очередей с требуемой очередью, то создаем логическое устройство
if(!(indices.graphicSupported && indices.presentSupported)) return VK_NULL_HANDLE;
VkDevice device = VK_NULL_HANDLE;
// если оба индекса не совпадают, нам надо создать две структуры VkDeviceQueueCreateInfo
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
// используем множество, чтобы получить только уникальные индексы (если вдруг оба индекса пересекаются)
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicIndex, indices.presentIndex};
float queuePriority = 1.0f;
for (uint32_t queueFamily : uniqueQueueFamilies) {
VkDeviceQueueCreateInfo queueCreateInfo{};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamily;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back(queueCreateInfo);
}
VkDeviceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
// устанавливаем количество структур VkDeviceQueueCreateInfo
createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
// устанавливаем список структур VkDeviceQueueCreateInfo
createInfo.pQueueCreateInfos = queueCreateInfos.data();
// создаем устройство
vkCreateDevice(physicalDevice, &createInfo, nullptr, &device);
return device;
}
Если индексы семейств очередей не совпадают, то нам нужно создать две очереди и соответственно два объекта VkDeviceQueueCreateInfo. В данном случае мы могли бы проверить индексы. И если они не совпадают, создать две структуры VkDeviceQueueCreateInfo, если совпадают - создать одну. Но в данном случае для сокращения кода я использовал встроенный функционал множеств. Прежде всего создается вектор для VkDeviceQueueCreateInfo:
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
Затем создается множество из двух индексов:
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicIndex, indices.presentIndex};
Если индексы совпадают, то в итоге множество будет содержать только один индекс.
Далее проходим по всему этому множеству и для каждого его элемента создаем объект VkDeviceQueueCreateInfo, который добавляется в вектор:
for (uint32_t queueFamily : uniqueQueueFamilies) {
VkDeviceQueueCreateInfo queueCreateInfo{};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamily;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back(queueCreateInfo);
}
Затем передаем объекты из этого вектора для создания логического устройства:
VkDeviceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
// устанавливаем количество структур VkDeviceQueueCreateInfo
createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
// устанавливаем список структур VkDeviceQueueCreateInfo
createInfo.pQueueCreateInfos = queueCreateInfos.data();
(Практический пример по этой статье: Очередь представления в GLFW)