Очередь представления

Последнее обновление: 04.04.2025

(Практический пример по этой статье: Очередь представления в 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)

Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850