From e1b5e0ae6b62d4e6c5ca7b3ac1679d8d59204cf0 Mon Sep 17 00:00:00 2001 From: FakeMichau <49685661+FakeMichau@users.noreply.github.com> Date: Thu, 13 Aug 2026 17:56:08 +0200 Subject: [PATCH] Add Nvngx FG Combo, FFX + Enabler combo mode Mainly for use with FSR 4 FG as otherwise it doesn't make sense. FFX for the middle fake frame, Enabler for the rest: 2x - FFX 3x - Enabler 4x - FFX + Enabler 5x - Enabler 6x - FFX + Enabler Due to pacing, only odd number of fake frames are able to use FFX --- OptiScaler.ini | 3 +- OptiScaler/Config.cpp | 4 + OptiScaler/OptiScaler.vcxproj | 2 + OptiScaler/OptiScaler.vcxproj.filters | 6 + OptiScaler/State.h | 1 + OptiScaler/framegen/nvngx/Nvngx_Combo.cpp | 288 ++++++++++++++++++++++ OptiScaler/framegen/nvngx/Nvngx_Combo.h | 69 ++++++ OptiScaler/framegen/nvngx/Nvngx_FG.cpp | 8 + OptiScaler/menu/menu_common.cpp | 25 +- 9 files changed, 402 insertions(+), 4 deletions(-) create mode 100644 OptiScaler/framegen/nvngx/Nvngx_Combo.cpp create mode 100644 OptiScaler/framegen/nvngx/Nvngx_Combo.h diff --git a/OptiScaler.ini b/OptiScaler.ini index 506788c3..a9657563 100644 --- a/OptiScaler.ini +++ b/OptiScaler.ini @@ -47,7 +47,8 @@ FGOutput=auto ; Nukems - requires dlssg_to_fsr3_amd_is_better.dll, FSR 3 FG ; Arturs - requires dlss-enabler-headless.dll, FSR 3 MFG ; FFX - requires amd_fidelityfx_dx12.dll OR amd_fidelityfx_loader_dx12.dll + amd_fidelityfx_framegeneration_dx12.dll, FSRFG 3 or 4 -; None, Nukems, Arturs, FFX - Default (auto) is None +; Combo - requires +; None, Nukems, Arturs, FFX, Combo - Default (auto) is None FGNvngxReplacement=auto ; Frametime source, would affect frame pacing diff --git a/OptiScaler/Config.cpp b/OptiScaler/Config.cpp index 2ac24b12..a7ca4be9 100644 --- a/OptiScaler/Config.cpp +++ b/OptiScaler/Config.cpp @@ -115,6 +115,8 @@ bool Config::Reload(std::filesystem::path iniPath) FGNvngxReplacement.set_from_config(FGNvngxReplacement::Arturs); else if (lstrcmpiA(FGNvngxReplacementString.value().c_str(), "ffx") == 0) FGNvngxReplacement.set_from_config(FGNvngxReplacement::FFX); + else if (lstrcmpiA(FGNvngxReplacementString.value().c_str(), "combo") == 0) + FGNvngxReplacement.set_from_config(FGNvngxReplacement::Combo); } if (auto forceXell = readBool("fakenvapi", "ForceXeLL"); forceXell.has_value() && forceXell.value()) @@ -908,6 +910,8 @@ bool Config::SaveIni() FGNvngxReplacementString = "Arturs"; else if (FGNvngxReplacementHeld.value() == FGNvngxReplacement::FFX) FGNvngxReplacementString = "FFX"; + else if (FGNvngxReplacementHeld.value() == FGNvngxReplacement::Combo) + FGNvngxReplacementString = "Combo"; } ini.SetValue("FrameGen", "FGNvngxReplacement", FGNvngxReplacementString.c_str()); diff --git a/OptiScaler/OptiScaler.vcxproj b/OptiScaler/OptiScaler.vcxproj index 90bd9dcf..4549cc5c 100644 --- a/OptiScaler/OptiScaler.vcxproj +++ b/OptiScaler/OptiScaler.vcxproj @@ -303,6 +303,7 @@ copy NUL "$(SolutionDir)x64\Release\a\!! EXTRACT ALL FILES TO GAME FOLDER !!" /Y + @@ -593,6 +594,7 @@ copy NUL "$(SolutionDir)x64\Release\a\!! EXTRACT ALL FILES TO GAME FOLDER !!" /Y + diff --git a/OptiScaler/OptiScaler.vcxproj.filters b/OptiScaler/OptiScaler.vcxproj.filters index 81c6f056..e9f113e6 100644 --- a/OptiScaler/OptiScaler.vcxproj.filters +++ b/OptiScaler/OptiScaler.vcxproj.filters @@ -932,6 +932,9 @@ Header Files + + Header Files + @@ -1496,6 +1499,9 @@ Source Files + + Source Files + diff --git a/OptiScaler/State.h b/OptiScaler/State.h index 94164cb0..38c9c7b7 100644 --- a/OptiScaler/State.h +++ b/OptiScaler/State.h @@ -54,6 +54,7 @@ enum class FGNvngxReplacement : uint32_t Nukems, Arturs, FFX, + Combo, }; enum class WorkingMode : uint32_t diff --git a/OptiScaler/framegen/nvngx/Nvngx_Combo.cpp b/OptiScaler/framegen/nvngx/Nvngx_Combo.cpp new file mode 100644 index 00000000..06fcb7fc --- /dev/null +++ b/OptiScaler/framegen/nvngx/Nvngx_Combo.cpp @@ -0,0 +1,288 @@ +#include "pch.h" +#include "Nvngx_Combo.h" + +#include + +#include "proxies/NVNGX_Proxy.h" +#include "proxies/Ntdll_Proxy.h" +#include +#include +#include + +using Microsoft::WRL::ComPtr; + +NVSDK_NGX_Result Nvngx_Combo::D3D12_Init(unsigned long long InApplicationId, const wchar_t* InApplicationDataPath, + ID3D12Device* InDevice, const NVSDK_NGX_FeatureCommonInfo* InFeatureInfo, + NVSDK_NGX_Version InSDKVersion) +{ + return D3D12_Init_Ext(InApplicationId, InApplicationDataPath, InDevice, InSDKVersion, InFeatureInfo); +} + +NVSDK_NGX_Result Nvngx_Combo::D3D12_Init_Ext(unsigned long long InApplicationId, const wchar_t* InApplicationDataPath, + ID3D12Device* InDevice, NVSDK_NGX_Version InSDKVersion, + const NVSDK_NGX_FeatureCommonInfo* InFeatureInfo) +{ + auto resultArturs = + artursProvider->D3D12_Init_Ext(InApplicationId, InApplicationDataPath, InDevice, InSDKVersion, InFeatureInfo); + auto resultFfx = + ffxProvider->D3D12_Init_Ext(InApplicationId, InApplicationDataPath, InDevice, InSDKVersion, InFeatureInfo); + + if (resultArturs != NVSDK_NGX_Result_Success || resultFfx != NVSDK_NGX_Result_Success) + { + return NVSDK_NGX_Result_Fail; + } + + return NVSDK_NGX_Result_Success; +} + +NVSDK_NGX_Result Nvngx_Combo::D3D12_Shutdown() +{ + auto resultArturs = artursProvider->D3D12_Shutdown(); + auto resultFfx = ffxProvider->D3D12_Shutdown(); + + if (resultArturs != NVSDK_NGX_Result_Success || resultFfx != NVSDK_NGX_Result_Success) + { + return NVSDK_NGX_Result_Fail; + } + + return NVSDK_NGX_Result_Success; +} + +NVSDK_NGX_Result Nvngx_Combo::D3D12_Shutdown1(ID3D12Device* InDevice) +{ + auto resultArturs = artursProvider->D3D12_Shutdown1(InDevice); + auto resultFfx = ffxProvider->D3D12_Shutdown1(InDevice); + + if (resultArturs != NVSDK_NGX_Result_Success || resultFfx != NVSDK_NGX_Result_Success) + { + return NVSDK_NGX_Result_Fail; + } + + return NVSDK_NGX_Result_Success; +} + +NVSDK_NGX_Result Nvngx_Combo::D3D12_GetScratchBufferSize(NVSDK_NGX_Feature InFeatureId, + const NVSDK_NGX_Parameter* InParameters, + size_t* OutSizeInBytes) +{ + if (!OutSizeInBytes) + return NVSDK_NGX_Result_FAIL_InvalidParameter; + + *OutSizeInBytes = 0; + + return NVSDK_NGX_Result_Success; +} + +NVSDK_NGX_Result Nvngx_Combo::D3D12_CreateFeature(ID3D12GraphicsCommandList* InCmdList, NVSDK_NGX_Feature InFeatureID, + NVSDK_NGX_Parameter* InParameters, NVSDK_NGX_Handle** OutHandle) +{ + Nvngx_Combo_Handle** OutOurHandle = (Nvngx_Combo_Handle**) OutHandle; + + if (!OutOurHandle || !InParameters) + return NVSDK_NGX_Result_FAIL_InvalidParameter; + + if (InFeatureID != NVSDK_NGX_Feature_FrameGeneration) + return NVSDK_NGX_Result_FAIL_FeatureNotSupported; + + *OutOurHandle = new Nvngx_Combo_Handle(lastIdCreated++ + NVNGX_PROVIDER_ID_OFFSET, nullptr); + + auto resultArturs = + artursProvider->D3D12_CreateFeature(InCmdList, InFeatureID, InParameters, &(*OutOurHandle)->artursHandle); + auto resultFfx = + ffxProvider->D3D12_CreateFeature(InCmdList, InFeatureID, InParameters, &(*OutOurHandle)->ffxHandle); + + if (resultArturs != NVSDK_NGX_Result_Success || resultFfx != NVSDK_NGX_Result_Success) + { + if (resultArturs == NVSDK_NGX_Result_Success) + artursProvider->D3D12_ReleaseFeature((*OutOurHandle)->artursHandle); + + if (resultFfx == NVSDK_NGX_Result_Success) + ffxProvider->D3D12_ReleaseFeature((*OutOurHandle)->ffxHandle); + + delete *OutOurHandle; + *OutOurHandle = nullptr; + + return NVSDK_NGX_Result_Fail; + } + + return NVSDK_NGX_Result_Success; +} + +NVSDK_NGX_Result Nvngx_Combo::D3D12_ReleaseFeature(NVSDK_NGX_Handle* InHandle) +{ + Nvngx_Combo_Handle* InOurHandle = (Nvngx_Combo_Handle*) InHandle; + + if (!InOurHandle || InOurHandle->Id < NVNGX_PROVIDER_ID_OFFSET) + return NVSDK_NGX_Result_FAIL_InvalidParameter; + + auto resultArturs = artursProvider->D3D12_ReleaseFeature(InOurHandle->artursHandle); + auto resultFfx = ffxProvider->D3D12_ReleaseFeature(InOurHandle->ffxHandle); + + delete InOurHandle; + + if (resultArturs != NVSDK_NGX_Result_Success || resultFfx != NVSDK_NGX_Result_Success) + { + return NVSDK_NGX_Result_Fail; + } + + return NVSDK_NGX_Result_Success; +} + +NVSDK_NGX_Result Nvngx_Combo::D3D12_GetFeatureRequirements(IDXGIAdapter* Adapter, + const NVSDK_NGX_FeatureDiscoveryInfo* FeatureDiscoveryInfo, + NVSDK_NGX_FeatureRequirement* OutSupported) +{ + if (!OutSupported) + return NVSDK_NGX_Result_Fail; + + OutSupported->FeatureSupported = NVSDK_NGX_FeatureSupportResult_Supported; + OutSupported->MinHWArchitecture = 0x0; + strcpy_s(OutSupported->MinOSVersion, "10.0.0"); + + return NVSDK_NGX_Result_Success; +} + +static D3D12_RESOURCE_STATES GetD3D12State(FfxApiResourceState state) +{ + switch (state) + { + case FFX_API_RESOURCE_STATE_COMMON: + return D3D12_RESOURCE_STATE_COMMON; + case FFX_API_RESOURCE_STATE_UNORDERED_ACCESS: + return D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + case FFX_API_RESOURCE_STATE_COMPUTE_READ: + return D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + case FFX_API_RESOURCE_STATE_PIXEL_READ: + return D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE; + case FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ: + return (D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + case FFX_API_RESOURCE_STATE_COPY_SRC: + return D3D12_RESOURCE_STATE_COPY_SOURCE; + case FFX_API_RESOURCE_STATE_COPY_DEST: + return D3D12_RESOURCE_STATE_COPY_DEST; + case FFX_API_RESOURCE_STATE_GENERIC_READ: + return D3D12_RESOURCE_STATE_GENERIC_READ; + case FFX_API_RESOURCE_STATE_INDIRECT_ARGUMENT: + return D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT; + case FFX_API_RESOURCE_STATE_RENDER_TARGET: + return D3D12_RESOURCE_STATE_RENDER_TARGET; + default: + return D3D12_RESOURCE_STATE_COMMON; + } +} + +static void CopyTexture(ID3D12GraphicsCommandList* CommandList, const FfxApiResource* Destination, + const FfxApiResource* Source) +{ + const auto cmdList12 = reinterpret_cast(CommandList); + + D3D12_RESOURCE_BARRIER barriers[2] = {}; + barriers[0].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[0].Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barriers[0].Transition.pResource = static_cast(Destination->resource); // Destination + barriers[0].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barriers[0].Transition.StateBefore = GetD3D12State((FfxApiResourceState) Destination->state); + barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; + + barriers[1] = barriers[0]; + barriers[1].Transition.pResource = static_cast(Source->resource); // Source + barriers[1].Transition.StateBefore = GetD3D12State((FfxApiResourceState) Source->state); + barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; + + cmdList12->ResourceBarrier(2, barriers); + cmdList12->CopyResource(barriers[0].Transition.pResource, barriers[1].Transition.pResource); + std::swap(barriers[0].Transition.StateBefore, barriers[0].Transition.StateAfter); + std::swap(barriers[1].Transition.StateBefore, barriers[1].Transition.StateAfter); + cmdList12->ResourceBarrier(2, barriers); +} + +NVSDK_NGX_Result Nvngx_Combo::D3D12_EvaluateFeature(ID3D12GraphicsCommandList* InCmdList, + const NVSDK_NGX_Handle* InFeatureHandle, + NVSDK_NGX_Parameter* InParameters, + PFN_NVSDK_NGX_ProgressCallback InCallback) +{ + // This is our handle so can cast away const + Nvngx_Combo_Handle* InOurHandle = (Nvngx_Combo_Handle*) InFeatureHandle; + + if (!InParameters || !InOurHandle || InOurHandle->Id < NVNGX_PROVIDER_ID_OFFSET) + return NVSDK_NGX_Result_FAIL_InvalidParameter; + + // Assuming ffx can only do one fake frame + uint32_t frameIndex = 1; + InParameters->Get("DLSSG.MultiFrameIndex", &frameIndex); + + uint32_t frameCount = 0; + InParameters->Get("DLSSG.MultiFrameCount", &frameCount); + + NVSDK_NGX_Result result {}; + + if ((frameCount == 1 && frameIndex == 1) || (frameCount == 3 && frameIndex == 2) || + (frameCount == 5 && frameIndex == 3)) + { + // Make the provider think we only want 2x to it generates the middle frame + InParameters->Set("DLSSG.MultiFrameIndex", 1); + InParameters->Set("DLSSG.MultiFrameCount", 1); + + result = ffxProvider->D3D12_EvaluateFeature(InCmdList, InOurHandle->ffxHandle, InParameters, InCallback); + + // Restore + InParameters->Set("DLSSG.MultiFrameIndex", frameIndex); + InParameters->Set("DLSSG.MultiFrameCount", frameCount); + } + else + { + result = artursProvider->D3D12_EvaluateFeature(InCmdList, InOurHandle->artursHandle, InParameters, InCallback); + } + + ID3D12Resource* outputReal = nullptr; + InParameters->Get("DLSSG.OutputReal", &outputReal); + auto outputRealFfx = ffxApiGetResourceDX12(outputReal, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS); + + // Copy backbuffer to output real + if (outputReal) + { + ID3D12Resource* backbuffer = nullptr; + InParameters->Get("DLSSG.Backbuffer", &backbuffer); + auto backbufferFfx = ffxApiGetResourceDX12(backbuffer, FFX_API_RESOURCE_STATE_COMPUTE_READ); + + if (backbuffer) + CopyTexture(InCmdList, &outputRealFfx, &backbufferFfx); + } + + return result; +} + +static NVSDK_NGX_Result GetCurrentSettingsCallback(Nvngx_Combo_Handle* InHandle, NVSDK_NGX_Parameter* InParameters) +{ + if (!InHandle || !InParameters) + return NVSDK_NGX_Result_FAIL_InvalidParameter; + + InParameters->Set("DLSSG.MustCallEval", 1); + InParameters->Set("DLSSG.BurstCaptureRunning", 0); + + return NVSDK_NGX_Result_Success; +} + +static NVSDK_NGX_Result EstimateVRAMCallback(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, + uint32_t, uint32_t, size_t* EstimatedSize) +{ + // Assume 300MB + if (EstimatedSize) + *EstimatedSize = 300 * 1024 * 1024; + + return NVSDK_NGX_Result_Success; +} + +NVSDK_NGX_Result Nvngx_Combo::D3D12_PopulateParameters_Impl(NVSDK_NGX_Parameter* InParameters) +{ + if (!InParameters) + return NVSDK_NGX_Result_FAIL_InvalidParameter; + + InParameters->Set("DLSSG.GetCurrentSettingsCallback", &GetCurrentSettingsCallback); + InParameters->Set("DLSSG.EstimateVRAMCallback", &EstimateVRAMCallback); + + // if (inited) // Query FFX + InParameters->Set("DLSSG.MultiFrameCountMax", 1); + + return NVSDK_NGX_Result_Success; +} diff --git a/OptiScaler/framegen/nvngx/Nvngx_Combo.h b/OptiScaler/framegen/nvngx/Nvngx_Combo.h new file mode 100644 index 00000000..f2c84be3 --- /dev/null +++ b/OptiScaler/framegen/nvngx/Nvngx_Combo.h @@ -0,0 +1,69 @@ +#pragma once +#include "IFGNvngx.h" +#include "Nvngx_FFX.h" +#include "Nvngx_Arturs.h" +#include + +struct Nvngx_Combo_Handle +{ + unsigned int Id; + + NVSDK_NGX_Handle* ffxHandle = nullptr; + NVSDK_NGX_Handle* artursHandle = nullptr; +}; + +class Nvngx_Combo : public IFGNvngx +{ + private: + std::atomic_uint32_t lastIdCreated = 0; + + std::unique_ptr artursProvider = nullptr; + std::unique_ptr ffxProvider = nullptr; + + public: + Nvngx_Combo() + { + artursProvider = std::make_unique(); + ffxProvider = std::make_unique(); + } + + bool isDx12Available() override final + { + return artursProvider->isDx12Available() && ffxProvider->isDx12Available(); + }; + bool isVulkanAvailable() override final { return false; }; + + // DX12 + NVSDK_NGX_Result D3D12_Init(unsigned long long InApplicationId, const wchar_t* InApplicationDataPath, + ID3D12Device* InDevice, const NVSDK_NGX_FeatureCommonInfo* InFeatureInfo, + NVSDK_NGX_Version InSDKVersion) override; + + NVSDK_NGX_Result D3D12_Init_Ext(unsigned long long InApplicationId, const wchar_t* InApplicationDataPath, + ID3D12Device* InDevice, NVSDK_NGX_Version InSDKVersion, + const NVSDK_NGX_FeatureCommonInfo* InFeatureInfo) override; + + NVSDK_NGX_Result D3D12_Shutdown() override; + + NVSDK_NGX_Result D3D12_Shutdown1(ID3D12Device* InDevice) override; + + NVSDK_NGX_Result D3D12_GetScratchBufferSize(NVSDK_NGX_Feature InFeatureId, const NVSDK_NGX_Parameter* InParameters, + size_t* OutSizeInBytes) override; + + NVSDK_NGX_Result D3D12_CreateFeature(ID3D12GraphicsCommandList* InCmdList, NVSDK_NGX_Feature InFeatureID, + NVSDK_NGX_Parameter* InParameters, NVSDK_NGX_Handle** OutHandle) override; + + NVSDK_NGX_Result D3D12_ReleaseFeature(NVSDK_NGX_Handle* InHandle) override; + + NVSDK_NGX_Result D3D12_GetFeatureRequirements(IDXGIAdapter* Adapter, + const NVSDK_NGX_FeatureDiscoveryInfo* FeatureDiscoveryInfo, + NVSDK_NGX_FeatureRequirement* OutSupported) override; + + NVSDK_NGX_Result D3D12_EvaluateFeature(ID3D12GraphicsCommandList* InCmdList, + const NVSDK_NGX_Handle* InFeatureHandle, NVSDK_NGX_Parameter* InParameters, + PFN_NVSDK_NGX_ProgressCallback InCallback) override; + + NVSDK_NGX_Result D3D12_PopulateParameters_Impl(NVSDK_NGX_Parameter* InParameters) override; + + int getMaxFakeFramesCount() override { return 5; } + FGNvngxReplacement getType() override { return FGNvngxReplacement::Combo; } +}; diff --git a/OptiScaler/framegen/nvngx/Nvngx_FG.cpp b/OptiScaler/framegen/nvngx/Nvngx_FG.cpp index 81d83bc6..8b70ff71 100644 --- a/OptiScaler/framegen/nvngx/Nvngx_FG.cpp +++ b/OptiScaler/framegen/nvngx/Nvngx_FG.cpp @@ -10,6 +10,7 @@ #include "Nvngx_Nukems.h" #include "Nvngx_Arturs.h" #include "Nvngx_FFX.h" +#include "Nvngx_Combo.h" #include IFGNvngx* Nvngx_FG::getProvider() @@ -33,6 +34,10 @@ IFGNvngx* Nvngx_FG::getProvider() _provider = std::make_unique(); break; + case FGNvngxReplacement::Combo: + _provider = std::make_unique(); + break; + case FGNvngxReplacement::None: default: return nullptr; @@ -41,6 +46,7 @@ IFGNvngx* Nvngx_FG::getProvider() if (!_provider->isDx12Available() && !_provider->isVulkanAvailable()) { // The selected provider cannot be used, try the remaining providers as fallback, try in order + // FGNvngxReplacement::Combo doesn't make sense to try as it's Arturs + FFX const FGNvngxReplacement fallbacks[] = { FGNvngxReplacement::Arturs, FGNvngxReplacement::FFX, @@ -57,6 +63,8 @@ IFGNvngx* Nvngx_FG::getProvider() return "Nvngx FFX"; case FGNvngxReplacement::Nukems: return "Nukems"; + case FGNvngxReplacement::Combo: + return "Combo"; case FGNvngxReplacement::None: default: return "???"; diff --git a/OptiScaler/menu/menu_common.cpp b/OptiScaler/menu/menu_common.cpp index 34b463ba..7c72cb84 100644 --- a/OptiScaler/menu/menu_common.cpp +++ b/OptiScaler/menu/menu_common.cpp @@ -1783,6 +1783,10 @@ void MenuCommon::RenderPerformanceOverlay(RenderMenuContext& ctx) { fgText = formatFg("FFX", Nvngx_FG::getMaxFakeFramesCount()); } + else if (activeNvngxFg == FGNvngxReplacement::Combo) + { + fgText = formatFg("Combo", Nvngx_FG::getMaxFakeFramesCount()); + } const auto overlayType = config->FpsOverlayType.value_or_default(); const bool hasFeature = currentFeature && !currentFeature->IsFrozen(); @@ -3107,6 +3111,8 @@ void MenuCommon::RenderFrameGenerationSelection(RenderMenuContext& ctx) { FGNvngxReplacement::Nukems, "Nukem's", "FSR 3 FG" }, { FGNvngxReplacement::Arturs, "Enabler", "FSR 3 MFG" }, { FGNvngxReplacement::FFX, "FSR 3/4 FG", "FSR 3/4 FG using the FFX" }, + { FGNvngxReplacement::Combo, "FFX + Enabler", "FFX for the middle fake frame, Enabler for the rest\n\n" + "2x - FFX\n3x - Enabler\n4x - FFX + Enabler\n5x - Enabler\n6x - FFX + Enabler" }, }; // clang-format on @@ -3144,6 +3150,11 @@ void MenuCommon::RenderFrameGenerationSelection(RenderMenuContext& ctx) nvngxOptions[fgNvngxFfxIndex].set_disabled(!FfxApiProxy::IsFGReady(false), "Missing amd_fidelityfx_framegeneration_dx12.dll"); + auto constexpr fgNvngxComboIndex = (uint32_t) FGNvngxReplacement::Combo; + nvngxOptions[fgNvngxComboIndex].set_disabled( + !FfxApiProxy::IsFGReady(false) || !state.artursFgFileAvailable, + "Missing amd_fidelityfx_framegeneration_dx12.dll\nor missing dlss-enabler-headless.dll"); + // TODO: Automatically switch to any other option if (!config->FGNvngxReplacement.has_value()) @@ -4383,6 +4394,12 @@ void MenuCommon::RenderFrameGenerationRuntimeSettings(RenderMenuContext& ctx) { SeparatorWithHelpMarker("Frame Generation (FSRFG via FFX)", "FFX using the DLSSG swapchain"); } + else if (activeNvngxFg == FGNvngxReplacement::Combo) + { + SeparatorWithHelpMarker("Frame Generation (Enabler + FFX)", + "FFX for middle fake frames, and Enabler for the rest\n\n2x - FFX\n" + "3x - Enabler\n4x - FFX + Enabler\n5x - Enabler\n6x - FFX + Enabler"); + } if (state.activeFgInput == FGInput::NvngxFG) { @@ -4448,7 +4465,7 @@ void MenuCommon::RenderFrameGenerationRuntimeSettings(RenderMenuContext& ctx) if (isLoaded) { - if (activeNvngxFg == FGNvngxReplacement::Arturs) + if (activeNvngxFg == FGNvngxReplacement::Arturs || activeNvngxFg == FGNvngxReplacement::Combo) { if (bool showDebug = config->NvngxFGShowDebug.value_or_default(); ImGui::Checkbox("Show Debug", &showDebug)) @@ -4503,7 +4520,8 @@ void MenuCommon::RenderFrameGenerationRuntimeSettings(RenderMenuContext& ctx) config->NvngxFGDispatchFlags = temp_flags; } } - else if (activeNvngxFg == FGNvngxReplacement::Nukems) + + if (activeNvngxFg == FGNvngxReplacement::Nukems) { if (ImGui::Checkbox("Enable Debug View", &state.dlssgDebugView)) { @@ -4514,7 +4532,8 @@ void MenuCommon::RenderFrameGenerationRuntimeSettings(RenderMenuContext& ctx) Nvngx_FG::setInterpolatedOnly(state.dlssgInterpolatedOnly); } } - else if (activeNvngxFg == FGNvngxReplacement::FFX) + + if (activeNvngxFg == FGNvngxReplacement::FFX || activeNvngxFg == FGNvngxReplacement::Combo) { if (_ffxFGIndex < 0) _ffxFGIndex = config->FfxFGIndex.value_or_default();