diff --git a/src/duckstation-qt/postprocessingsettingswidget.cpp b/src/duckstation-qt/postprocessingsettingswidget.cpp index 7f4db7345..852cad62b 100644 --- a/src/duckstation-qt/postprocessingsettingswidget.cpp +++ b/src/duckstation-qt/postprocessingsettingswidget.cpp @@ -308,14 +308,45 @@ void PostProcessingShaderConfigWidget::createUi() { u32 row = 0; + const std::string* last_category = nullptr; + for (PostProcessing::ShaderOption& option : m_options) { if (option.ui_name.empty()) continue; + if (!last_category || option.category != *last_category) + { + if (last_category) + m_layout->addItem(new QSpacerItem(1, 4), row++, 0); + + if (!option.category.empty()) + { + QLabel* label = new QLabel(QString::fromStdString(option.category), this); + QFont label_font(label->font()); + label_font.setPointSizeF(12.0f); + label->setFont(label_font); + m_layout->addWidget(label, row++, 0, 1, 3, Qt::AlignLeft); + } + + if (last_category) + { + QLabel* line = new QLabel(this); + line->setFrameShape(QFrame::HLine); + line->setFixedHeight(4); + line->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + m_layout->addWidget(line, row++, 0, 1, 3); + } + + last_category = &option.category; + } + + const QString tooltip = QString::fromStdString(option.tooltip); + if (option.type == PostProcessing::ShaderOption::Type::Bool) { QCheckBox* checkbox = new QCheckBox(QString::fromStdString(option.ui_name), this); + checkbox->setToolTip(tooltip); checkbox->setChecked(option.value[0].int_value != 0); connect(checkbox, &QCheckBox::stateChanged, [this, &option](int state) { option.value[0].int_value = (state == Qt::Checked) ? 1 : 0; @@ -325,6 +356,24 @@ void PostProcessingShaderConfigWidget::createUi() m_widgets.push_back(checkbox); row++; } + else if (option.type == PostProcessing::ShaderOption::Type::Int && !option.choice_options.empty()) + { + QLabel* label = new QLabel(QString::fromStdString(option.ui_name), this); + label->setToolTip(tooltip); + m_layout->addWidget(label, row, 0, 1, 1, Qt::AlignLeft); + + QComboBox* combo = new QComboBox(this); + combo->setToolTip(tooltip); + for (const std::string& combo_option : option.choice_options) + combo->addItem(QString::fromStdString(combo_option)); + connect(combo, &QComboBox::currentIndexChanged, [this, &option](int index) { + option.value[0].int_value = index; + updateConfigForOption(option); + }); + m_layout->addWidget(combo, row, 1, 1, 2, Qt::AlignLeft); + m_widgets.push_back(combo); + row++; + } else { for (u32 i = 0; i < option.vector_size; i++) @@ -342,14 +391,17 @@ void PostProcessingShaderConfigWidget::createUi() } QWidget* label_w = new QLabel(label, this); + label_w->setToolTip(tooltip); m_layout->addWidget(label_w, row, 0, 1, 1, Qt::AlignLeft); m_widgets.push_back(label_w); QSlider* slider = new QSlider(Qt::Horizontal, this); + slider->setToolTip(tooltip); m_layout->addWidget(slider, row, 1, 1, 1, Qt::AlignLeft); m_widgets.push_back(slider); QLabel* slider_label = new QLabel(this); + slider_label->setToolTip(tooltip); m_layout->addWidget(slider_label, row, 2, 1, 1, Qt::AlignLeft); m_widgets.push_back(slider_label); diff --git a/src/util/postprocessing.h b/src/util/postprocessing.h index a4c4620cf..cc082ec0c 100644 --- a/src/util/postprocessing.h +++ b/src/util/postprocessing.h @@ -51,6 +51,8 @@ struct ShaderOption std::string name; std::string ui_name; std::string dependent_option; + std::string category; + std::string tooltip; Type type; u32 vector_size; u32 buffer_size; @@ -60,6 +62,7 @@ struct ShaderOption ValueVector max_value; ValueVector step_value; ValueVector value; + std::vector choice_options; static u32 ParseIntVector(const std::string_view& line, ValueVector* values); static u32 ParseFloatVector(const std::string_view& line, ValueVector* values); diff --git a/src/util/postprocessing_shader_fx.cpp b/src/util/postprocessing_shader_fx.cpp index e2d463820..ed6e893f7 100644 --- a/src/util/postprocessing_shader_fx.cpp +++ b/src/util/postprocessing_shader_fx.cpp @@ -430,9 +430,20 @@ bool PostProcessing::ReShadeFXShader::CreateModule(s32 buffer_width, s32 buffer_ return true; } +static bool HasAnnotationWithName(const reshadefx::uniform_info& uniform, const std::string_view& annotation_name) +{ + for (const reshadefx::annotation& an : uniform.annotations) + { + if (an.name == annotation_name) + return true; + } + + return false; +} + static std::string_view GetStringAnnotationValue(const std::vector& annotations, - const std::string_view& annotation_name, - const std::string_view& default_value) + const std::string_view annotation_name, + const std::string_view default_value) { for (const reshadefx::annotation& an : annotations) { @@ -449,7 +460,7 @@ static std::string_view GetStringAnnotationValue(const std::vector& annotations, - const std::string_view& annotation_name, bool default_value) + const std::string_view annotation_name, bool default_value) { for (const reshadefx::annotation& an : annotations) { @@ -466,7 +477,7 @@ static bool GetBooleanAnnotationValue(const std::vector& } static PostProcessing::ShaderOption::ValueVector -GetVectorAnnotationValue(const reshadefx::uniform_info& uniform, const std::string_view& annotation_name, +GetVectorAnnotationValue(const reshadefx::uniform_info& uniform, const std::string_view annotation_name, const PostProcessing::ShaderOption::ValueVector& default_value) { PostProcessing::ShaderOption::ValueVector vv = default_value; @@ -477,7 +488,7 @@ GetVectorAnnotationValue(const reshadefx::uniform_info& uniform, const std::stri const u32 components = std::min(an.type.components(), PostProcessing::ShaderOption::MAX_VECTOR_COMPONENTS); - if (an.type.base == uniform.type.base) + if (an.type.base == uniform.type.base || (an.type.is_integral() && uniform.type.is_integral())) // int<->uint { if (components > 0) std::memcpy(&vv[0].float_value, &an.value.as_float[0], sizeof(float) * components); @@ -584,6 +595,8 @@ bool PostProcessing::ReShadeFXShader::CreateOptions(const reshadefx::module& mod ShaderOption opt; opt.name = ui.name; + opt.category = GetStringAnnotationValue(ui.annotations, "ui_category", std::string_view()); + opt.tooltip = GetStringAnnotationValue(ui.annotations, "ui_tooltip", std::string_view()); if (!GetBooleanAnnotationValue(ui.annotations, "hidden", false)) { @@ -592,7 +605,7 @@ bool PostProcessing::ReShadeFXShader::CreateOptions(const reshadefx::module& mod opt.ui_name = ui.name; } - // const std::string_view ui_type = GetStringAnnotationValue(ui.annotations, "ui_type", std::string_view(); + const std::string_view ui_type = GetStringAnnotationValue(ui.annotations, "ui_type", std::string_view()); switch (ui.type.base) { @@ -624,8 +637,8 @@ bool PostProcessing::ReShadeFXShader::CreateOptions(const reshadefx::module& mod return false; } - opt.min_value = GetVectorAnnotationValue(ui, "ui_min", {}); - opt.max_value = GetVectorAnnotationValue(ui, "ui_max", {}); + opt.min_value = GetVectorAnnotationValue(ui, "ui_min", opt.default_value); + opt.max_value = GetVectorAnnotationValue(ui, "ui_max", opt.default_value); ShaderOption::ValueVector default_step = {}; switch (opt.type) { @@ -654,6 +667,25 @@ bool PostProcessing::ReShadeFXShader::CreateOptions(const reshadefx::module& mod } opt.step_value = GetVectorAnnotationValue(ui, "ui_step", default_step); + // set a default maximum based on step if there isn't one + if (!HasAnnotationWithName(ui, "ui_max") && HasAnnotationWithName(ui, "ui_step")) + { + for (u32 i = 0; i < opt.vector_size; i++) + { + switch (opt.type) + { + case ShaderOption::Type::Float: + opt.max_value[i].float_value = opt.min_value[i].float_value + (opt.step_value[i].float_value * 100.0f); + break; + case ShaderOption::Type::Int: + opt.max_value[i].int_value = opt.min_value[i].int_value + (opt.step_value[i].int_value * 100); + break; + default: + break; + } + } + } + if (ui.has_initializer_value) { std::memcpy(&opt.default_value[0].float_value, &ui.initializer_value.as_float[0], @@ -667,9 +699,44 @@ bool PostProcessing::ReShadeFXShader::CreateOptions(const reshadefx::module& mod // Assume default if user doesn't set it. opt.value = opt.default_value; + if (!ui_type.empty() && opt.vector_size > 1) + { + Log_WarningFmt("Uniform '{}' has UI type of '{}' but is vector not scalar ({}), ignoring", opt.name, ui_type, + opt.vector_size); + } + else if (!ui_type.empty()) + { + if ((ui_type == "combo" || ui_type == "radio") && opt.type == ShaderOption::Type::Int) + { + const std::string_view ui_values = GetStringAnnotationValue(ui.annotations, "ui_items", std::string_view()); + + size_t start_pos = 0; + while (start_pos < ui_values.size()) + { + size_t end_pos = start_pos; + while (end_pos < ui_values.size() && ui_values[end_pos] != '\0') + end_pos++; + + const size_t len = end_pos - start_pos; + if (len > 0) + opt.choice_options.emplace_back(ui_values.substr(start_pos, len)); + start_pos = end_pos + 1; + } + + // update max if it hasn't been specified + const size_t num_choices = opt.choice_options.size(); + if (num_choices > 0) + opt.max_value[0].int_value = std::max(static_cast(num_choices - 1), opt.max_value[0].int_value); + } + } + m_options.push_back(std::move(opt)); } + // sort based on category + std::sort(m_options.begin(), m_options.end(), + [](const ShaderOption& lhs, const ShaderOption& rhs) { return lhs.category < rhs.category; }); + m_uniforms_size = mod.total_uniform_size; Log_DevFmt("{}: {} options", m_filename, m_options.size()); return true; @@ -741,6 +808,12 @@ bool PostProcessing::ReShadeFXShader::GetSourceOption(const reshadefx::uniform_i *si = SourceOptionType::MousePoint; return true; } + else if (source == "mousebutton") + { + Log_WarningFmt("Ignoring mousebutton source in uniform '{}', not supported.", ui.name); + *si = SourceOptionType::Zero; + return true; + } else if (source == "random") { if ((!ui.type.is_floating_point() && !ui.type.is_integral()) || ui.type.components() != 1)