summaryrefslogtreecommitdiff
path: root/src/citron/configuration/configure_graphics.cpp
diff options
context:
space:
mode:
authorZephyron <zephyron@citron-emu.org>2024-12-31 16:19:25 +1000
committerZephyron <zephyron@citron-emu.org>2024-12-31 16:19:25 +1000
commit9427e27e24a7135880ee2881c3c44988e174b41a (patch)
tree83f0062a35be144f6b162eaa823c5b3c7620146e /src/citron/configuration/configure_graphics.cpp
parentb35ae725d20960411e8588b11c12a2d55f86c9d0 (diff)
chore: update project branding to citron
Diffstat (limited to 'src/citron/configuration/configure_graphics.cpp')
-rw-r--r--src/citron/configuration/configure_graphics.cpp552
1 files changed, 552 insertions, 0 deletions
diff --git a/src/citron/configuration/configure_graphics.cpp b/src/citron/configuration/configure_graphics.cpp
new file mode 100644
index 000000000..54c931e56
--- /dev/null
+++ b/src/citron/configuration/configure_graphics.cpp
@@ -0,0 +1,552 @@
+// SPDX-FileCopyrightText: 2016 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <functional>
+#include <iosfwd>
+#include <iterator>
+#include <string>
+#include <tuple>
+#include <typeinfo>
+#include <utility>
+#include <vector>
+#include <QBoxLayout>
+#include <QCheckBox>
+#include <QColorDialog>
+#include <QComboBox>
+#include <QIcon>
+#include <QLabel>
+#include <QLineEdit>
+#include <QPixmap>
+#include <QPushButton>
+#include <QSlider>
+#include <QStringLiteral>
+#include <QtCore/qobjectdefs.h>
+#include <qabstractbutton.h>
+#include <qboxlayout.h>
+#include <qcombobox.h>
+#include <qcoreevent.h>
+#include <qglobal.h>
+#include <qgridlayout.h>
+#include <vulkan/vulkan_core.h>
+
+#include "common/common_types.h"
+#include "common/dynamic_library.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "common/settings_enums.h"
+#include "core/core.h"
+#include "ui_configure_graphics.h"
+#include "yuzu/configuration/configuration_shared.h"
+#include "yuzu/configuration/configure_graphics.h"
+#include "yuzu/configuration/shared_widget.h"
+#include "yuzu/qt_common.h"
+#include "yuzu/uisettings.h"
+#include "yuzu/vk_device_info.h"
+
+static const std::vector<VkPresentModeKHR> default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR,
+ VK_PRESENT_MODE_FIFO_KHR};
+
+// Converts a setting to a present mode (or vice versa)
+static constexpr VkPresentModeKHR VSyncSettingToMode(Settings::VSyncMode mode) {
+ switch (mode) {
+ case Settings::VSyncMode::Immediate:
+ return VK_PRESENT_MODE_IMMEDIATE_KHR;
+ case Settings::VSyncMode::Mailbox:
+ return VK_PRESENT_MODE_MAILBOX_KHR;
+ case Settings::VSyncMode::Fifo:
+ return VK_PRESENT_MODE_FIFO_KHR;
+ case Settings::VSyncMode::FifoRelaxed:
+ return VK_PRESENT_MODE_FIFO_RELAXED_KHR;
+ default:
+ return VK_PRESENT_MODE_FIFO_KHR;
+ }
+}
+
+static constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode) {
+ switch (mode) {
+ case VK_PRESENT_MODE_IMMEDIATE_KHR:
+ return Settings::VSyncMode::Immediate;
+ case VK_PRESENT_MODE_MAILBOX_KHR:
+ return Settings::VSyncMode::Mailbox;
+ case VK_PRESENT_MODE_FIFO_KHR:
+ return Settings::VSyncMode::Fifo;
+ case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
+ return Settings::VSyncMode::FifoRelaxed;
+ default:
+ return Settings::VSyncMode::Fifo;
+ }
+}
+
+ConfigureGraphics::ConfigureGraphics(
+ const Core::System& system_, std::vector<VkDeviceInfo::Record>& records_,
+ const std::function<void()>& expose_compute_option_,
+ const std::function<void(Settings::AspectRatio, Settings::ResolutionSetup)>&
+ update_aspect_ratio_,
+ std::shared_ptr<std::vector<ConfigurationShared::Tab*>> group_,
+ const ConfigurationShared::Builder& builder, QWidget* parent)
+ : ConfigurationShared::Tab(group_, parent), ui{std::make_unique<Ui::ConfigureGraphics>()},
+ records{records_}, expose_compute_option{expose_compute_option_},
+ update_aspect_ratio{update_aspect_ratio_}, system{system_},
+ combobox_translations{builder.ComboboxTranslations()},
+ shader_mapping{
+ combobox_translations.at(Settings::EnumMetadata<Settings::ShaderBackend>::Index())} {
+ vulkan_device = Settings::values.vulkan_device.GetValue();
+ RetrieveVulkanDevices();
+
+ ui->setupUi(this);
+
+ Setup(builder);
+
+ for (const auto& device : vulkan_devices) {
+ vulkan_device_combobox->addItem(device);
+ }
+
+ UpdateBackgroundColorButton(QColor::fromRgb(Settings::values.bg_red.GetValue(),
+ Settings::values.bg_green.GetValue(),
+ Settings::values.bg_blue.GetValue()));
+ UpdateAPILayout();
+ PopulateVSyncModeSelection(false); //< must happen after UpdateAPILayout
+
+ // VSync setting needs to be determined after populating the VSync combobox
+ const auto vsync_mode_setting = Settings::values.vsync_mode.GetValue();
+ const auto vsync_mode = VSyncSettingToMode(vsync_mode_setting);
+ int index{};
+ for (const auto mode : vsync_mode_combobox_enum_map) {
+ if (mode == vsync_mode) {
+ break;
+ }
+ index++;
+ }
+ if (static_cast<unsigned long>(index) < vsync_mode_combobox_enum_map.size()) {
+ vsync_mode_combobox->setCurrentIndex(index);
+ }
+
+ connect(api_combobox, qOverload<int>(&QComboBox::activated), this, [this] {
+ UpdateAPILayout();
+ PopulateVSyncModeSelection(false);
+ });
+ connect(vulkan_device_combobox, qOverload<int>(&QComboBox::activated), this,
+ [this](int device) {
+ UpdateDeviceSelection(device);
+ PopulateVSyncModeSelection(false);
+ });
+ connect(shader_backend_combobox, qOverload<int>(&QComboBox::activated), this,
+ [this](int backend) { UpdateShaderBackendSelection(backend); });
+
+ connect(ui->bg_button, &QPushButton::clicked, this, [this] {
+ const QColor new_bg_color = QColorDialog::getColor(bg_color);
+ if (!new_bg_color.isValid()) {
+ return;
+ }
+ UpdateBackgroundColorButton(new_bg_color);
+ });
+
+ const auto& update_screenshot_info = [this, &builder]() {
+ const auto& combobox_enumerations = builder.ComboboxTranslations().at(
+ Settings::EnumMetadata<Settings::AspectRatio>::Index());
+ const auto ratio_index = aspect_ratio_combobox->currentIndex();
+ const auto ratio =
+ static_cast<Settings::AspectRatio>(combobox_enumerations[ratio_index].first);
+
+ const auto& combobox_enumerations_resolution = builder.ComboboxTranslations().at(
+ Settings::EnumMetadata<Settings::ResolutionSetup>::Index());
+ const auto res_index = resolution_combobox->currentIndex();
+ const auto setup = static_cast<Settings::ResolutionSetup>(
+ combobox_enumerations_resolution[res_index].first);
+
+ update_aspect_ratio(ratio, setup);
+ };
+
+ connect(aspect_ratio_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ update_screenshot_info);
+ connect(resolution_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
+ update_screenshot_info);
+
+ api_combobox->setEnabled(!UISettings::values.has_broken_vulkan && api_combobox->isEnabled());
+ ui->api_widget->setEnabled(
+ (!UISettings::values.has_broken_vulkan || Settings::IsConfiguringGlobal()) &&
+ ui->api_widget->isEnabled());
+
+ if (Settings::IsConfiguringGlobal()) {
+ ui->bg_widget->setEnabled(Settings::values.bg_red.UsingGlobal());
+ }
+}
+
+void ConfigureGraphics::PopulateVSyncModeSelection(bool use_setting) {
+ const Settings::RendererBackend backend{GetCurrentGraphicsBackend()};
+ if (backend == Settings::RendererBackend::Null) {
+ vsync_mode_combobox->setEnabled(false);
+ return;
+ }
+ vsync_mode_combobox->setEnabled(true);
+
+ const int current_index = //< current selected vsync mode from combobox
+ vsync_mode_combobox->currentIndex();
+ const auto current_mode = //< current selected vsync mode as a VkPresentModeKHR
+ current_index == -1 || use_setting
+ ? VSyncSettingToMode(Settings::values.vsync_mode.GetValue())
+ : vsync_mode_combobox_enum_map[current_index];
+ int index{};
+ const int device{vulkan_device_combobox->currentIndex()}; //< current selected Vulkan device
+
+ const auto& present_modes = //< relevant vector of present modes for the selected device or API
+ backend == Settings::RendererBackend::Vulkan && device > -1 ? device_present_modes[device]
+ : default_present_modes;
+
+ vsync_mode_combobox->clear();
+ vsync_mode_combobox_enum_map.clear();
+ vsync_mode_combobox_enum_map.reserve(present_modes.size());
+ for (const auto present_mode : present_modes) {
+ const auto mode_name = TranslateVSyncMode(present_mode, backend);
+ if (mode_name.isEmpty()) {
+ continue;
+ }
+
+ vsync_mode_combobox->insertItem(index, mode_name);
+ vsync_mode_combobox_enum_map.push_back(present_mode);
+ if (present_mode == current_mode) {
+ vsync_mode_combobox->setCurrentIndex(index);
+ }
+ index++;
+ }
+
+ if (!Settings::IsConfiguringGlobal()) {
+ vsync_restore_global_button->setVisible(!Settings::values.vsync_mode.UsingGlobal());
+
+ const Settings::VSyncMode global_vsync_mode = Settings::values.vsync_mode.GetValue(true);
+ vsync_restore_global_button->setEnabled(
+ (backend == Settings::RendererBackend::OpenGL &&
+ (global_vsync_mode == Settings::VSyncMode::Immediate ||
+ global_vsync_mode == Settings::VSyncMode::Fifo)) ||
+ backend == Settings::RendererBackend::Vulkan);
+ }
+}
+
+void ConfigureGraphics::UpdateVsyncSetting() const {
+ const Settings::RendererBackend backend{GetCurrentGraphicsBackend()};
+ if (backend == Settings::RendererBackend::Null) {
+ return;
+ }
+
+ const auto mode = vsync_mode_combobox_enum_map[vsync_mode_combobox->currentIndex()];
+ const auto vsync_mode = PresentModeToSetting(mode);
+ Settings::values.vsync_mode.SetValue(vsync_mode);
+}
+
+void ConfigureGraphics::UpdateDeviceSelection(int device) {
+ if (device == -1) {
+ return;
+ }
+ if (GetCurrentGraphicsBackend() == Settings::RendererBackend::Vulkan) {
+ vulkan_device = device;
+ }
+}
+
+void ConfigureGraphics::UpdateShaderBackendSelection(int backend) {
+ if (backend == -1) {
+ return;
+ }
+ if (GetCurrentGraphicsBackend() == Settings::RendererBackend::OpenGL) {
+ shader_backend = static_cast<Settings::ShaderBackend>(backend);
+ }
+}
+
+ConfigureGraphics::~ConfigureGraphics() = default;
+
+void ConfigureGraphics::SetConfiguration() {}
+
+void ConfigureGraphics::Setup(const ConfigurationShared::Builder& builder) {
+ QLayout* api_layout = ui->api_widget->layout();
+ QWidget* api_grid_widget = new QWidget(this);
+ QVBoxLayout* api_grid_layout = new QVBoxLayout(api_grid_widget);
+ api_grid_layout->setContentsMargins(0, 0, 0, 0);
+ api_layout->addWidget(api_grid_widget);
+
+ QLayout& graphics_layout = *ui->graphics_widget->layout();
+
+ std::map<u32, QWidget*> hold_graphics;
+ std::vector<QWidget*> hold_api;
+
+ for (const auto setting : Settings::values.linkage.by_category[Settings::Category::Renderer]) {
+ ConfigurationShared::Widget* widget = [&]() {
+ if (setting->Id() == Settings::values.fsr_sharpening_slider.Id()) {
+ // FSR needs a reversed slider and a 0.5 multiplier
+ return builder.BuildWidget(
+ setting, apply_funcs, ConfigurationShared::RequestType::ReverseSlider, true,
+ 0.5f, nullptr, tr("%", "FSR sharpening percentage (e.g. 50%)"));
+ } else {
+ return builder.BuildWidget(setting, apply_funcs);
+ }
+ }();
+
+ if (widget == nullptr) {
+ continue;
+ }
+ if (!widget->Valid()) {
+ widget->deleteLater();
+ continue;
+ }
+
+ if (setting->Id() == Settings::values.renderer_backend.Id()) {
+ // Add the renderer combobox now so it's at the top
+ api_grid_layout->addWidget(widget);
+ api_combobox = widget->combobox;
+ api_restore_global_button = widget->restore_button;
+
+ if (!Settings::IsConfiguringGlobal()) {
+ QObject::connect(api_restore_global_button, &QAbstractButton::clicked,
+ [this](bool) { UpdateAPILayout(); });
+
+ // Detach API's restore button and place it where we want
+ // Lets us put it on the side, and it will automatically scale if there's a
+ // second combobox (shader_backend, vulkan_device)
+ widget->layout()->removeWidget(api_restore_global_button);
+ api_layout->addWidget(api_restore_global_button);
+ }
+ } else if (setting->Id() == Settings::values.vulkan_device.Id()) {
+ // Keep track of vulkan_device's combobox so we can populate it
+ hold_api.push_back(widget);
+ vulkan_device_combobox = widget->combobox;
+ vulkan_device_widget = widget;
+ } else if (setting->Id() == Settings::values.shader_backend.Id()) {
+ // Keep track of shader_backend's combobox so we can populate it
+ hold_api.push_back(widget);
+ shader_backend_combobox = widget->combobox;
+ shader_backend_widget = widget;
+ } else if (setting->Id() == Settings::values.vsync_mode.Id()) {
+ // Keep track of vsync_mode's combobox so we can populate it
+ vsync_mode_combobox = widget->combobox;
+
+ // Since vsync is populated at runtime, we have to manually set up the button for
+ // restoring the global setting.
+ if (!Settings::IsConfiguringGlobal()) {
+ QPushButton* restore_button =
+ ConfigurationShared::Widget::CreateRestoreGlobalButton(
+ Settings::values.vsync_mode.UsingGlobal(), widget);
+ restore_button->setEnabled(true);
+ widget->layout()->addWidget(restore_button);
+
+ QObject::connect(restore_button, &QAbstractButton::clicked,
+ [restore_button, this](bool) {
+ Settings::values.vsync_mode.SetGlobal(true);
+ PopulateVSyncModeSelection(true);
+
+ restore_button->setVisible(false);
+ });
+
+ std::function<void()> set_non_global = [restore_button, this]() {
+ Settings::values.vsync_mode.SetGlobal(false);
+ UpdateVsyncSetting();
+ restore_button->setVisible(true);
+ };
+ QObject::connect(widget->combobox, QOverload<int>::of(&QComboBox::activated),
+ [set_non_global]() { set_non_global(); });
+ vsync_restore_global_button = restore_button;
+ }
+ hold_graphics.emplace(setting->Id(), widget);
+ } else if (setting->Id() == Settings::values.aspect_ratio.Id()) {
+ // Keep track of the aspect ratio combobox to update other UI tabs that need it
+ aspect_ratio_combobox = widget->combobox;
+ hold_graphics.emplace(setting->Id(), widget);
+ } else if (setting->Id() == Settings::values.resolution_setup.Id()) {
+ // Keep track of the resolution combobox to update other UI tabs that need it
+ resolution_combobox = widget->combobox;
+ hold_graphics.emplace(setting->Id(), widget);
+ } else {
+ hold_graphics.emplace(setting->Id(), widget);
+ }
+ }
+
+ for (const auto& [id, widget] : hold_graphics) {
+ graphics_layout.addWidget(widget);
+ }
+
+ for (auto widget : hold_api) {
+ api_grid_layout->addWidget(widget);
+ }
+
+ // Background color is too specific to build into the new system, so we manage it here
+ // (3 settings, all collected into a single widget with a QColor to manage on top)
+ if (Settings::IsConfiguringGlobal()) {
+ apply_funcs.push_back([this](bool powered_on) {
+ Settings::values.bg_red.SetValue(static_cast<u8>(bg_color.red()));
+ Settings::values.bg_green.SetValue(static_cast<u8>(bg_color.green()));
+ Settings::values.bg_blue.SetValue(static_cast<u8>(bg_color.blue()));
+ });
+ } else {
+ QPushButton* bg_restore_button = ConfigurationShared::Widget::CreateRestoreGlobalButton(
+ Settings::values.bg_red.UsingGlobal(), ui->bg_widget);
+ ui->bg_widget->layout()->addWidget(bg_restore_button);
+
+ QObject::connect(bg_restore_button, &QAbstractButton::clicked,
+ [bg_restore_button, this](bool) {
+ const int r = Settings::values.bg_red.GetValue(true);
+ const int g = Settings::values.bg_green.GetValue(true);
+ const int b = Settings::values.bg_blue.GetValue(true);
+ UpdateBackgroundColorButton(QColor::fromRgb(r, g, b));
+
+ bg_restore_button->setVisible(false);
+ bg_restore_button->setEnabled(false);
+ });
+
+ QObject::connect(ui->bg_button, &QAbstractButton::clicked, [bg_restore_button](bool) {
+ bg_restore_button->setVisible(true);
+ bg_restore_button->setEnabled(true);
+ });
+
+ apply_funcs.push_back([bg_restore_button, this](bool powered_on) {
+ const bool using_global = !bg_restore_button->isEnabled();
+ Settings::values.bg_red.SetGlobal(using_global);
+ Settings::values.bg_green.SetGlobal(using_global);
+ Settings::values.bg_blue.SetGlobal(using_global);
+ if (!using_global) {
+ Settings::values.bg_red.SetValue(static_cast<u8>(bg_color.red()));
+ Settings::values.bg_green.SetValue(static_cast<u8>(bg_color.green()));
+ Settings::values.bg_blue.SetValue(static_cast<u8>(bg_color.blue()));
+ }
+ });
+ }
+}
+
+const QString ConfigureGraphics::TranslateVSyncMode(VkPresentModeKHR mode,
+ Settings::RendererBackend backend) const {
+ switch (mode) {
+ case VK_PRESENT_MODE_IMMEDIATE_KHR:
+ return backend == Settings::RendererBackend::OpenGL
+ ? tr("Off")
+ : QStringLiteral("Immediate (%1)").arg(tr("VSync Off"));
+ case VK_PRESENT_MODE_MAILBOX_KHR:
+ return QStringLiteral("Mailbox (%1)").arg(tr("Recommended"));
+ case VK_PRESENT_MODE_FIFO_KHR:
+ return backend == Settings::RendererBackend::OpenGL
+ ? tr("On")
+ : QStringLiteral("FIFO (%1)").arg(tr("VSync On"));
+ case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
+ return QStringLiteral("FIFO Relaxed");
+ default:
+ return {};
+ break;
+ }
+}
+
+int ConfigureGraphics::FindIndex(u32 enumeration, int value) const {
+ for (u32 i = 0; i < combobox_translations.at(enumeration).size(); i++) {
+ if (combobox_translations.at(enumeration)[i].first == static_cast<u32>(value)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void ConfigureGraphics::ApplyConfiguration() {
+ const bool powered_on = system.IsPoweredOn();
+ for (const auto& func : apply_funcs) {
+ func(powered_on);
+ }
+
+ UpdateVsyncSetting();
+
+ Settings::values.vulkan_device.SetGlobal(true);
+ Settings::values.shader_backend.SetGlobal(true);
+ if (Settings::IsConfiguringGlobal() ||
+ (!Settings::IsConfiguringGlobal() && api_restore_global_button->isEnabled())) {
+ auto backend = static_cast<Settings::RendererBackend>(
+ combobox_translations
+ .at(Settings::EnumMetadata<
+ Settings::RendererBackend>::Index())[api_combobox->currentIndex()]
+ .first);
+ switch (backend) {
+ case Settings::RendererBackend::OpenGL:
+ Settings::values.shader_backend.SetGlobal(Settings::IsConfiguringGlobal());
+ Settings::values.shader_backend.SetValue(static_cast<Settings::ShaderBackend>(
+ shader_mapping[shader_backend_combobox->currentIndex()].first));
+ break;
+ case Settings::RendererBackend::Vulkan:
+ Settings::values.vulkan_device.SetGlobal(Settings::IsConfiguringGlobal());
+ Settings::values.vulkan_device.SetValue(vulkan_device_combobox->currentIndex());
+ break;
+ case Settings::RendererBackend::Null:
+ break;
+ }
+ }
+}
+
+void ConfigureGraphics::changeEvent(QEvent* event) {
+ if (event->type() == QEvent::LanguageChange) {
+ RetranslateUI();
+ }
+
+ QWidget::changeEvent(event);
+}
+
+void ConfigureGraphics::RetranslateUI() {
+ ui->retranslateUi(this);
+}
+
+void ConfigureGraphics::UpdateBackgroundColorButton(QColor color) {
+ bg_color = color;
+
+ QPixmap pixmap(ui->bg_button->size());
+ pixmap.fill(bg_color);
+
+ const QIcon color_icon(pixmap);
+ ui->bg_button->setIcon(color_icon);
+}
+
+void ConfigureGraphics::UpdateAPILayout() {
+ bool runtime_lock = !system.IsPoweredOn();
+ bool need_global = !(Settings::IsConfiguringGlobal() || api_restore_global_button->isEnabled());
+ vulkan_device = Settings::values.vulkan_device.GetValue(need_global);
+ shader_backend = Settings::values.shader_backend.GetValue(need_global);
+ vulkan_device_widget->setEnabled(!need_global && runtime_lock);
+ shader_backend_widget->setEnabled(!need_global && runtime_lock);
+
+ const auto current_backend = GetCurrentGraphicsBackend();
+ const bool is_opengl = current_backend == Settings::RendererBackend::OpenGL;
+ const bool is_vulkan = current_backend == Settings::RendererBackend::Vulkan;
+
+ vulkan_device_widget->setVisible(is_vulkan);
+ shader_backend_widget->setVisible(is_opengl);
+
+ if (is_opengl) {
+ shader_backend_combobox->setCurrentIndex(
+ FindIndex(Settings::EnumMetadata<Settings::ShaderBackend>::Index(),
+ static_cast<int>(shader_backend)));
+ } else if (is_vulkan && static_cast<int>(vulkan_device) < vulkan_device_combobox->count()) {
+ vulkan_device_combobox->setCurrentIndex(vulkan_device);
+ }
+}
+
+void ConfigureGraphics::RetrieveVulkanDevices() {
+ vulkan_devices.clear();
+ vulkan_devices.reserve(records.size());
+ device_present_modes.clear();
+ device_present_modes.reserve(records.size());
+ for (const auto& record : records) {
+ vulkan_devices.push_back(QString::fromStdString(record.name));
+ device_present_modes.push_back(record.vsync_support);
+
+ if (record.has_broken_compute) {
+ expose_compute_option();
+ }
+ }
+}
+
+Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
+ const auto selected_backend = [&]() {
+ if (!Settings::IsConfiguringGlobal() && !api_restore_global_button->isEnabled()) {
+ return Settings::values.renderer_backend.GetValue(true);
+ }
+ return static_cast<Settings::RendererBackend>(
+ combobox_translations.at(Settings::EnumMetadata<Settings::RendererBackend>::Index())
+ .at(api_combobox->currentIndex())
+ .first);
+ }();
+
+ if (selected_backend == Settings::RendererBackend::Vulkan &&
+ UISettings::values.has_broken_vulkan) {
+ return Settings::RendererBackend::OpenGL;
+ }
+ return selected_backend;
+}