Skip to content

Commit 19e934e

Browse files
committed
fix(framegen): close only attempted provider initialization
1 parent 6f5597a commit 19e934e

8 files changed

Lines changed: 234 additions & 12 deletions

‎.github/workflows/just_build_no_signature.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ jobs:
133133
& $env:ComSpec /d /c $command
134134
if ($LASTEXITCODE) { throw "Compatibility guard test failed: $name" }
135135
}
136-
foreach ($script in @('test_shutdown_routing.py','test_provider_publication_routes.py','test_provider_dll_boundary.py','test_combo_release.py','test_native_operation_routes.py','test_native_module_publication.py','test_native_init_scopes.py','test_ngx_init_paths.py','test_ngx_init_metadata.py','test_provider_release_admission.py','test_provider_call_admission.py')) {
136+
foreach ($script in @('test_shutdown_routing.py','test_provider_publication_routes.py','test_provider_dll_boundary.py','test_combo_release.py','test_native_operation_routes.py','test_native_module_publication.py','test_native_init_scopes.py','test_ngx_init_paths.py','test_ngx_init_metadata.py','test_provider_release_admission.py','test_provider_call_admission.py','test_provider_init_footprint.py')) {
137137
$command = 'call "' + $vcvars + '" && python tests\' + $script + ' --compiler cl'
138138
& $env:ComSpec /d /c $command
139139
if ($LASTEXITCODE) { throw "Compatibility routing test failed: $script" }

‎OptiScaler/framegen/nvngx/Nvngx_FG.cpp‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,9 @@ NVSDK_NGX_Result Nvngx_FG::D3D12_Init(unsigned long long InApplicationId, const
234234
if (!lease)
235235
return NVSDK_NGX_Result_FAIL_NotInitialized;
236236

237+
if (!InDevice)
238+
return NVSDK_NGX_Result_FAIL_InvalidParameter;
239+
237240
const auto lookup = lookupProvider();
238241
if (lookup.status == ProviderStatus::Pending)
239242
return NVSDK_NGX_Result_FAIL_NotInitialized;
@@ -242,6 +245,7 @@ NVSDK_NGX_Result Nvngx_FG::D3D12_Init(unsigned long long InApplicationId, const
242245
if (!provider)
243246
return NVSDK_NGX_Result_Fail;
244247

248+
_dx12InitAttempts.insert(InDevice);
245249
return provider->D3D12_Init(InApplicationId, InApplicationDataPath, InDevice, InFeatureInfo, InSDKVersion);
246250
}
247251

@@ -253,6 +257,9 @@ NVSDK_NGX_Result Nvngx_FG::D3D12_Init_Ext(unsigned long long InApplicationId, co
253257
if (!lease)
254258
return NVSDK_NGX_Result_FAIL_NotInitialized;
255259

260+
if (!InDevice)
261+
return NVSDK_NGX_Result_FAIL_InvalidParameter;
262+
256263
const auto lookup = lookupProvider();
257264
if (lookup.status == ProviderStatus::Pending)
258265
return NVSDK_NGX_Result_FAIL_NotInitialized;
@@ -261,6 +268,7 @@ NVSDK_NGX_Result Nvngx_FG::D3D12_Init_Ext(unsigned long long InApplicationId, co
261268
if (!provider)
262269
return NVSDK_NGX_Result_Fail;
263270

271+
_dx12InitAttempts.insert(InDevice);
264272
return provider->D3D12_Init_Ext(InApplicationId, InApplicationDataPath, InDevice, InSDKVersion, InFeatureInfo);
265273
}
266274

@@ -447,6 +455,9 @@ NVSDK_NGX_Result Nvngx_FG::VULKAN_Init(unsigned long long InApplicationId, const
447455
if (!lease)
448456
return NVSDK_NGX_Result_FAIL_NotInitialized;
449457

458+
if (!InDevice)
459+
return NVSDK_NGX_Result_FAIL_InvalidParameter;
460+
450461
const auto lookup = lookupProvider();
451462
if (lookup.status == ProviderStatus::Pending)
452463
return NVSDK_NGX_Result_FAIL_NotInitialized;
@@ -455,6 +466,7 @@ NVSDK_NGX_Result Nvngx_FG::VULKAN_Init(unsigned long long InApplicationId, const
455466
if (!provider)
456467
return NVSDK_NGX_Result_Fail;
457468

469+
_vulkanInitAttempts.insert(InDevice);
458470
return provider->VULKAN_Init(InApplicationId, InApplicationDataPath, InInstance, InPD, InDevice, InGIPA, InGDPA,
459471
InFeatureInfo, InSDKVersion);
460472
}
@@ -468,6 +480,9 @@ NVSDK_NGX_Result Nvngx_FG::VULKAN_Init_Ext(unsigned long long InApplicationId, c
468480
if (!lease)
469481
return NVSDK_NGX_Result_FAIL_NotInitialized;
470482

483+
if (!InDevice)
484+
return NVSDK_NGX_Result_FAIL_InvalidParameter;
485+
471486
const auto lookup = lookupProvider();
472487
if (lookup.status == ProviderStatus::Pending)
473488
return NVSDK_NGX_Result_FAIL_NotInitialized;
@@ -476,6 +491,7 @@ NVSDK_NGX_Result Nvngx_FG::VULKAN_Init_Ext(unsigned long long InApplicationId, c
476491
if (!provider)
477492
return NVSDK_NGX_Result_Fail;
478493

494+
_vulkanInitAttempts.insert(InDevice);
479495
return provider->VULKAN_Init_Ext(InApplicationId, InApplicationDataPath, InInstance, InPD, InDevice, InSDKVersion,
480496
InFeatureInfo);
481497
}
@@ -490,6 +506,9 @@ NVSDK_NGX_Result Nvngx_FG::VULKAN_Init_Ext2(unsigned long long InApplicationId,
490506
if (!lease)
491507
return NVSDK_NGX_Result_FAIL_NotInitialized;
492508

509+
if (!InDevice)
510+
return NVSDK_NGX_Result_FAIL_InvalidParameter;
511+
493512
const auto lookup = lookupProvider();
494513
if (lookup.status == ProviderStatus::Pending)
495514
return NVSDK_NGX_Result_FAIL_NotInitialized;
@@ -498,6 +517,7 @@ NVSDK_NGX_Result Nvngx_FG::VULKAN_Init_Ext2(unsigned long long InApplicationId,
498517
if (!provider)
499518
return NVSDK_NGX_Result_Fail;
500519

520+
_vulkanInitAttempts.insert(InDevice);
501521
return provider->VULKAN_Init_Ext2(InApplicationId, InApplicationDataPath, InInstance, InPD, InDevice, InGIPA,
502522
InGDPA, InSDKVersion, InFeatureInfo);
503523
}

‎OptiScaler/framegen/nvngx/Nvngx_FG.h‎

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <framegen/ProviderHandleRegistry.h>
1010
#include <framegen/ProviderPublication.h>
1111
#include <framegen/ProviderCallAdmission.h>
12+
#include <unordered_set>
1213

1314
class Nvngx_FG
1415
{
@@ -25,6 +26,10 @@ class Nvngx_FG
2526
static inline ProviderHandleRegistry<Nvngx_FG_Handle> _handles;
2627
static inline ProviderPublication<IFGNvngx> _provider;
2728
static inline ProviderCallAdmission _calls;
29+
// Accessed only with transition admission. An attempted Init may have
30+
// partially initialized a provider even when it reports failure.
31+
static inline std::unordered_set<ID3D12Device*> _dx12InitAttempts;
32+
static inline std::unordered_set<VkDevice> _vulkanInitAttempts;
2833
static inline std::unique_ptr<HudCopy_Dx12> _hudCopy;
2934

3035
static std::unique_ptr<IFGNvngx> createProvider();
@@ -42,10 +47,20 @@ class Nvngx_FG
4247
return std::forward<Callback>(callback)(
4348
[](ID3D12Device* device)
4449
{
45-
auto* provider = _provider.Peek();
46-
if (!provider || !provider->isDx12Available())
50+
if (device ? !_dx12InitAttempts.contains(device) : _dx12InitAttempts.empty())
4751
return NVSDK_NGX_Result_Success;
48-
return device ? provider->D3D12_Shutdown1(device) : provider->D3D12_Shutdown();
52+
auto* provider = _provider.Peek();
53+
const auto result = !provider || !provider->isDx12Available() ? NVSDK_NGX_Result_Success
54+
: device ? provider->D3D12_Shutdown1(device)
55+
: provider->D3D12_Shutdown();
56+
if (result == NVSDK_NGX_Result_Success)
57+
{
58+
if (device)
59+
_dx12InitAttempts.erase(device);
60+
else
61+
_dx12InitAttempts.clear();
62+
}
63+
return result;
4964
});
5065
}
5166

@@ -57,10 +72,20 @@ class Nvngx_FG
5772
return std::forward<Callback>(callback)(
5873
[](VkDevice device)
5974
{
60-
auto* provider = _provider.Peek();
61-
if (!provider || !provider->isVulkanAvailable())
75+
if (device ? !_vulkanInitAttempts.contains(device) : _vulkanInitAttempts.empty())
6276
return NVSDK_NGX_Result_Success;
63-
return device ? provider->VULKAN_Shutdown1(device) : provider->VULKAN_Shutdown();
77+
auto* provider = _provider.Peek();
78+
const auto result = !provider || !provider->isVulkanAvailable() ? NVSDK_NGX_Result_Success
79+
: device ? provider->VULKAN_Shutdown1(device)
80+
: provider->VULKAN_Shutdown();
81+
if (result == NVSDK_NGX_Result_Success)
82+
{
83+
if (device)
84+
_vulkanInitAttempts.erase(device);
85+
else
86+
_vulkanInitAttempts.clear();
87+
}
88+
return result;
6489
});
6590
}
6691

‎docs/PROVIDER_TRANSITIONS_20260924.md‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@ The per-handle registry prevented Release racing Evaluate for the same public to
66

77
ProviderCallAdmission now separates lifecycle transitions from ordinary operations across the one shared replacement provider, including its D3D12 and Vulkan entry points. A transition is admitted only when no operation/transition is active; an operation is admitted only outside a transition. Conflicting calls return FAIL_NotInitialized without waiting. A movable scope owner releases admission on normal return and exceptions. The short metadata mutex is never held while calling native/provider/application code. Concurrent ordinary operations remain allowed as before; this change is transition exclusion, not universal provider thread safety.
88

9-
All 22 NVSDK-result entry points in Nvngx_FG are covered. The three Create variants clear OutHandle even when admission is denied. Both exported shutdown coordinators hold provider transition admission across native shutdown, provider shutdown and local cleanup. A busy provider therefore prevents the exported shutdown from touching native ownership in the first place. A later provider error propagates and preserves local cleanup state for retry; a successful native shutdown is not rolled back or repeated because its existing ownership ledger already records completion. A published provider is closed even if the current configuration selector has changed to None. An absent provider or unsupported API closes as a no-op without loading a DLL.
9+
All 22 NVSDK-result entry points in Nvngx_FG are covered. The three Create variants clear OutHandle even when admission is denied. Both exported shutdown coordinators hold provider transition admission across native shutdown, provider shutdown and local cleanup. A busy provider therefore prevents the exported shutdown from touching native ownership in the first place. A later provider error propagates and preserves local cleanup state for retry; a successful native shutdown is not rolled back or repeated because its existing ownership ledger already records completion. A provider with an outstanding Init attempt is closed even if the current configuration selector has changed to None. An absent provider or unsupported API closes as a no-op without loading a DLL.
1010

1111
The shutdown callback supplied to the coordinator must be used synchronously within its admitted scope; it is not a stored/deferred teardown API. No game/driver settings, SL1 runtime policy, installer behavior or frame multiplier code changes.
1212

13+
## Init cleanup footprint follow-up
14+
15+
A capability query can publish the provider without ever initializing it. Propagating that provider's Shutdown failure would incorrectly reject ordinary DLSS-only shutdown. Five provider Init routes now record a per-API/device cleanup footprint immediately before calling the SDK. Shutdown skips devices with no attempt; successful close clears only the affected footprint (or all for global close). Failed or throwing Init may have partially allocated resources and therefore keeps its cleanup obligation; failed or throwing close keeps it for retry. Null Init devices are rejected before invoking the SDK. Published-but-unused providers close without calling the SDK.
16+
17+
These sets are accessed only under transition admission. They track attempted initialization, not readiness, COM ownership, reference counts or handle generations. Repeated Init behavior remains forwarded. Combo child-level partial success still requires its own bookkeeping.
18+
1319
## Focused checks
1420

1521
- 85 new extracted entry-prefix/production-admission checks cover all 22 provider SDK entry points, busy transition/operation routing, empty Create outputs, moved leases, reentrant callback threads, exceptions, admission retained through caller cleanup, and no-op unsupported/absent-provider shutdown. Native/GPU bodies beyond ordinary admission are stand-ins.
22+
- 104 new checks execute all five complete Init bodies and the real shutdown coordinators with fake SDK results: never-initialized provider, wrong/null device, busy/Pending/unavailable lookup, failed/throwing Init and close, retry, global clearing, and independent API footprints.
1623
- Actual exported native/provider shutdown checks expand from 43 to 52: blocked shutdown changes neither API/device ownership nor local state, provider failure is propagated, and retry does not repeat a successful native shutdown.
1724
- Affected 36 provider publication-route and 18 per-handle Release-route checks pass with the actual new coordinator bodies.
1825
- Full Windows build/format/package outcome will be recorded after CI completes. These CPU checks do not establish that the Witcher loading/toggle crash is fixed.

‎tests/test_provider_call_admission.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
ROOT=Path(__file__).resolve().parents[1]
1414
PRELUDE=r'''
1515
#include "framegen/ProviderCallAdmission.h"
16+
#include <unordered_set>
1617
#include <functional>
1718
#include <future>
1819
#include <chrono>
@@ -49,6 +50,7 @@
4950
}
5051
check(bool(Nvngx_FG::_calls.TryTransition()));
5152
// Joining a callback thread does not wait on a lock held by the initiating call.
53+
Nvngx_FG::_dx12InitAttempts.insert(nullptr);
5254
provider.callback=[] {
5355
auto worker=std::async(std::launch::async,[] {
5456
return !Nvngx_FG::_calls.TryOperation() && !Nvngx_FG::_calls.TryTransition();
@@ -58,6 +60,7 @@
5860
};
5961
check(Nvngx_FG::D3D12_Shutdown()==-8);
6062
check(bool(Nvngx_FG::_calls.TryOperation()));
63+
Nvngx_FG::_vulkanInitAttempts.insert(nullptr);
6164
provider.callback=[]()->int { throw std::runtime_error("native close"); };
6265
bool threw=false;
6366
try { Nvngx_FG::VULKAN_Shutdown(); } catch(const std::runtime_error&) { threw=true; }
@@ -72,9 +75,9 @@
7275
// No provider and unsupported API are no-op shutdowns, with no lazy loading.
7376
Nvngx_FG::_provider.present=false; int before=provider.calls;
7477
check(Nvngx_FG::D3D12_Shutdown()==0 && Nvngx_FG::VULKAN_Shutdown()==0 && provider.calls==before);
75-
Nvngx_FG::_provider.present=true; provider.supportsVk=false;
78+
Nvngx_FG::_provider.present=true; provider.supportsVk=false; Nvngx_FG::_vulkanInitAttempts.insert(nullptr);
7679
check(Nvngx_FG::VULKAN_Shutdown()==0 && provider.calls==before);
77-
provider.supportsVk=true; provider.supportsDx=false;
80+
provider.supportsVk=true; provider.supportsDx=false; Nvngx_FG::_dx12InitAttempts.insert(nullptr);
7881
check(Nvngx_FG::D3D12_Shutdown()==0 && provider.calls==before);
7982
std::cout<<"PASS: "<<checks<<" provider call admission checks\n";
8083
}
@@ -103,6 +106,9 @@ def main():
103106
elif any(x in param for x in ('InApplicationId','InSDKVersion','InFeatureID','InFeatureId')): arguments.append('0')
104107
else: arguments.append('nullptr')
105108
call=f'Nvngx_FG::{name}('+','.join(arguments)+')'
109+
if '_Shutdown' in name:
110+
target='_dx12InitAttempts' if name.startswith('D3D12') else '_vulkanInitAttempts'
111+
checks.append(f'Nvngx_FG::{target}.insert(nullptr);')
106112
checks.append(f'out=&sentinel; check({call}==42);')
107113
if 'CreateFeature' in name: checks.append('check(out==nullptr);')
108114
checks.append('{ auto transition=Nvngx_FG::_calls.TryTransition(); out=&sentinel; '+f'check({call}==-7);')
@@ -112,6 +118,8 @@ def main():
112118
checks.append('{ auto operation=Nvngx_FG::_calls.TryOperation(); '+f'check({call}=={expected});'+' }')
113119
cls='''struct Nvngx_FG {
114120
inline static ProviderCallAdmission _calls;
121+
inline static std::unordered_set<ID3D12Device*> _dx12InitAttempts;
122+
inline static std::unordered_set<VkDevice> _vulkanInitAttempts;
115123
struct Publication { bool present; Provider* Peek() { return present?&provider:nullptr; } };
116124
inline static Publication _provider{true};
117125
'''+ '\n'.join(methods)+'\n'+'\n'.join(function(header,'template <typename Callback> static NVSDK_NGX_Result '+name+'(') for name in ('WithDx12Shutdown','WithVulkanShutdown'))+'\n};\n'

0 commit comments

Comments
 (0)