diff options
author | Zephyron <zephyron@citron-emu.org> | 2024-12-31 16:19:25 +1000 |
---|---|---|
committer | Zephyron <zephyron@citron-emu.org> | 2024-12-31 16:19:25 +1000 |
commit | 9427e27e24a7135880ee2881c3c44988e174b41a (patch) | |
tree | 83f0062a35be144f6b162eaa823c5b3c7620146e /src/citron/configuration/configure_graphics.cpp | |
parent | b35ae725d20960411e8588b11c12a2d55f86c9d0 (diff) |
chore: update project branding to citron
Diffstat (limited to 'src/citron/configuration/configure_graphics.cpp')
-rw-r--r-- | src/citron/configuration/configure_graphics.cpp | 552 |
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; +} |