API 适配NVNGX 函数拦截与决策逻辑本文详解 upscalerBridge 如何拦截 20 个 NGX D3D12 API 函数以及每个函数的转发 vs 内部处理决策逻辑。一、为什么要自己实现整套 NGX API游戏通过GetProcAddress(nvngx, NVSDK_NGX_D3D12_CreateFeature)获取 NGX 函数指针。如果我们的 DLL 没有导出这些函数游戏会绕过我们直接调用真正的nvngx.dll。因此我们必须完整导出游戏可能调用的所有 NGX 函数并在每个函数内部决定这个调用应该转发给真正的 nvngx.dll还是由我们拦截处理。[inputs/NVNGX_DLSS_Dx12.cpp](file:///e:/Projects/Repositories/upscalerBridge/upscalerBridge/inputs/NVNGX_DLSS_Dx12.cpp)二、20 个被拦截函数的完整清单初始化3 个函数行号说明NVSDK_NGX_D3D12_Init_Ext146带扩展参数的初始化。所有 Init 变体最终汇聚于此NVSDK_NGX_D3D12_Init225旧版 API用ScopedInitDx12防递归后委托给 Init_ExtNVSDK_NGX_D3D12_Init_ProjectID282按 ProjectID 初始化保存项目信息后委托给 Init_Ext关闭2 个函数行号说明NVSDK_NGX_D3D12_Shutdown372清理所有 DLSS feature、FG contextNVSDK_NGX_D3D12_Shutdown1422带设备的变体最终委托给 ShutdownFeature 生命周期3 个★ 核心函数行号说明NVSDK_NGX_D3D12_CreateFeature745创建超分/FrameGen 实例NVSDK_NGX_D3D12_EvaluateFeature1099每帧执行超分最高频调用NVSDK_NGX_D3D12_ReleaseFeature820释放实例参数管理5 个函数行号说明NVSDK_NGX_D3D12_GetParameters464获取 SDK 管理的持久参数表NVSDK_NGX_D3D12_GetCapabilityParameters504分配预填充能力信息的参数表NVSDK_NGX_D3D12_AllocateParameters543分配空白参数表NVSDK_NGX_D3D12_DestroyParameters590按 AllocType 决定释放方式NVSDK_NGX_D3D12_PopulateParameters_Impl568填充参数表查询2 个函数行号说明NVSDK_NGX_D3D12_GetFeatureRequirements904欺骗核心查询硬件是否支持某 FeatureNVSDK_NGX_D3D12_GetScratchBufferSize1198返回临时缓冲大小固定 50MB通用 NGX1 个在 NVNGX.cpp 中函数行号说明NVSDK_NGX_UpdateFeature87运行时更新 ApplicationId/ProjectId三、核心决策维度两个判断依据所有函数的转发 vs 内部处理决策都基于两个条件条件 1Feature 类型CreateFeature / GetFeatureRequirementsFeature SuperSampling → 内部处理我们替换为 FSR/DLSS Feature RayReconstruction → 内部处理 Feature FrameGeneration → 看 FGInput 配置 其他 FeatureISP、Reserved → 转发给真正的 nvngx.dll为什么只拦截这两个 FeatureNGX 支持多种 Feature——ImageSignalProcessing、Reserved1 等。我们只替换超分和光线重建其余功能让真正的 nvngx 处理。条件 2Handle ID 范围EvaluateFeature / ReleaseFeatureHandle ID 1000000 (DLSS_MOD_ID_OFFSET) → 转发真正的 nvngx 创建的 Handle Handle ID 1000000 → 内部处理我们创建的 Handle为什么需要两个维度同一个进程可能同时有真正的 DLSS-D和我们替换的 DLSS-SR。用 Handle ID 而非 Feature ID可以精确区分每个具体实例。四、逐函数决策逻辑详解4.1 Init_Ext唯一两边都执行的函数[第 146-223 行](file:///e:/Projects/Repositories/upscalerBridge/upscalerBridge/inputs/NVNGX_DLSS_Dx12.cpp#L146)NVSDK_NGX_Result NVSDK_NGX_D3D12_Init_Ext( unsigned long long InApplicationId, const wchar_t* InApplicationDataPath, ID3D12Device* InDevice, NVSDK_NGX_Version InSDKVersion, const NVSDK_NGX_FeatureCommonInfo* InFeatureInfo) { // ① 保存应用信息到 State 单例 State::Instance().NVNGX_ApplicationId InApplicationId; State::Instance().NVNGX_Version InSDKVersion; // ② 如果有真正的 nvngx.dll → 转发 Init if (Config::Instance()-DLSSEnabled.value_or_default() !_skipInit) { if (NVNGXProxy::NVNGXModule() nullptr) NVNGXProxy::InitNVNGX(); // 惰性加载 nvngx.dll if (NVNGXProxy::NVNGXModule() ! nullptr) { auto result NVNGXProxy::D3D12_Init_Ext()(InApplicationId, ...); if (result Success) NVNGXProxy::SetDx12Inited(true); // 标记 D3D12 已初始化 } } // ③ 执行我们自己的初始化无论步骤②是否执行 D3D12Device InDevice; D3D12Hooks::HookDevice(InDevice); // Hook ID3D12Device 的 vtable UpscalerTimeDx12::Init(InDevice); // GPU 计时器初始化 State::Instance().nvngxDx12Inited true; return NVSDK_NGX_Result_Success; }为什么是两边都执行而不是二选一有些游戏在 Init 阶段需要真正的 nvngx.dll 返回能力信息如支持的 Feature 列表、驱动版本要求而这些信息我们无法完全模拟。所以我们在转发 Init 的同时也必须执行自己的初始化Hook D3D12 设备、启动计时器。注意_skipInit标志——当其他 Init 变体如Init、Init_ProjectID委托给Init_Ext时会设置此标志防止重复执行。4.2 CreateFeature按 Feature 类型分流[第 745-818 行](file:///e:/Projects/Repositories/upscalerBridge/upscalerBridge/inputs/NVNGX_DLSS_Dx12.cpp#L745)CreateFeature(InCmdList, InFeatureID, InParameters, OutHandle) │ ├─ InFeatureID SuperSampling │ ├─ YES → [内部] TryCreateOptiFeature() │ │ ├─ GetUpscalerBackend() # FFX or DLSS │ │ ├─ FeatureProvider::GetFeature() # 创建 FSR2FeatureDx12 or DLSSFeatureDx12 │ │ ├─ feature-Init() # 初始化管道 │ │ └─ Dx12Contexts[handleId] feature # 存入 Context Map │ │ │ └─ NO → InFeatureID RayReconstruction │ ├─ YES → [内部] TryCreateOptiFeature() │ └─ NO → [转发] NVNGXProxy::D3D12_CreateFeature()() │ └─ 调用真正的 nvngx.dll为什么 FrameGeneration 不走内部路径帧生成DLSS-G是独立 Feature。我们可以在菜单中配置用 NVIDIA 原生帧生成还是 FSR 帧生成。这通过State::activeFgInput来控制而不是在 CreateFeature 中拦截。4.3 EvaluateFeature按 Handle ID 分流[第 1099-1192 行](file:///e:/Projects/Repositories/upscalerBridge/upscalerBridge/inputs/NVNGX_DLSS_Dx12.cpp#L1099)NVSDK_NGX_Result NVSDK_NGX_D3D12_EvaluateFeature( ID3D12GraphicsCommandList* InCmdList, const NVSDK_NGX_Handle* InFeatureHandle, NVSDK_NGX_Parameter* InParameters, ...) { const uint32_t handleId InFeatureHandle-Id; // ★ 核心判断这个 Handle 是谁创建的 if (handleId DLSS_MOD_ID_OFFSET) // 1000000 { // 真正 nvngx.dll 创建的 → 透明转发 return NVNGXProxy::D3D12_EvaluateFeature()(InCmdList, ...); } // 我们创建的 → TryEvaluateOptiFeature() // → Dx12Contexts[handleId] 查找 IFeature_Dx12 // → feature-Evaluate() → FSR/DLSS 超分 return TryEvaluateOptiFeature(InCmdList, InFeatureHandle, ...); }为什么用 Handle ID 而不是 Feature ID假设一个游戏同时创建了两个 FeatureHandle A (Id45)真正的 DLSS-DRay Reconstruction由 nvngx.dll 创建Handle B (Id1000001)我们替换的 DLSS-SRSuper Resolution由 upscalerBridge 创建两者都是DLSS Feature但 Evaluate 时只能用 Handle 区分。用 Feature ID 无法区分谁是谁。4.4 ReleaseFeature对称的清理逻辑[第 820-896 行](file:///e:/Projects/Repositories/upscalerBridge/upscalerBridge/inputs/NVNGX_DLSS_Dx12.cpp#L820)NVSDK_NGX_Result NVSDK_NGX_D3D12_ReleaseFeature(NVSDK_NGX_Handle* InHandle) { auto handleId InHandle-Id; if (handleId DLSS_MOD_ID_OFFSET) { // 真正的 nvngx 创建的 → 转发释放 return NVNGXProxy::D3D12_ReleaseFeature()(InHandle); } // 我们创建的 → 从 Dx12Contexts 移除 if (auto it Dx12Contexts.find(handleId); it ! Dx12Contexts.end()) { if (it-second.feature.get() State::Instance().currentFeature) State::Instance().currentFeature nullptr; Dx12Contexts.erase(it); // unique_ptr 自动析构 } return NVSDK_NGX_Result_Success; }对称性Release 的决策与 Create 完全对称——Handle ID 1000000 转发 1000000 内部清理。4.5 GetFeatureRequirements欺骗的核心[第 904-957 行](file:///e:/Projects/Repositories/upscalerBridge/upscalerBridge/inputs/NVNGX_DLSS_Dx12.cpp#L904)NVSDK_NGX_Result NVSDK_NGX_D3D12_GetFeatureRequirements( IDXGIAdapter* Adapter, const NVSDK_NGX_FeatureDiscoveryInfo* FeatureDiscoveryInfo, NVSDK_NGX_FeatureRequirement* OutSupported) { // ★ 超分 → 永远返回 Supported无论什么显卡 if (FeatureDiscoveryInfo-FeatureID NVSDK_NGX_Feature_SuperSampling) { OutSupported-FeatureSupported NVSDK_NGX_FeatureSupportResult_Supported; OutSupported-MinHWArchitecture 0; // 无硬件限制 strcpy_s(OutSupported-MinOSVersion, 10.0.10240.16384); return NVSDK_NGX_Result_Success; } // 其他 Feature → DLSS 可用时转发 if (Config::Instance()-DLSSEnabled gpu.dlssCapable) return NVNGXProxy::D3D12_GetFeatureRequirements()(...); // DLSS 不可用 → 返回不支持 OutSupported-FeatureSupported AdapterUnsupported; return NVSDK_NGX_Result_FAIL_FeatureNotSupported; }这是整个适配逻辑的核心AMD 显卡上游戏询问DLSS 支持吗“我们回答支持”。游戏放心地创建 DLSS Feature我们给一个 FSR 实例。4.6 参数管理函数函数决策逻辑GetParametersDLSS 可用 → 转发拿到真实参数后复制 upscalerBridge 配置不可用 → 返回自定义参数GetCapabilityParametersDLSS 可用 → 转发成功后初始化不可用 → 新建 InternDynamic 参数AllocateParametersDLSS 可用 → 转发NVDynamic不可用 → 新建 InternDynamic 参数DestroyParameters读取 AllocType 标记NVDynamic → 转发 NGX DestroyInternDynamic → deletePopulateParameters_Impl总是内部处理参数表填充4.7 ScratchBuffer 和 UpdateFeatureGetScratchBufferSize总是返回固定值5242880050MB不转发UpdateFeature总是返回Success只更新内部的 AppId/ProjectId五、Handle 机制多实例隔离的核心5.1 Handle ID 分配与分界// CreateFeature 中分配 Handle const uint32_t handleId IFeature::GetNextHandleId(); // 原子递增 *OutHandle new NVSDK_NGX_Handle { handleId };Handle ID 1000000 → nvngx.dll 原生 Handle透明转发 Handle ID 1000000 → upscalerBridge Handle内部处理GetNextHandleId()使用std::atomicuint32_t保证线程安全每个新 Feature 获得唯一的递增 ID。5.2 Context Map// 全局映射Handle ID → 超分实例 ankerl::unordered_dense::mapUINT, ContextData Dx12Contexts; struct ContextData { unique_ptrIFeature_Dx12 feature; // FSR2FeatureDx12 或 DLSSFeatureDx12 };游戏持有NVSDK_NGX_Handle*只包含一个uint32_t Id我们在 O(1) 的 hash map 中查找。游戏无法直接访问IFeature_Dx12Handle 对游戏来说是一个不透明的整数。5.3 后端热切换的透明性用户可以随时在菜单中切换超分后端DLSS → FSR。热切换时Dx12Contexts[handleId]中的旧IFeature_Dx12被销毁新后端创建的IFeature_Dx12放入同一个 slotHandle ID 不变游戏无感知详见 Overlay 模式博客 第四章。六、部分决策速查表#函数行号转发条件内部处理条件1Init_Ext146DLSS 可用总是执行两边都走2Init225同 Init_Ext防递归后委托 Init_Ext3Init_ProjectID282同 Init_Ext委托 Init_Ext4Shutdown372DLSS 可用总是执行清理5GetParameters464DLSS 可用不可用时返回自定义参数6GetCapabilityParameters504DLSS 可用创建 InternDynamic7AllocateParameters543DLSS 可用创建 InternDynamic8DestroyParameters590AllocTypeNVDynamicAllocTypeInternDynamic9CreateFeature745Feature≠SR/RRFeatureSR/RR10EvaluateFeature1099HandleId1000000HandleId≥100000011ReleaseFeature820HandleId1000000HandleId≥100000012GetFeatureRequirements904Feature≠SRSR 强制 Supported七、设计原则原则说明全量拦截导出游戏可能调用的所有 NGX 函数不漏一个条件转发只拦截 SuperSampling/RayReconstruction其余透明转发Handle 分区ID 分界 Context Map多实例互不干扰热切换兼容Handle 不变只换 Context Map 中的实例对称为王Create/Release 决策完全对称优雅降级DLSS 不可用时走 FSR不返回错误