duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

postprocessingsettingswidget.cpp (17098B)


      1 // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "postprocessingsettingswidget.h"
      5 #include "qthost.h"
      6 #include "settingwidgetbinder.h"
      7 
      8 #include "util/postprocessing.h"
      9 
     10 #include "common/error.h"
     11 
     12 #include <QtWidgets/QCheckBox>
     13 #include <QtWidgets/QDialogButtonBox>
     14 #include <QtWidgets/QGridLayout>
     15 #include <QtWidgets/QLabel>
     16 #include <QtWidgets/QMessageBox>
     17 #include <QtWidgets/QSlider>
     18 
     19 PostProcessingSettingsWidget::PostProcessingSettingsWidget(SettingsWindow* dialog, QWidget* parent) : QTabWidget(parent)
     20 {
     21   addTab(new PostProcessingChainConfigWidget(dialog, this, PostProcessing::Config::DISPLAY_CHAIN_SECTION),
     22          tr("Display"));
     23   addTab(new PostProcessingChainConfigWidget(dialog, this, PostProcessing::Config::INTERNAL_CHAIN_SECTION),
     24          tr("Internal"));
     25   setDocumentMode(true);
     26 }
     27 
     28 PostProcessingSettingsWidget::~PostProcessingSettingsWidget() = default;
     29 
     30 PostProcessingChainConfigWidget::PostProcessingChainConfigWidget(SettingsWindow* dialog, QWidget* parent,
     31                                                                  const char* section)
     32   : QWidget(parent), m_dialog(dialog), m_section(section)
     33 {
     34   SettingsInterface* sif = dialog->getSettingsInterface();
     35 
     36   m_ui.setupUi(this);
     37 
     38   SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enablePostProcessing, section, "Enabled", false);
     39 
     40   updateList();
     41   updateButtonsAndConfigPane(std::nullopt);
     42   connectUi();
     43 }
     44 
     45 PostProcessingChainConfigWidget::~PostProcessingChainConfigWidget() = default;
     46 
     47 SettingsInterface& PostProcessingChainConfigWidget::getSettingsInterfaceToUpdate()
     48 {
     49   return m_dialog->isPerGameSettings() ? *m_dialog->getSettingsInterface() : *Host::Internal::GetBaseSettingsLayer();
     50 }
     51 
     52 void PostProcessingChainConfigWidget::commitSettingsUpdate()
     53 {
     54   if (m_dialog->isPerGameSettings())
     55   {
     56     m_dialog->saveAndReloadGameSettings();
     57   }
     58   else
     59   {
     60     Host::CommitBaseSettingChanges();
     61     g_emu_thread->updatePostProcessingSettings();
     62   }
     63 }
     64 
     65 void PostProcessingChainConfigWidget::connectUi()
     66 {
     67   connect(m_ui.reload, &QPushButton::clicked, this, &PostProcessingChainConfigWidget::onReloadButtonClicked);
     68   connect(m_ui.add, &QToolButton::clicked, this, &PostProcessingChainConfigWidget::onAddButtonClicked);
     69   connect(m_ui.remove, &QToolButton::clicked, this, &PostProcessingChainConfigWidget::onRemoveButtonClicked);
     70   connect(m_ui.clear, &QToolButton::clicked, this, &PostProcessingChainConfigWidget::onClearButtonClicked);
     71   connect(m_ui.moveUp, &QToolButton::clicked, this, &PostProcessingChainConfigWidget::onMoveUpButtonClicked);
     72   connect(m_ui.moveDown, &QToolButton::clicked, this, &PostProcessingChainConfigWidget::onMoveDownButtonClicked);
     73   connect(m_ui.stages, &QListWidget::itemSelectionChanged, this,
     74           &PostProcessingChainConfigWidget::onSelectedShaderChanged);
     75 }
     76 
     77 std::optional<u32> PostProcessingChainConfigWidget::getSelectedIndex() const
     78 {
     79   QList<QListWidgetItem*> selected_items = m_ui.stages->selectedItems();
     80   return selected_items.empty() ? std::nullopt :
     81                                   std::optional<u32>(selected_items.first()->data(Qt::UserRole).toUInt());
     82 }
     83 
     84 void PostProcessingChainConfigWidget::selectIndex(s32 index)
     85 {
     86   if (index < 0 || index >= m_ui.stages->count())
     87     return;
     88 
     89   QSignalBlocker sb(m_ui.stages);
     90   m_ui.stages->setCurrentItem(m_ui.stages->item(index));
     91   updateButtonsAndConfigPane(index);
     92 }
     93 
     94 void PostProcessingChainConfigWidget::updateList()
     95 {
     96   const auto lock = Host::GetSettingsLock();
     97   const SettingsInterface& si = getSettingsInterfaceToUpdate();
     98 
     99   updateList(si);
    100 }
    101 
    102 void PostProcessingChainConfigWidget::updateList(const SettingsInterface& si)
    103 {
    104   m_ui.stages->clear();
    105 
    106   const u32 stage_count = PostProcessing::Config::GetStageCount(si, m_section);
    107 
    108   for (u32 i = 0; i < stage_count; i++)
    109   {
    110     const std::string stage_name = PostProcessing::Config::GetStageShaderName(si, m_section, i);
    111     QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(stage_name), m_ui.stages);
    112     item->setData(Qt::UserRole, QVariant(i));
    113   }
    114 
    115   m_ui.clear->setEnabled(stage_count > 0);
    116   m_ui.reload->setEnabled(stage_count > 0);
    117   updateButtonsAndConfigPane(std::nullopt);
    118 }
    119 
    120 void PostProcessingChainConfigWidget::updateButtonsAndConfigPane(std::optional<u32> index)
    121 {
    122   m_ui.remove->setEnabled(index.has_value());
    123 
    124   if (index.has_value())
    125   {
    126     m_ui.moveUp->setEnabled(index.value() > 0);
    127     m_ui.moveDown->setEnabled(index.value() < static_cast<u32>(m_ui.stages->count() - 1));
    128   }
    129   else
    130   {
    131     m_ui.moveUp->setEnabled(false);
    132     m_ui.moveDown->setEnabled(false);
    133   }
    134 
    135   m_ui.scrollArea->setWidget(nullptr);
    136   m_ui.scrollArea->setVisible(false);
    137 
    138   if (m_shader_config)
    139   {
    140     delete m_shader_config;
    141     m_shader_config = nullptr;
    142   }
    143 
    144   if (!index.has_value())
    145     return;
    146 
    147   const auto lock = Host::GetSettingsLock();
    148   const SettingsInterface& si = getSettingsInterfaceToUpdate();
    149   std::vector<PostProcessing::ShaderOption> options =
    150     PostProcessing::Config::GetStageOptions(si, m_section, index.value());
    151   if (options.empty())
    152     return;
    153 
    154   m_shader_config =
    155     new PostProcessingShaderConfigWidget(m_ui.scrollArea, this, m_section, index.value(), std::move(options));
    156   m_ui.scrollArea->setWidget(m_shader_config);
    157   m_ui.scrollArea->setVisible(true);
    158 }
    159 
    160 void PostProcessingChainConfigWidget::onAddButtonClicked()
    161 {
    162   QMenu menu;
    163 
    164   const std::vector<std::pair<std::string, std::string>> shaders = PostProcessing::GetAvailableShaderNames();
    165   if (shaders.empty())
    166   {
    167     menu.addAction(tr("No Shaders Available"))->setEnabled(false);
    168   }
    169   else
    170   {
    171     for (auto& [display_name, name] : shaders)
    172     {
    173       QAction* action = menu.addAction(QString::fromStdString(display_name));
    174       connect(action, &QAction::triggered, [this, shader = std::move(name)]() {
    175         auto lock = Host::GetSettingsLock();
    176         SettingsInterface& si = getSettingsInterfaceToUpdate();
    177 
    178         Error error;
    179         if (!PostProcessing::Config::AddStage(si, m_section, shader, &error))
    180         {
    181           lock.unlock();
    182           QMessageBox::critical(this, tr("Error"),
    183                                 tr("Failed to add shader: %1").arg(QString::fromStdString(error.GetDescription())));
    184           return;
    185         }
    186 
    187         updateList(si);
    188         lock.unlock();
    189         commitSettingsUpdate();
    190       });
    191     }
    192   }
    193 
    194   menu.exec(QCursor::pos());
    195 }
    196 
    197 void PostProcessingChainConfigWidget::onRemoveButtonClicked()
    198 {
    199   QList<QListWidgetItem*> selected_items = m_ui.stages->selectedItems();
    200   if (selected_items.empty())
    201     return;
    202 
    203   auto lock = Host::GetSettingsLock();
    204   SettingsInterface& si = getSettingsInterfaceToUpdate();
    205 
    206   QListWidgetItem* item = selected_items.first();
    207   u32 index = item->data(Qt::UserRole).toUInt();
    208   if (index < PostProcessing::Config::GetStageCount(si, m_section))
    209   {
    210     PostProcessing::Config::RemoveStage(si, m_section, index);
    211     updateList(si);
    212     lock.unlock();
    213     commitSettingsUpdate();
    214   }
    215 }
    216 
    217 void PostProcessingChainConfigWidget::onClearButtonClicked()
    218 {
    219   if (QMessageBox::question(this, tr("Question"), tr("Are you sure you want to clear all shader stages?"),
    220                             QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
    221   {
    222     auto lock = Host::GetSettingsLock();
    223     SettingsInterface& si = getSettingsInterfaceToUpdate();
    224     PostProcessing::Config::ClearStages(si, m_section);
    225     updateList(si);
    226     lock.unlock();
    227     commitSettingsUpdate();
    228   }
    229 }
    230 
    231 void PostProcessingChainConfigWidget::onMoveUpButtonClicked()
    232 {
    233   std::optional<u32> index = getSelectedIndex();
    234   if (index.has_value() && index.value() > 0)
    235   {
    236     auto lock = Host::GetSettingsLock();
    237     SettingsInterface& si = getSettingsInterfaceToUpdate();
    238     PostProcessing::Config::MoveStageUp(si, m_section, index.value());
    239     updateList(si);
    240     lock.unlock();
    241     selectIndex(index.value() - 1);
    242     commitSettingsUpdate();
    243   }
    244 }
    245 
    246 void PostProcessingChainConfigWidget::onMoveDownButtonClicked()
    247 {
    248   std::optional<u32> index = getSelectedIndex();
    249   if (index.has_value() || index.value() < (static_cast<u32>(m_ui.stages->count() - 1)))
    250   {
    251     auto lock = Host::GetSettingsLock();
    252     SettingsInterface& si = getSettingsInterfaceToUpdate();
    253     PostProcessing::Config::MoveStageDown(si, m_section, index.value());
    254     updateList(si);
    255     lock.unlock();
    256     selectIndex(index.value() + 1);
    257     commitSettingsUpdate();
    258   }
    259 }
    260 
    261 void PostProcessingChainConfigWidget::onReloadButtonClicked()
    262 {
    263   g_emu_thread->reloadPostProcessingShaders();
    264 }
    265 
    266 void PostProcessingChainConfigWidget::onSelectedShaderChanged()
    267 {
    268   std::optional<u32> index = getSelectedIndex();
    269   updateButtonsAndConfigPane(index);
    270 }
    271 
    272 PostProcessingShaderConfigWidget::PostProcessingShaderConfigWidget(QWidget* parent,
    273                                                                    PostProcessingChainConfigWidget* widget,
    274                                                                    const char* section, u32 stage_index,
    275                                                                    std::vector<PostProcessing::ShaderOption> options)
    276   : QWidget(parent), m_widget(widget), m_section(section), m_stage_index(stage_index), m_options(std::move(options))
    277 {
    278   m_layout = new QGridLayout(this);
    279 
    280   createUi();
    281 }
    282 
    283 PostProcessingShaderConfigWidget::~PostProcessingShaderConfigWidget() = default;
    284 
    285 void PostProcessingShaderConfigWidget::updateConfigForOption(const PostProcessing::ShaderOption& option)
    286 {
    287   const auto lock = Host::GetSettingsLock();
    288   SettingsInterface& si = m_widget->getSettingsInterfaceToUpdate();
    289   PostProcessing::Config::SetStageOption(si, m_section, m_stage_index, option);
    290   m_widget->commitSettingsUpdate();
    291 }
    292 
    293 void PostProcessingShaderConfigWidget::onResetDefaultsClicked()
    294 {
    295   {
    296     const auto lock = Host::GetSettingsLock();
    297     SettingsInterface& si = m_widget->getSettingsInterfaceToUpdate();
    298     for (PostProcessing::ShaderOption& option : m_options)
    299     {
    300       if (std::memcmp(option.value.data(), option.default_value.data(), sizeof(option.value)) == 0)
    301         continue;
    302 
    303       option.value = option.default_value;
    304       PostProcessing::Config::UnsetStageOption(si, m_section, m_stage_index, option);
    305     }
    306     m_widget->commitSettingsUpdate();
    307   }
    308 
    309   // Toss and recreate UI.
    310   for (auto it = m_widgets.rbegin(); it != m_widgets.rend(); ++it)
    311   {
    312     m_layout->removeWidget(*it);
    313     delete *it;
    314   }
    315   m_widgets.clear();
    316   createUi();
    317 }
    318 
    319 void PostProcessingShaderConfigWidget::createUi()
    320 {
    321   u32 row = 0;
    322 
    323   const std::string* last_category = nullptr;
    324 
    325   for (PostProcessing::ShaderOption& option : m_options)
    326   {
    327     if (option.ui_name.empty())
    328       continue;
    329 
    330     if (!last_category || option.category != *last_category)
    331     {
    332       if (last_category)
    333         m_layout->addItem(new QSpacerItem(1, 4), row++, 0);
    334 
    335       if (!option.category.empty())
    336       {
    337         QLabel* label = new QLabel(QString::fromStdString(option.category), this);
    338         QFont label_font(label->font());
    339         label_font.setPointSizeF(12.0f);
    340         label->setFont(label_font);
    341         m_layout->addWidget(label, row++, 0, 1, 3, Qt::AlignLeft);
    342       }
    343 
    344       if (last_category || !option.category.empty())
    345       {
    346         QLabel* line = new QLabel(this);
    347         line->setFrameShape(QFrame::HLine);
    348         line->setFixedHeight(4);
    349         line->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    350         m_layout->addWidget(line, row++, 0, 1, 3);
    351       }
    352 
    353       last_category = &option.category;
    354     }
    355 
    356     const QString tooltip = QString::fromStdString(option.tooltip);
    357 
    358     if (option.type == PostProcessing::ShaderOption::Type::Bool)
    359     {
    360       QCheckBox* checkbox = new QCheckBox(QString::fromStdString(option.ui_name), this);
    361       checkbox->setToolTip(tooltip);
    362       checkbox->setChecked(option.value[0].int_value != 0);
    363       connect(checkbox, &QCheckBox::checkStateChanged, [this, &option](Qt::CheckState state) {
    364         option.value[0].int_value = (state == Qt::Checked) ? 1 : 0;
    365         updateConfigForOption(option);
    366       });
    367       m_layout->addWidget(checkbox, row, 0, 1, 3, Qt::AlignLeft);
    368       m_widgets.push_back(checkbox);
    369       row++;
    370     }
    371     else if (option.type == PostProcessing::ShaderOption::Type::Int && !option.choice_options.empty())
    372     {
    373       QLabel* label = new QLabel(QString::fromStdString(option.ui_name), this);
    374       label->setToolTip(tooltip);
    375       m_layout->addWidget(label, row, 0, 1, 1, Qt::AlignLeft);
    376 
    377       QComboBox* combo = new QComboBox(this);
    378       combo->setToolTip(tooltip);
    379       for (const std::string& combo_option : option.choice_options)
    380         combo->addItem(QString::fromStdString(combo_option));
    381       combo->setCurrentIndex(option.value[0].int_value);
    382       connect(combo, &QComboBox::currentIndexChanged, [this, &option](int index) {
    383         option.value[0].int_value = index;
    384         updateConfigForOption(option);
    385       });
    386       m_layout->addWidget(combo, row, 1, 1, 2, Qt::AlignLeft);
    387       m_widgets.push_back(combo);
    388       row++;
    389     }
    390     else
    391     {
    392       for (u32 i = 0; i < option.vector_size; i++)
    393       {
    394         QString label;
    395         if (option.vector_size <= 1)
    396         {
    397           label = QString::fromStdString(option.ui_name);
    398         }
    399         else
    400         {
    401           static constexpr std::array<const char*, PostProcessing::ShaderOption::MAX_VECTOR_COMPONENTS + 1> suffixes = {
    402             {QT_TR_NOOP("Red"), QT_TR_NOOP("Green"), QT_TR_NOOP("Blue"), QT_TR_NOOP("Alpha")}};
    403           label = tr("%1 (%2)").arg(QString::fromStdString(option.ui_name)).arg(tr(suffixes[i]));
    404         }
    405 
    406         QWidget* label_w = new QLabel(label, this);
    407         label_w->setToolTip(tooltip);
    408         m_layout->addWidget(label_w, row, 0, 1, 1, Qt::AlignLeft);
    409         m_widgets.push_back(label_w);
    410 
    411         QSlider* slider = new QSlider(Qt::Horizontal, this);
    412         slider->setToolTip(tooltip);
    413         m_layout->addWidget(slider, row, 1, 1, 1, Qt::AlignLeft);
    414         m_widgets.push_back(slider);
    415 
    416         QLabel* slider_label = new QLabel(this);
    417         slider_label->setToolTip(tooltip);
    418         m_layout->addWidget(slider_label, row, 2, 1, 1, Qt::AlignLeft);
    419         m_widgets.push_back(slider_label);
    420 
    421         if (option.type == PostProcessing::ShaderOption::Type::Int)
    422         {
    423           slider_label->setText(QString::number(option.value[i].int_value));
    424 
    425           const int range = option.max_value[i].int_value - option.min_value[i].int_value;
    426           const int step_value =
    427             (option.step_value[i].int_value != 0) ? option.step_value[i].int_value : ((range + 99) / 100);
    428           const int num_steps = range / step_value;
    429           slider->setMinimum(0);
    430           slider->setMaximum(num_steps);
    431           slider->setSingleStep(1);
    432           slider->setTickInterval(step_value);
    433           slider->setValue((option.value[i].int_value - option.min_value[i].int_value) / step_value);
    434           connect(slider, &QSlider::valueChanged, [this, &option, i, slider_label](int value) {
    435             const int new_value = std::clamp(option.min_value[i].int_value + (value * option.step_value[i].int_value),
    436                                              option.min_value[i].int_value, option.max_value[i].int_value);
    437             option.value[i].int_value = new_value;
    438             slider_label->setText(QString::number(new_value));
    439             updateConfigForOption(option);
    440           });
    441         }
    442         else
    443         {
    444           slider_label->setText(QString::number(option.value[i].float_value));
    445 
    446           const float range = option.max_value[i].float_value - option.min_value[i].float_value;
    447           const float step_value =
    448             (option.step_value[i].float_value != 0) ? option.step_value[i].float_value : ((range + 99.0f) / 100.0f);
    449           const float num_steps = std::ceil(range / step_value);
    450           slider->setMinimum(0);
    451           slider->setMaximum(num_steps);
    452           slider->setSingleStep(1);
    453           slider->setTickInterval(step_value);
    454           slider->setValue(
    455             static_cast<int>((option.value[i].float_value - option.min_value[i].float_value) / step_value));
    456           connect(slider, &QSlider::valueChanged, [this, &option, i, slider_label](int value) {
    457             const float new_value = std::clamp(option.min_value[i].float_value +
    458                                                  (static_cast<float>(value) * option.step_value[i].float_value),
    459                                                option.min_value[i].float_value, option.max_value[i].float_value);
    460             option.value[i].float_value = new_value;
    461             slider_label->setText(QString::number(new_value));
    462             updateConfigForOption(option);
    463           });
    464         }
    465 
    466         row++;
    467       }
    468     }
    469   }
    470 
    471   QDialogButtonBox* button_box = new QDialogButtonBox(QDialogButtonBox::RestoreDefaults, this);
    472   connect(button_box, &QDialogButtonBox::clicked, this, &PostProcessingShaderConfigWidget::onResetDefaultsClicked);
    473   m_layout->addWidget(button_box, row, 0, 1, -1);
    474 
    475   row++;
    476   m_layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding), row, 0, 1, 3);
    477 }