Статьи

Документация

Дополнительно

Графические окна

Для запуска графических окон необходим модуль xenolith_backend_vkgui. Для упрощения работы используется модуль xenolith::application.

Класс приложения (xenolith::Application) - основной для работы графического оконного приложения. Класс vk::GuiApplication контролирует создание графического API так, чтобы оно было пригодно для графических окон. Класс vk::BootstrapApplication реализует базовое работоспосбное приложение.

Для реализации собственной версии оконного приложения необходимо использовать функцию vk::platform::initInstance, инициализирующую графический API для использования с текущей оконной системой:

auto inst = vk::platform::createInstance([&] (vk::platform::VulkanInstanceData &data, const vk::platform::VulkanInstanceInfo &info) {
    data.applicationName = appInfo.applicationName;
    data.applicationVersion = appInfo.applicationVersion;
    data.checkPresentationSupport = vk::platform::checkPresentationSupport;
    return vk::platform::initInstance(data, info);
});

Для создания окна используется функция Application::addView:

addView(ViewInfo{
    .name = "Имя приложения",
    .bundleId = "name.bundle.org",
    .rect = URect(UVec2{0, 0}, _data.screenSize),
    .decoration = _data.viewDecoration,
    .density = _data.density,
    .selectConfig = [this] (xenolith::View &view, const core::SurfaceInfo &info) -> core::SwapchainConfig {
        // функция выбора конфигурации для окна
        return selectConfig(static_cast<View &>(view), info);
    },
    .onCreated = [this] (xenolith::View &view, const core::FrameContraints &constraints) {
        // создание сцены
        auto scene = createSceneForView(static_cast<View &>(view), constraints);
        view.getDirector()->runScene(move(scene));
    },
    .onClosed = [this] (xenolith::View &view) {
        // закрытие окна
        end(); // завершает работу приложения
    }
});

Основная проблема - ввыбор рабочей конфигурации для окна. Система предоставляет доступные для выбора параметры в виде SurfaceInfo, необходимо вернуть SwapchainConfig с выбранной конфигурацией.

Базовая реализация такова:

core::SwapchainConfig BootstrapApplication::selectConfig(vk::View &, const core::SurfaceInfo &info) {
    std::unique_lock<Mutex> lock(_configMutex);

    core::SwapchainConfig ret;
    // не изменяем предлагаемый размер
    ret.extent = info.currentExtent;
    
    // 2 для двойной буферизации, 3 для тройной
    ret.imageCount = std::max(uint32_t(3), info.minImageCount);

    // режимы презентации отсортированы по предпочтению движка, выбираем лучший из доступных
    ret.presentMode = info.presentModes.front();

    // быстрый режим презентации используется при активной трансформации окна для быстрой реакции на ввод. Если доступен режим Immediate - используется он
    // в противном случае, быстрый режим не используется
    if (std::find(info.presentModes.begin(), info.presentModes.end(), core::PresentMode::Immediate) != info.presentModes.end()) {
        ret.presentModeFast = core::PresentMode::Immediate;
    }

    // ищем базовый для платформы формат изображения в списке
    // пользователь может определить свой алгоритм поиска и выбирать из доступных форматов
    auto it = info.formats.begin();
    while (it != info.formats.end()) {
        if (it->first != xenolith::platform::getCommonFormat()) {
            ++ it;
        } else {
            break;
        }
    }

    // если не найдено - выбираем первый доступный
    if (it == info.formats.end()) {
        ret.imageFormat = info.formats.front().first;
        ret.colorSpace = info.formats.front().second;
    } else {
        // если найдено - используем найденный
        ret.imageFormat = it->first;
        ret.colorSpace = it->second;
    }

    // Проверяем, доступен ли режим непрозрачных окон, иначе - используем режим наследования родительского окна
    if ((info.supportedCompositeAlpha & core::CompositeAlphaFlags::Opaque) != core::CompositeAlphaFlags::None) {
        ret.alpha = core::CompositeAlphaFlags::Opaque;
    } else if ((info.supportedCompositeAlpha & core::CompositeAlphaFlags::Inherit) != core::CompositeAlphaFlags::None) {
        ret.alpha = core::CompositeAlphaFlags::Inherit;
    }

    // Проверяем, доступен ли трансфер в конечное изображение, вместо отрисовки.
    // Это определяет доступность рендеринга вне экрана для рисования в окно.
    ret.transfer = (info.supportedUsageFlags & core::ImageUsage::TransferDst) != core::ImageUsage::None;

    // Для mailbox принудительно включаем тройную буфферизацию, этот режим часто имеет проблемы с двойной буферизацией в драйвере
    if (ret.presentMode == core::PresentMode::Mailbox) {
        ret.imageCount = std::max(uint32_t(3), ret.imageCount);
    }

    // Не меняем трансформацию
    ret.transform = info.currentTransform;

    // Сохраняем результат в основном потоке
    performOnMainThread([this, info, ret] {
        _surfaceInfo = info;
        _swapchainConfig = ret;

        onSwapchainConfig(this);
    });

    return ret;
}