Vulkan API — урок 17. Выбор настроек swap chain

Если условия swapChainAdequate были выполнены, то поддерживаемого функционала безусловно достаточно, но все еще остается множество иных настроек на все случаи жизни. Теперь нужно написать несколько функций для того, что бы подобрать наиболее подходящие настройки для нашей swap chain. Имеется три основных типа настроек:

  • Формат поверхности (глубина цвета)
  • Формат представления (Presentation mode) (условия смены изображений на экране)
  • Размер свопа (разрешение изображения в swap chain)

Для каждой из этих настроек мы подберем наилучшее значение, если оно будет доступным, а если нет – подберем ближайшее к нему доступное.

Формат поверхности

Функция для этого параметра начинается так:

VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {

}

Позже мы передадим члены formats структуры SwapChainSupportDetails как аргументы.

Каждый VkSurfaceFormatKHR содержит format и colorSpace элементы.  format определяет цветовые каналы и типы. К примеру, VK_FORMAT_B8G8R8A8_UNORM означает, что мы используем B,G,R и алфа каналы в указанном порядке и 8-бит unsigned integer для каждого значения, сумарно 32 бита на пиксель. Элемент colorSpace показывает, что цветовое пространство SRGB поддерживается или не используется флаг VK_COLOR_SPACE_SRGB_NONLINEAR_KHR. Внимание, до мая 2016 этот флаг назывался VK_COLORSPACE_SRGB_NONLINEAR_KHR.

В качестве цветового пространства мы будем использовать SRGB, если доступно, т.к. оно приводит к более точным результатам. Работать непосредственно с цветами SRGB несколько сложно, так что мы будем использовать стандарт RGB для цветового формата, а именно VK_FORMAT_B8G8R8A8_UNORM, т.к. он очень широко распространен.

Наилучший вариантом окажется отсутствие предпочтительного формата у поверхности, о чем Vulkan нам скажет вернув лишь одну запись VkSurfaceFormatKHR, в которой формат установлен как VK_FORMAT_UNDEFINED.

if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) {
    return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
}

Если мы не свободны в выборе формата, тогда мы переберем список и увидим если предпочтительная комбинация доступна:

for (const auto& availableFormat : availableFormats) {
    if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
        return availableFormat;
    }
}

Ну а если же и такой вариант нам окажется недоступен, тогда следует начать перебирать доступные форматы по тому, насколько они «хороши» для конкретной задачи, но пока же мы выберем первый формат из списка.

VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
    if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) {
        return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
    }

    for (const auto& availableFormat : availableFormats) {
        if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
            return availableFormat;
        }
    }

    return availableFormats[0];
}

Формат представления

Это, вероятно, наиболее важный параметр для swap chain, потому что он представляет собой условия вывода изображения на экран. В Vulkan’е доступны четыре режима:

  • VK_PRESENT_MODE_IMMEDIATE_KHR: Изображения предоставляются вашим приложением и выводятся на экран сразу же., это может привести к тирингу (вывод на экран разных частей разных кадров в один момент времени).

nvidia-logo

  • VK_PRESENT_MODE_FIFO_KHR: Swap chain является очередью, где дисплей принимает изображение из начала очереди, а программа рендерит изображение и вставляет в конец очереди. Если очередь заполнена, то программа ожидает. Наиболее похоже на вертикальную синхронизацию в соверменных играх.
  • VK_PRESENT_MODE_FIFO_RELAXED_KHR: Этот вариант отличается от предыдущего тем, что когда изображение запаздывает и очередь остается пустой, то вновь поступившее изображение выводится на экран сразу как поступит, что опять же может привести к тирингу.
  • VK_PRESENT_MODE_MAILBOX_KHR: Данный вариант позволяет не блокировать приложение, когда очередь заполнена, а заменять изображения в очереди на новые. Этот режим может использоваться для реализации тройной буферизации, что позволяет избежать тиринга и значительно меньшие задержки, по сравнению  со стандартной вертикальной синхронизацией, которая использует двойную буферизацию.

Гарантированно будет доступен только лишь режим VK_PRESENT_MODE_FIFO_KHR, так что мы снова пишем функцию для поиска наилучшего доступного режима:

VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR> availablePresentModes) {
    return VK_PRESENT_MODE_FIFO_KHR;
}

Я солидарен с мнением, что тройная буферизация – наилучший вариант. Она позволяет избежать тиринга и сохранить низкие задержки. Теперь проверим, можем ли её использовать:

VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR> availablePresentModes) {
    for (const auto& availablePresentMode : availablePresentModes) {
        if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
            return availablePresentMode;
        }
    }

    return VK_PRESENT_MODE_FIFO_KHR;
}

Размер свопа (Swap extent)

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

VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {

}

Размер свопа – это разрешение изображения в цепи и почти всегда полностью совпадает с разрешением окна, в котором и будет происходить отрисовка. Диапазон возможных разрешений определяется в структуре VkSurfaceCapabilitiesKHR. Vulkan рекомендует нам соответствовать разрешению окна, установив ширину и высоту в элементе currentExtent. Однако, некоторые оконные менеджеры позволяют нам указывать отличающиеся данные, и это указывается настройками ширины и высоты в currentExtent, при помощи специального значения – максимальное значение uint32_t. В таком случае мы укажем разрешение наиболее соответствующее окну в приделах  minImageExtent и maxImageExtent. Немножко путанное описание, но по коду все сразу становится понятно:

VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
    if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
        return capabilities.currentExtent;
    } else {
        VkExtent2D actualExtent = {WIDTH, HEIGHT};

        actualExtent.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actualExtent.width));
        actualExtent.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actualExtent.height));
        
        return actualExtent;
    }
}

Функции max and min используются здесь для задания значений WIDTH and HEIGHT между доступными минимальным и максимальным поддерживаемыми значениями.

Main Admin

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

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