From 75f5ccfb78d276d7291b97ce3acca72c0b123786 Mon Sep 17 00:00:00 2001 From: FakeMichau <49685661+FakeMichau@users.noreply.github.com> Date: Sun, 8 Mar 2026 23:47:17 +0100 Subject: [PATCH] Rethink combo list creation --- OptiScaler/menu/menu_common.cpp | 232 ++++++++++++++++---------------- OptiScaler/menu/menu_common.h | 22 +++ 2 files changed, 140 insertions(+), 114 deletions(-) diff --git a/OptiScaler/menu/menu_common.cpp b/OptiScaler/menu/menu_common.cpp index 6ae50731..ad213797 100644 --- a/OptiScaler/menu/menu_common.cpp +++ b/OptiScaler/menu/menu_common.cpp @@ -1382,6 +1382,49 @@ void MenuCommon::PopulateCombo(std::string name, CustomOptional* va } } +template +void MenuCommon::PopulateComboNew(const std::string& name, TOptional& currentValue, + const std::vector>& options) +{ + // Find the label for the currently selected item + std::string preview = "Unknown"; + T currentVal = currentValue.value_or(options[0].value); + + for (const auto& opt : options) + { + if (opt.value == currentVal) + { + preview = opt.label; + break; + } + } + + if (ImGui::BeginCombo(name.c_str(), preview.c_str())) + { + for (const auto& opt : options) + { + if (opt.disabled) + ImGui::BeginDisabled(); + + bool isSelected = (currentVal == opt.value); + if (ImGui::Selectable(opt.label.c_str(), isSelected)) + { + currentValue = opt.value; + } + + // Show tooltip for the individual item if it exists + if (!opt.tooltip.empty() && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) + { + ImGui::SetTooltip("%s", opt.tooltip.c_str()); + } + + if (opt.disabled) + ImGui::EndDisabled(); + } + ImGui::EndCombo(); + } +} + static ImVec4 toneMapColor(const ImVec4& color) { // Apply tone mapping (e.g., Reinhard tone mapping) @@ -3039,169 +3082,132 @@ bool MenuCommon::RenderMenu() } } + /// FG INPUTS + + static std::vector> inputOptions; + inputOptions.clear(); + // clang-format off - const char* fgInputOptions[] = { - "No Frame Generation", // NoFG - "Nukem's DLSSG", // Nukems - "FSR 3.1 FG", // FSRFG - "DLSSG via Streamline", // DLSSG - "XeFG", // XeFG - "OptiFG (Upscaler)", // Upscaler - "FSR 3.0 FG", // FSRFG30 - }; - std::vector fgInputDesc = { - "", - "Limited to FSR3-FG\n\nSupports Hudless out of the box\n\nUses Streamline swapchain for pacing", - "Can be used with any FG Output\n\nSupports Hudless out of the box", - "Can be used with any FG Output\n\nSupports Hudless out of the box\n\nLimited to games that use Streamline v2", - "Support not implemented", - "Upscaler must be enabled\n\nCan be used with any FG Output, but might be imperfect with some\n\nTo prevent UI glitching, HUDfix required", - "Can be used with any FG Output\n\nSupports Hudless out of the box", - }; - std::vector disabledMaskInput = { - false, - false, - false, - false, // TODO: Disable DLSSG inputs in games that can't support it - true, - false, - false, + + inputOptions = { + { FGInput::NoFG, "No Frame Generation", "" }, + { FGInput::Nukems, "Nukem's DLSSG", "Limited to FSR3-FG\n\nSupports Hudless out of the box\n\nUses Streamline swapchain for pacing" }, + { FGInput::FSRFG, "FSR 3.1 FG", "Can be used with any FG Output\n\nSupports Hudless out of the box" }, + { FGInput::DLSSG, "DLSSG via Streamline", "Can be used with any FG Output\n\nSupports Hudless out of the box\n\nLimited to games that use Streamline v2" }, + { FGInput::XeFG, "XeFG", "" }, + { FGInput::Upscaler, "OptiFG (Upscaler)", "Upscaler must be enabled\n\nCan be used with any FG Output, but might be imperfect with some\n\nTo prevent UI glitching, HUDfix required" }, + { FGInput::FSRFG30, "FSR 3.0 FG", "Can be used with any FG Output\n\nSupports Hudless out of the box" } }; + // clang-format on + // XeFG requirements + auto constexpr xefgInputIndex = (uint32_t) FGInput::XeFG; + inputOptions[xefgInputIndex].set_disabled(true, "Support not implemented"); + // OptiFG requirements auto constexpr optiFgIndex = (uint32_t) FGInput::Upscaler; - if (state.api == API::DX11 || state.api == API::Vulkan) - { - disabledMaskInput[optiFgIndex] = true; - fgInputDesc[optiFgIndex] = "Unsupported API"; - } - else if (state.workingMode == WorkingMode::Nvngx) - { - disabledMaskInput[optiFgIndex] = true; - fgInputDesc[optiFgIndex] = "Unsupported Opti working mode"; - } - else if (state.activeFgOutput == FGOutput::FSRFG && !FfxApiProxy::IsFGReady() && !fsr31InitTried) + inputOptions[optiFgIndex].set_disabled(state.api == API::DX11 || state.api == API::Vulkan, + "Unsupported API"); + inputOptions[optiFgIndex].set_disabled(state.workingMode == WorkingMode::Nvngx, + "Unsupported Opti working mode"); + + if (!inputOptions[optiFgIndex].disabled && state.activeFgOutput == FGOutput::FSRFG && !FfxApiProxy::IsFGReady() && + !fsr31InitTried) { fsr31InitTried = true; FfxApiProxy::InitFfxDx12(); - disabledMaskInput[optiFgIndex] = !FfxApiProxy::IsFGReady(); - fgInputDesc[optiFgIndex] = "amd_fidelityfx_dx12.dll is missing"; + inputOptions[optiFgIndex].set_disabled(!FfxApiProxy::IsFGReady(), + "amd_fidelityfx_dx12.dll is missing"); } - else if (state.activeFgOutput == FGOutput::XeFG && !xefgInitTried && XeFGProxy::Module() == nullptr) + else if (!inputOptions[optiFgIndex].disabled && state.activeFgOutput == FGOutput::XeFG && + !xefgInitTried && XeFGProxy::Module() == nullptr) { xefgInitTried = true; XeFGProxy::InitXeFG(); - disabledMaskInput[optiFgIndex] = XeFGProxy::Module() == nullptr; - fgInputDesc[optiFgIndex] = "libxess_fg.dll is missing"; + inputOptions[optiFgIndex].set_disabled(XeFGProxy::Module() == nullptr, "libxess_fg.dll is missing"); } // DLSSG inputs requirements auto constexpr dlssgInputIndex = (uint32_t) FGInput::DLSSG; - if (state.streamlineVersion.major < 2) - { - disabledMaskInput[dlssgInputIndex] = true; - fgInputDesc[dlssgInputIndex] = - StrFmt("Unsupported Streamline version: %d.%d.%d", state.streamlineVersion.major, - state.streamlineVersion.minor, state.streamlineVersion.patch); + inputOptions[dlssgInputIndex].set_disabled(state.swapchainApi == API::DX11, "Unsupported API"); - if (config->FGInput.value_or_default() == FGInput::DLSSG) - config->FGInput.reset(); - } - else if (State::Instance().swapchainApi == API::DX11) + if (!inputOptions[dlssgInputIndex].disabled && state.streamlineVersion.major < 2) { - disabledMaskInput[dlssgInputIndex] = true; - fgInputDesc[dlssgInputIndex] = "Unsupported API"; + inputOptions[dlssgInputIndex].set_disabled(true, std::format("Unsupported Streamline version: {}.{}.{}", state.streamlineVersion.major, + state.streamlineVersion.minor, state.streamlineVersion.patch)); } // FSRFG inputs requirements auto constexpr fsrfgInputIndex = (uint32_t) FGInput::FSRFG; - if (State::Instance().swapchainApi != API::DX12) - { - disabledMaskInput[fsrfgInputIndex] = true; - fgInputDesc[fsrfgInputIndex] = "Unsupported API"; - } + inputOptions[fsrfgInputIndex].set_disabled(state.swapchainApi != API::DX12, "Unsupported API"); + // FSRFG30 inputs requirements auto constexpr fsrfg30InputIndex = (uint32_t) FGInput::FSRFG30; - if (State::Instance().swapchainApi != API::DX12) - { - disabledMaskInput[fsrfg30InputIndex] = true; - fgInputDesc[fsrfg30InputIndex] = "Unsupported API"; - } - - constexpr auto fgInputOptionsCount = sizeof(fgInputOptions) / sizeof(char*); + inputOptions[fsrfg30InputIndex].set_disabled(state.swapchainApi != API::DX12, "Unsupported API"); if (!config->FGInput.has_value()) config->FGInput = config->FGInput.value_or_default(); // need to have a value before combo + + /// FG OUTPUTS + + static std::vector> outputOptions; + outputOptions.clear(); + // clang-format off - const char* fgOutputOptions[] = { - "No Frame Generation", // NoFG - "FSR3-FG via Nukem's", // Nukems - "FSR FG", // FSRFG - "DLSSG", // DLSSG - "XeFG" // XeFG - }; - std::vector fgOutputDesc = { - "", - "Enable DLSS-FG in-game", - "FSR3/4 FG", - "Support not implemented", - "XeFG", - }; - std::vector disabledMaskOutput = { - false, - false, - false, - true, - false, + + outputOptions = { + { FGOutput::NoFG, "No Frame Generation", "" }, + { FGOutput::Nukems, "FSR3-FG via Nukem's", "Enable DLSS-FG in-game" }, + { FGOutput::FSRFG, "FSR FG", "FSR3/4 FG" }, + { FGOutput::DLSSG, "DLSSG", "Support not implemented" }, + { FGOutput::XeFG, "XeFG", "XeFG" } }; + // clang-format on + // DLSSG output requirements + auto constexpr dlssgOutputIndex = (uint32_t) FGOutput::DLSSG; + outputOptions[dlssgOutputIndex].set_disabled(true, "Support not implemented"); + // Nukem's FG mod requirements auto constexpr nukemsInputIndex = (uint32_t) FGInput::Nukems; auto constexpr nukemsOutputIndex = (uint32_t) FGOutput::Nukems; if (state.workingMode == WorkingMode::Nvngx) { - disabledMaskInput[nukemsInputIndex] = true; - fgInputDesc[nukemsInputIndex] = "Unsupported Opti working mode"; - disabledMaskOutput[nukemsOutputIndex] = true; - fgOutputDesc[nukemsOutputIndex] = "Unsupported Opti working mode"; + inputOptions[nukemsInputIndex].set_disabled(true, "Unsupported Opti working mode"); + outputOptions[nukemsOutputIndex].set_disabled(true, "Unsupported Opti working mode"); } else if (!state.NukemsFilesAvailable) { - disabledMaskInput[nukemsInputIndex] = true; - fgInputDesc[nukemsInputIndex] = "Missing the dlssg_to_fsr3_amd_is_better.dll file"; - disabledMaskOutput[nukemsOutputIndex] = true; - fgOutputDesc[nukemsOutputIndex] = "Missing the dlssg_to_fsr3_amd_is_better.dll file"; + inputOptions[nukemsInputIndex].set_disabled(true, + "Missing the dlssg_to_fsr3_amd_is_better.dll file"); + outputOptions[nukemsOutputIndex].set_disabled(true, + "Missing the dlssg_to_fsr3_amd_is_better.dll file"); } - // FSR FG / XeFG output requirements + // FSR FG output requirements auto constexpr fsrfgOutputIndex = (uint32_t) FGOutput::FSRFG; - auto constexpr xefgOutputIndex = (uint32_t) FGOutput::XeFG; - if (state.swapchainApi != API::DX12) - { - disabledMaskOutput[fsrfgOutputIndex] = true; - fgOutputDesc[fsrfgOutputIndex] = "Unsupported API"; - disabledMaskOutput[xefgOutputIndex] = true; - fgOutputDesc[xefgOutputIndex] = "Unsupported API"; - } + outputOptions[fsrfgOutputIndex].set_disabled(state.swapchainApi != API::DX12, "Unsupported API"); - constexpr auto fgOutputOptionsCount = std::size(fgOutputOptions); + // XeFG output requirements + auto constexpr xefgOutputIndex = (uint32_t) FGOutput::XeFG; + outputOptions[xefgOutputIndex].set_disabled(state.swapchainApi != API::DX12, "Unsupported API"); // Unsupported FG input selected - if (config->FGInput != FGInput::NoFG && disabledMaskInput[(uint32_t) state.activeFgInput] && + if (config->FGInput != FGInput::NoFG && inputOptions[(uint32_t) state.activeFgInput].disabled && state.activeFgInput == config->FGInput) { - LOG_WARN("Resetting FGInput to NoFG: {}", fgInputDesc[(uint32_t) state.activeFgInput]); + LOG_WARN("Resetting FGInput to NoFG: {}", inputOptions[(uint32_t) state.activeFgInput].label); config->FGInput = FGInput::NoFG; } // Unsupported FG output selected - if (config->FGOutput != FGOutput::NoFG && disabledMaskOutput[(uint32_t) state.activeFgOutput] && + if (config->FGOutput != FGOutput::NoFG && outputOptions[(uint32_t) state.activeFgOutput].disabled && state.activeFgOutput == config->FGOutput) { - LOG_WARN("Resetting FGOutput to NoFG: {}", fgOutputDesc[(uint32_t) state.activeFgOutput]); + LOG_WARN("Resetting FGOutput to NoFG: {}", outputOptions[(uint32_t) state.activeFgOutput].label); config->FGOutput = FGOutput::NoFG; } @@ -3215,9 +3221,7 @@ bool MenuCommon::RenderMenu() { ImGui::TableNextColumn(); - PopulateCombo("FG Source", reinterpret_cast*>(&config->FGInput), - fgInputOptions, fgInputDesc.data(), fgInputOptionsCount, disabledMaskInput.data(), - false); + PopulateComboNew("FG Input", config->FGInput, inputOptions); ShowTooltip("The data source to be used for FG"); ImGui::TableNextColumn(); @@ -3225,13 +3229,13 @@ bool MenuCommon::RenderMenu() const bool disableOutputs = config->FGInput.value_or_default() == FGInput::Nukems; ImGui::BeginDisabled(disableOutputs); - PopulateCombo("FG Output", reinterpret_cast*>(&config->FGOutput), - fgOutputOptions, fgOutputDesc.data(), fgOutputOptionsCount, - disabledMaskOutput.data(), false); + PopulateComboNew("FG Output", config->FGOutput, outputOptions); ImGui::EndDisabled(); if (disableOutputs) ShowTooltip("Doesn't matter with the selected FG Source"); + else + ShowTooltip("The FG that you will actually be using"); ImGui::EndTable(); } diff --git a/OptiScaler/menu/menu_common.h b/OptiScaler/menu/menu_common.h index 22ca0ee9..a49bc80f 100644 --- a/OptiScaler/menu/menu_common.h +++ b/OptiScaler/menu/menu_common.h @@ -55,6 +55,25 @@ class ScopedCollapsingHeader bool _headerOpen = false; }; +template struct MenuOption +{ + T value; + std::string label; + std::string tooltip; + bool disabled = false; + + MenuOption& set_disabled(bool condition, const std::string& reason = "") + { + if (condition) + { + disabled = true; + if (!reason.empty()) + tooltip = reason; + } + return *this; + } +}; + class MenuCommon { private: @@ -170,6 +189,9 @@ class MenuCommon static void PopulateCombo(std::string name, CustomOptional* value, const char* names[], const std::string desc[], int length, const uint8_t disabledMask[] = nullptr, bool firstAsDefault = true); + template + static void PopulateComboNew(const std::string& name, TOptional& currentValue, + const std::vector>& options); public: static void Dx11Inited() { _dx11Ready = true; }