From 10b3d652282c194936905064d7eb265ecb7db75c Mon Sep 17 00:00:00 2001 From: FakeMichau <49685661+FakeMichau@users.noreply.github.com> Date: Tue, 11 Aug 2026 20:43:45 +0200 Subject: [PATCH] Add version selection to Nvngx FFX output --- OptiScaler/framegen/nvngx/IFGNvngx.h | 5 ++ OptiScaler/framegen/nvngx/Nvngx_FFX.cpp | 68 +++++++++++++++--- OptiScaler/framegen/nvngx/Nvngx_FG.cpp | 10 +++ OptiScaler/framegen/nvngx/Nvngx_FG.h | 1 + OptiScaler/menu/menu_common.cpp | 96 ++++++++++++++++++++++--- 5 files changed, 163 insertions(+), 17 deletions(-) diff --git a/OptiScaler/framegen/nvngx/IFGNvngx.h b/OptiScaler/framegen/nvngx/IFGNvngx.h index b2c74bdc..10886214 100644 --- a/OptiScaler/framegen/nvngx/IFGNvngx.h +++ b/OptiScaler/framegen/nvngx/IFGNvngx.h @@ -10,7 +10,12 @@ constexpr unsigned int NVNGX_PROVIDER_ID_OFFSET = 2000000; class IFGNvngx { + protected: + feature_version _version { 0, 0, 0 }; + public: + feature_version version() const { return _version; }; + virtual ~IFGNvngx() = default; virtual int getMaxFakeFramesCount(API api) = 0; diff --git a/OptiScaler/framegen/nvngx/Nvngx_FFX.cpp b/OptiScaler/framegen/nvngx/Nvngx_FFX.cpp index 19145d00..6088aa68 100644 --- a/OptiScaler/framegen/nvngx/Nvngx_FFX.cpp +++ b/OptiScaler/framegen/nvngx/Nvngx_FFX.cpp @@ -331,8 +331,36 @@ NVSDK_NGX_Result Nvngx_FFX::D3D12_EvaluateFeature(ID3D12GraphicsCommandList* InC backendDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_DX12; backendDesc.device = InOurHandle->device; + if (State::Instance().fgChanged && InOurHandle->fgContext) + { + auto retCode = FfxApiProxy::D3D12_DestroyContext(&InOurHandle->fgContext, nullptr); + + if (retCode == FFX_API_RETURN_OK) + InOurHandle->fgContext = nullptr; + else + LOG_WARN("Could destroy FFX context"); + } + + State::Instance().fgChanged = false; + if (InOurHandle->fgContext == nullptr) { + ffxQueryDescGetVersions versionQuery {}; + versionQuery.header.type = FFX_API_QUERY_DESC_TYPE_GET_VERSIONS; + versionQuery.createDescType = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION; + versionQuery.device = InOurHandle->device; + uint64_t versionCount = 0; + versionQuery.outputCount = &versionCount; + // get number of versions for allocation + FfxApiProxy::D3D12_Query(nullptr, &versionQuery.header); + + State::Instance().ffxFGVersionIds.resize(versionCount); + State::Instance().ffxFGVersionNames.resize(versionCount); + versionQuery.versionIds = State::Instance().ffxFGVersionIds.data(); + versionQuery.versionNames = State::Instance().ffxFGVersionNames.data(); + // fill version ids and names arrays. + FfxApiProxy::D3D12_Query(nullptr, &versionQuery.header); + ffxCreateContextDescFrameGeneration createFg {}; createFg.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION; @@ -369,8 +397,8 @@ NVSDK_NGX_Result Nvngx_FFX::D3D12_EvaluateFeature(ID3D12GraphicsCommandList* InC createFg.flags |= FFX_FRAMEGENERATION_ENABLE_DISPLAY_RESOLUTION_MOTION_VECTORS; } - // if (fgConstants.flags & FG_Flags::Async) - createFg.flags |= FFX_FRAMEGENERATION_ENABLE_ASYNC_WORKLOAD_SUPPORT; + if (Config::Instance()->FGAsync.value_or_default()) + createFg.flags |= FFX_FRAMEGENERATION_ENABLE_ASYNC_WORKLOAD_SUPPORT; if (depthPlaneInfinite) createFg.flags |= FFX_FRAMEGENERATION_ENABLE_DEPTH_INFINITE; @@ -383,12 +411,32 @@ NVSDK_NGX_Result Nvngx_FFX::D3D12_EvaluateFeature(ID3D12GraphicsCommandList* InC // TODO: add code for hudless with different formats - ffxReturnCode_t retCode = FfxApiProxy::D3D12_CreateContext(&InOurHandle->fgContext, &createFg.header, nullptr); - - if (retCode != FFX_API_RETURN_OK) { - LOG_ERROR("Failed to create FFX context"); - return NVSDK_NGX_Result_Fail; + ScopedSkipSpoofing skipSpoofing {}; + ScopedSkipHeapCapture skipHeapCapture {}; + + // Currently 0 is non-ML FG and 1 is ML FG + if (Config::Instance()->FfxFGIndex.value_or_default() < 0 || + Config::Instance()->FfxFGIndex.value_or_default() >= State::Instance().ffxFGVersionIds.size()) + Config::Instance()->FfxFGIndex.set_volatile_value(0); + + ffxOverrideVersion override = { 0 }; + override.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION; + override.versionId = State::Instance().ffxFGVersionIds[Config::Instance()->FfxFGIndex.value_or_default()]; + + backendDesc.header.pNext = &override.header; + + _version.parse_version( + State::Instance().ffxFGVersionNames[Config::Instance()->FfxFGIndex.value_or_default()]); + + ffxReturnCode_t retCode = + FfxApiProxy::D3D12_CreateContext(&InOurHandle->fgContext, &createFg.header, nullptr); + + if (retCode != FFX_API_RETURN_OK) + { + LOG_ERROR("Failed to create FFX context"); + return NVSDK_NGX_Result_Fail; + } } } @@ -405,8 +453,12 @@ NVSDK_NGX_Result Nvngx_FFX::D3D12_EvaluateFeature(ID3D12GraphicsCommandList* InC ffxConfigureDescFrameGeneration configureDesc {}; configureDesc.header.type = FFX_API_CONFIGURE_DESC_TYPE_FRAMEGENERATION; configureDesc.frameGenerationEnabled = true; - // configureDesc.allowAsyncWorkloads = true; // TODO: can crash + configureDesc.allowAsyncWorkloads = Config::Instance()->FGAsync.value_or_default(); configureDesc.flags = FFX_FRAMEGENERATION_FLAG_NO_SWAPCHAIN_CONTEXT_NOTIFY; + + if (Config::Instance()->FGDebugView.value_or_default()) + configureDesc.flags |= FFX_FRAMEGENERATION_FLAG_DRAW_DEBUG_VIEW; + configureDesc.frameID = InOurHandle->lastFrameId; configureDesc.generationRect = { 0, 0, (int) InOurHandle->swapchainWidth, (int) InOurHandle->swapchainHeight }; diff --git a/OptiScaler/framegen/nvngx/Nvngx_FG.cpp b/OptiScaler/framegen/nvngx/Nvngx_FG.cpp index a4f5d177..0299374f 100644 --- a/OptiScaler/framegen/nvngx/Nvngx_FG.cpp +++ b/OptiScaler/framegen/nvngx/Nvngx_FG.cpp @@ -130,6 +130,16 @@ bool Nvngx_FG::isVulkanAvailable() return provider->isVulkanAvailable(); } +feature_version Nvngx_FG::version() +{ + auto* provider = getProvider(); + + if (!provider) + return {}; + + return provider->version(); +} + void Nvngx_FG::setDebugView(bool enabled) { auto* provider = getProvider(); diff --git a/OptiScaler/framegen/nvngx/Nvngx_FG.h b/OptiScaler/framegen/nvngx/Nvngx_FG.h index 2aab92eb..24b8c0cb 100644 --- a/OptiScaler/framegen/nvngx/Nvngx_FG.h +++ b/OptiScaler/framegen/nvngx/Nvngx_FG.h @@ -19,6 +19,7 @@ class Nvngx_FG static int getMaxFakeFramesCount(API api); static bool isDx12Available(); static bool isVulkanAvailable(); + static feature_version version(); // TODO: nukem-specific, unify static void setDebugView(bool enabled); diff --git a/OptiScaler/menu/menu_common.cpp b/OptiScaler/menu/menu_common.cpp index d04370ac..13c53d0f 100644 --- a/OptiScaler/menu/menu_common.cpp +++ b/OptiScaler/menu/menu_common.cpp @@ -3571,7 +3571,7 @@ void MenuCommon::RenderFrameGenerationRuntimeSettings(RenderMenuContext& ctx) ImGui::SameLine(0.0f, 16.0f); - if (state.currentFG->Version().major > 3) + if (state.currentFG && state.currentFG->Version().major > 3) { if (bool fgwm = config->FSRFGEnableWatermark.value_or_default(); ImGui::Checkbox("Enable Watermark", &fgwm)) @@ -4438,13 +4438,6 @@ void MenuCommon::RenderFrameGenerationRuntimeSettings(RenderMenuContext& ctx) if (isLoaded) { - if (bool disableHudless = config->NvngxFGDisableHudless.value_or_default(); - ImGui::Checkbox("Disable HUDless", &disableHudless)) - { - config->NvngxFGDisableHudless = disableHudless; - } - ShowHelpMarker("Might be required for some sets of DispatchFlags"); - if (activeNvngxFg == FGNvngxReplacement::Arturs) { if (bool showDebug = config->NvngxFGShowDebug.value_or_default(); @@ -4513,8 +4506,93 @@ void MenuCommon::RenderFrameGenerationRuntimeSettings(RenderMenuContext& ctx) } else if (activeNvngxFg == FGNvngxReplacement::FFX) { - // TODO: FSR version selection + if (_ffxFGIndex < 0) + _ffxFGIndex = config->FfxFGIndex.value_or_default(); + + if (state.ffxFGVersionNames.size() > 0) + { + ImGui::PushItemWidth(135.0f * menuResScale); + + auto currentName = StrFmt("FSR %s", state.ffxFGVersionNames[_ffxFGIndex]); + if (ImGui::BeginCombo("FFX FG", currentName.c_str())) + { + for (int n = 0; n < state.ffxFGVersionIds.size(); n++) + { + auto name = StrFmt("FSR %s", state.ffxFGVersionNames[n]); + if (ImGui::Selectable(name.c_str(), config->FfxFGIndex.value_or_default() == n)) + _ffxFGIndex = n; + } + + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + + ShowHelpMarker("List of FGs reported by FFX SDK"); + + ImGui::SameLine(0.0f, 6.0f); + + if (ImGui::Button("Change FG") && _ffxFGIndex != config->FfxFGIndex.value_or_default()) + { + config->FfxFGIndex = _ffxFGIndex; + state.fgChanged = true; + } + } + + bool fgAsync = config->FGAsync.value_or_default(); + if (ImGui::Checkbox("Allow Async##2", &fgAsync)) + { + config->FGAsync = fgAsync; + + if (config->FGEnabled.value_or_default()) + { + state.fgChanged = true; + LOG_DEBUG("Async set FGChanged"); + } + } + ShowHelpMarker("Enable Async for better FG performance\nMight cause crashes, especially with HUD Fix!"); + + ImGui::SameLine(0.0f, 20.0f * menuResScale); + bool fgDV = config->FGDebugView.value_or_default(); + if (ImGui::Checkbox("Debug View##3", &fgDV)) + { + config->FGDebugView = fgDV; + + if (config->FGEnabled.value_or_default()) + { + state.fgChanged = true; + LOG_DEBUG("DebugView set FGChanged"); + } + } + ShowHelpMarker("Enable FSR3.1-FG Debug view\n\n" + "Top left: Game Motion Vectors\n" + "Top middle: GMV Depth\n" + "Top right: Optical Flow MV\n" + "Middle: Interpolated frame only\n" + "Bottom left: Disocclusion mask\n" + "Bottom middle: Interpolation source (w/o UI)\n" + "Bottom right: HUDless resource"); + + if (Nvngx_FG::version().major > 3) + { + ImGui::SameLine(0.0f, 20.0f * menuResScale); + if (bool fgwm = config->FSRFGEnableWatermark.value_or_default(); + ImGui::Checkbox("Enable Watermark", &fgwm)) + { + LOG_DEBUG("FSRFGEnableWatermark set FGWatermark: {}", fgwm); + config->FSRFGEnableWatermark = fgwm; + } + + ShowHelpMarker("After changing this option, please Save Settings\n" + "It will be applied on next launch."); + } } + + if (bool disableHudless = config->NvngxFGDisableHudless.value_or_default(); + ImGui::Checkbox("Disable HUDless", &disableHudless)) + { + config->NvngxFGDisableHudless = disableHudless; + } + ShowHelpMarker("Might be required for some sets of DispatchFlags"); } }