Rethink combo list creation

This commit is contained in:
FakeMichau
2026-03-08 23:47:17 +01:00
parent 0e41bd7e03
commit 75f5ccfb78
2 changed files with 140 additions and 114 deletions
+118 -114
View File
@@ -1382,6 +1382,49 @@ void MenuCommon::PopulateCombo(std::string name, CustomOptional<uint32_t, B>* va
}
}
template <typename T, typename TOptional>
void MenuCommon::PopulateComboNew(const std::string& name, TOptional& currentValue,
const std::vector<MenuOption<T>>& 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<MenuOption<FGInput>> 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<std::string> 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<uint8_t> 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<MenuOption<FGOutput>> 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<std::string> fgOutputDesc = {
"",
"Enable DLSS-FG in-game",
"FSR3/4 FG",
"Support not implemented",
"XeFG",
};
std::vector<uint8_t> 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<CustomOptional<uint32_t>*>(&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<CustomOptional<uint32_t>*>(&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();
}
+22
View File
@@ -55,6 +55,25 @@ class ScopedCollapsingHeader
bool _headerOpen = false;
};
template <typename T> 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<uint32_t, B>* value, const char* names[],
const std::string desc[], int length, const uint8_t disabledMask[] = nullptr,
bool firstAsDefault = true);
template <typename T, typename TOptional>
static void PopulateComboNew(const std::string& name, TOptional& currentValue,
const std::vector<MenuOption<T>>& options);
public:
static void Dx11Inited() { _dx11Ready = true; }