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();