Skip to content

Commit d1fb5d8

Browse files
committed
fix(fg): validate native creation before publishing or logging handles
1 parent 2bf6876 commit d1fb5d8

8 files changed

Lines changed: 205 additions & 69 deletions

File tree

‎.github/workflows/just_build_no_signature.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ jobs:
128128
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
129129
$vs = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
130130
$vcvars = Join-Path $vs 'VC\Auxiliary\Build\vcvars64.bat'
131-
foreach ($name in @('Aurora_DLSSG_Frame_Guard_Tests','Aurora_Provider_Handle_Tests','Aurora_Input_Lock_Tests','Aurora_Streamline_Binding_Tests')) {
131+
foreach ($name in @('Aurora_DLSSG_Frame_Guard_Tests','Aurora_Provider_Handle_Tests','Aurora_Input_Lock_Tests','Aurora_Streamline_Binding_Tests','Aurora_Provider_Create_Tests')) {
132132
$command = 'call "' + $vcvars + '" && cl /nologo /EHsc /std:c++20 /I OptiScaler tests\' + $name + '.cpp /Fe:x64\' + $name + '.exe && x64\' + $name + '.exe'
133133
& $env:ComSpec /d /c $command
134134
if ($LASTEXITCODE) { throw "Compatibility guard test failed: $name" }
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
3+
#include <utility>
4+
5+
// Keep the caller's output empty until native creation and validation both
6+
// succeed. Error returns must not publish wrappers around absent native handles.
7+
template <typename PublicHandle, typename Registry, typename Value, typename Result, typename Create>
8+
Result CreateProviderHandle(Registry& registry, PublicHandle** output, Value initial, Result success,
9+
Result invalidParameter, Result malformedSuccess, Create&& create)
10+
{
11+
if (!output)
12+
return invalidParameter;
13+
*output = nullptr;
14+
auto pending = registry.Prepare(std::move(initial));
15+
const auto result = std::forward<Create>(create)(pending->value);
16+
if (result != success)
17+
return result;
18+
if (!pending->value.nativeHandle)
19+
return malformedSuccess;
20+
*output = reinterpret_cast<PublicHandle*>(registry.Publish(pending));
21+
return result;
22+
}

‎OptiScaler/framegen/nvngx/Nvngx_FG.cpp‎

Lines changed: 31 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <NVNGX_Parameter.h>
44
#include "Nvngx_FG.h"
5+
#include <framegen/ProviderHandleCreation.h>
56

67
#include "proxies/NVNGX_Proxy.h"
78
#include "proxies/Ntdll_Proxy.h"
@@ -267,26 +268,16 @@ NVSDK_NGX_Result Nvngx_FG::D3D12_GetScratchBufferSize(NVSDK_NGX_Feature InFeatur
267268
NVSDK_NGX_Result Nvngx_FG::D3D12_CreateFeature(ID3D12GraphicsCommandList* InCmdList, NVSDK_NGX_Feature InFeatureID,
268269
NVSDK_NGX_Parameter* InParameters, NVSDK_NGX_Handle** OutHandle)
269270
{
270-
auto* provider = getProvider();
271-
272-
if (!provider)
273-
return NVSDK_NGX_Result_Fail;
274-
275-
if (!OutHandle)
276-
return NVSDK_NGX_Result_FAIL_InvalidParameter;
277-
278-
auto pending = _handles.Prepare({ lastIdCreated++ + NVNGX_PROVIDER_ID_OFFSET });
279-
auto* proxyHandle = &pending->value;
280-
*OutHandle = nullptr;
281-
282-
auto result = provider->D3D12_CreateFeature(InCmdList, InFeatureID, InParameters, &proxyHandle->nativeHandle);
283-
284-
if (result == NVSDK_NGX_Result_Success)
285-
*OutHandle = reinterpret_cast<NVSDK_NGX_Handle*>(_handles.Publish(pending));
286-
287-
// LOG_TRACE("Handle given to the game: {:X}", (uint64_t) *OutHandle);
288-
289-
return result;
271+
return CreateProviderHandle(_handles, OutHandle, Nvngx_FG_Handle { lastIdCreated++ + NVNGX_PROVIDER_ID_OFFSET },
272+
NVSDK_NGX_Result_Success, NVSDK_NGX_Result_FAIL_InvalidParameter, NVSDK_NGX_Result_Fail,
273+
[&](Nvngx_FG_Handle& handle)
274+
{
275+
auto* provider = getProvider();
276+
if (!provider)
277+
return NVSDK_NGX_Result_Fail;
278+
return provider->D3D12_CreateFeature(InCmdList, InFeatureID, InParameters,
279+
&handle.nativeHandle);
280+
});
290281
}
291282

292283
NVSDK_NGX_Result Nvngx_FG::D3D12_ReleaseFeature(NVSDK_NGX_Handle* InHandle)
@@ -476,51 +467,32 @@ NVSDK_NGX_Result Nvngx_FG::VULKAN_GetScratchBufferSize(NVSDK_NGX_Feature InFeatu
476467
NVSDK_NGX_Result Nvngx_FG::VULKAN_CreateFeature(VkCommandBuffer InCmdBuffer, NVSDK_NGX_Feature InFeatureID,
477468
NVSDK_NGX_Parameter* InParameters, NVSDK_NGX_Handle** OutHandle)
478469
{
479-
auto* provider = getProvider();
480-
481-
if (!provider)
482-
return NVSDK_NGX_Result_Fail;
483-
484-
if (!OutHandle)
485-
return NVSDK_NGX_Result_FAIL_InvalidParameter;
486-
487-
auto pending = _handles.Prepare({ lastIdCreated++ + NVNGX_PROVIDER_ID_OFFSET });
488-
auto* proxyHandle = &pending->value;
489-
*OutHandle = nullptr;
490-
auto result = provider->VULKAN_CreateFeature(InCmdBuffer, InFeatureID, InParameters, &proxyHandle->nativeHandle);
491-
492-
if (result == NVSDK_NGX_Result_Success)
493-
*OutHandle = reinterpret_cast<NVSDK_NGX_Handle*>(_handles.Publish(pending));
494-
495-
// LOG_TRACE("Handle given to the game: {:X}", (uint64_t) *OutHandle);
496-
497-
return result;
470+
return CreateProviderHandle(_handles, OutHandle, Nvngx_FG_Handle { lastIdCreated++ + NVNGX_PROVIDER_ID_OFFSET },
471+
NVSDK_NGX_Result_Success, NVSDK_NGX_Result_FAIL_InvalidParameter, NVSDK_NGX_Result_Fail,
472+
[&](Nvngx_FG_Handle& handle)
473+
{
474+
auto* provider = getProvider();
475+
if (!provider)
476+
return NVSDK_NGX_Result_Fail;
477+
return provider->VULKAN_CreateFeature(InCmdBuffer, InFeatureID, InParameters,
478+
&handle.nativeHandle);
479+
});
498480
}
499481

500482
NVSDK_NGX_Result Nvngx_FG::VULKAN_CreateFeature1(VkDevice InDevice, VkCommandBuffer InCmdList,
501483
NVSDK_NGX_Feature InFeatureID, NVSDK_NGX_Parameter* InParameters,
502484
NVSDK_NGX_Handle** OutHandle)
503485
{
504-
auto* provider = getProvider();
505-
506-
if (!provider)
507-
return NVSDK_NGX_Result_Fail;
508-
509-
if (!OutHandle)
510-
return NVSDK_NGX_Result_FAIL_InvalidParameter;
511-
512-
auto pending = _handles.Prepare({ lastIdCreated++ + NVNGX_PROVIDER_ID_OFFSET });
513-
auto* proxyHandle = &pending->value;
514-
*OutHandle = nullptr;
515-
auto result =
516-
provider->VULKAN_CreateFeature1(InDevice, InCmdList, InFeatureID, InParameters, &proxyHandle->nativeHandle);
517-
518-
if (result == NVSDK_NGX_Result_Success)
519-
*OutHandle = reinterpret_cast<NVSDK_NGX_Handle*>(_handles.Publish(pending));
520-
521-
// LOG_TRACE("Handle given to the game: {:X}", (uint64_t) *OutHandle);
522-
523-
return result;
486+
return CreateProviderHandle(_handles, OutHandle, Nvngx_FG_Handle { lastIdCreated++ + NVNGX_PROVIDER_ID_OFFSET },
487+
NVSDK_NGX_Result_Success, NVSDK_NGX_Result_FAIL_InvalidParameter, NVSDK_NGX_Result_Fail,
488+
[&](Nvngx_FG_Handle& handle)
489+
{
490+
auto* provider = getProvider();
491+
if (!provider)
492+
return NVSDK_NGX_Result_Fail;
493+
return provider->VULKAN_CreateFeature1(InDevice, InCmdList, InFeatureID,
494+
InParameters, &handle.nativeHandle);
495+
});
524496
}
525497

526498
NVSDK_NGX_Result Nvngx_FG::VULKAN_ReleaseFeature(NVSDK_NGX_Handle* InHandle)

‎OptiScaler/inputs/NVNGX_DLSS_Dx12.cpp‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -743,17 +743,16 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsComma
743743
{
744744
LOG_FUNC();
745745

746+
if (!OutHandle)
747+
return NVSDK_NGX_Result_FAIL_InvalidParameter;
748+
*OutHandle = nullptr;
749+
746750
if (!InCmdList)
747751
{
748752
LOG_ERROR("InCmdList is null");
749753
return NVSDK_NGX_Result_Fail;
750754
}
751755

752-
if (!OutHandle)
753-
{
754-
LOG_ERROR("OutHandle is null");
755-
return NVSDK_NGX_Result_Fail;
756-
}
757756

758757
const State& state = State::Instance();
759758
const Config& cfg = *Config::Instance();
@@ -766,7 +765,7 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsComma
766765

767766
NVSDK_NGX_Result res = Nvngx_FG::D3D12_CreateFeature(InCmdList, InFeatureID, InParameters, OutHandle);
768767

769-
if (*OutHandle)
768+
if (res == NVSDK_NGX_Result_Success && *OutHandle)
770769
{
771770
LOG_INFO("Created modded DLSSG feature with HandleId: {}", (*OutHandle)->Id);
772771
HandleToFeature[(*OutHandle)->Id] = InFeatureID;
@@ -785,7 +784,7 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_D3D12_CreateFeature(ID3D12GraphicsComma
785784

786785
NVSDK_NGX_Result res = NVNGXProxy::D3D12_CreateFeature()(InCmdList, InFeatureID, InParameters, OutHandle);
787786

788-
if (*OutHandle)
787+
if (res == NVSDK_NGX_Result_Success && *OutHandle)
789788
{
790789
LOG_INFO("Native CreateFeature success, HandleId: {}", (*OutHandle)->Id);
791790
HandleToFeature[(*OutHandle)->Id] = InFeatureID;

‎OptiScaler/inputs/NVNGX_DLSS_Vk.cpp‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -818,10 +818,17 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_VULKAN_CreateFeature1(VkDevice InDevice
818818
NVSDK_NGX_Parameter* InParameters,
819819
NVSDK_NGX_Handle** OutHandle)
820820
{
821+
if (!OutHandle)
822+
return NVSDK_NGX_Result_FAIL_InvalidParameter;
823+
*OutHandle = nullptr;
824+
821825
if (Nvngx_FG::isVulkanAvailable() && InFeatureID == NVSDK_NGX_Feature_FrameGeneration)
822826
{
823827
auto result = Nvngx_FG::VULKAN_CreateFeature1(InDevice, InCmdList, InFeatureID, InParameters, OutHandle);
824-
LOG_INFO("Creating new modded DLSSG feature with HandleId: {0}", (*OutHandle)->Id);
828+
if (result == NVSDK_NGX_Result_Success && *OutHandle)
829+
LOG_INFO("Creating new modded DLSSG feature with HandleId: {0}", (*OutHandle)->Id);
830+
else
831+
LOG_ERROR("Modded DLSSG feature creation failed: {0:X}", (UINT) result);
825832
return result;
826833
}
827834
else if (InFeatureID != NVSDK_NGX_Feature_SuperSampling && InFeatureID != NVSDK_NGX_Feature_RayReconstruction)
@@ -911,12 +918,19 @@ NVSDK_NGX_API NVSDK_NGX_Result NVSDK_NGX_VULKAN_CreateFeature(VkCommandBuffer In
911918
NVSDK_NGX_Parameter* InParameters,
912919
NVSDK_NGX_Handle** OutHandle)
913920
{
921+
if (!OutHandle)
922+
return NVSDK_NGX_Result_FAIL_InvalidParameter;
923+
*OutHandle = nullptr;
924+
914925
LOG_FUNC();
915926

916927
if (Nvngx_FG::isVulkanAvailable() && InFeatureID == NVSDK_NGX_Feature_FrameGeneration)
917928
{
918929
auto result = Nvngx_FG::VULKAN_CreateFeature(InCmdBuffer, InFeatureID, InParameters, OutHandle);
919-
LOG_INFO("Creating new modded DLSSG feature with HandleId: {0}", (*OutHandle)->Id);
930+
if (result == NVSDK_NGX_Result_Success && *OutHandle)
931+
LOG_INFO("Creating new modded DLSSG feature with HandleId: {0}", (*OutHandle)->Id);
932+
else
933+
LOG_ERROR("Modded DLSSG feature creation failed: {0:X}", (UINT) result);
920934
return result;
921935
}
922936
else if (InFeatureID != NVSDK_NGX_Feature_SuperSampling && InFeatureID != NVSDK_NGX_Feature_RayReconstruction)

‎docs/CODEX_HANDOFF.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ The user abandoned the RC3 one-click installer branch and authorized compatibili
1313

1414
## Continuation rules
1515

16+
Latest user constraints: no ZZZ installation/feedback is available, and Witcher cannot currently be retested. Keep these as pending validation; continue independent code work without repeatedly requesting game tests. New features are deferred. The user's conditional main-branch merge request requires sufficient stability confidence, which has not yet been established.
17+
1618
Official upstream is `https://github.com/optiscaler/OptiScaler.git`; the local remote named `upstream` is instead the grim-susemi fork. Official audit cutoff is now `a4890db5b3c7c6918f4b35d0fd3318b42e2ccc66`. It adds only the Sword and Fairy 7 quirk after `4af2417b`; adopted with provenance. Nine commits before that, after `731f3b79`, were already reviewed. Do not repeat those audits.
1719

1820
Current package workflow builds this branch and runs production-header frame/handle guard tests. Keep the original installer layout inherited from `aurora`; do not restore RC3 tools.
@@ -21,6 +23,8 @@ The 2026-09-16 continuation adds optional input discovery outside the state lock
2123

2224
The next continuation implements active private Streamline binding after device selection, separates runtime initialization from FG readiness (including Witcher's second-device path), and guards failed Reflex frame-token acquisition. See [Streamline binding audit](STREAMLINE_BINDING_20260916.md), including 23 lifecycle checks and the full-build result. This supersedes the earlier deferred-binding item; provider-global concurrency and actual game verification remain outstanding.
2325

26+
The creation-failure follow-up adds 16 CPU checks and repairs null/stale handle access after failed FG creation in D3D12/Vulkan. See [FG creation audit](FG_CREATION_20260916.md), which also explains why provider construction needs an explicit busy/routing design and why device-specific shutdown cannot simply become a global lock/stop.
27+
2428
The user subsequently provided ZZZ 11008 component-error screenshots and reports of disappearing files, and NTE menu stutter with an NPI workaround. GPU is tentatively RTX 40; exact build, missing filenames, protection history, and crash logs remain unknown. See [NTE/ZZZ follow-up](NTE_ZZZ_COMPATIBILITY.md). Do not confuse component errors, GPU exceptions, and absent FG input. No driver/game settings were changed by this work.
2529

2630
Git network requests on this host have worked with per-command `http.proxy=http://127.0.0.1:7897`; do not alter global proxy or disable TLS verification. Push only `origin Compatibility-fixes`.

‎docs/FG_CREATION_20260916.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# FG creation failure boundary — 2026-09-16
2+
3+
Continuation from `2bf68763` on `Compatibility-fixes`. The user currently cannot test Witcher and does not own ZZZ; actual game verification is pending and must not block independent source-level work. New features are deferred. No main-branch merge is justified by CPU tests alone.
4+
5+
## Confirmed defects repaired
6+
7+
- Both exported Vulkan FG Create routes logged `(*OutHandle)->Id` unconditionally after provider creation. Failure leaves the output null, so the diagnostic path could itself crash. These routes now validate/clear output and only inspect the handle after successful creation with a non-null result.
8+
- The D3D12 and Vulkan provider adapters previously returned early for an unavailable provider before clearing a caller's old output, and published wrappers when a native provider returned success with a null native handle. A shared production helper now clears output before provider lookup, retains unpublished wrapper ownership during creation, and publishes only a successful, non-null native handle.
9+
- D3D12 exported creation clears output before command-list validation and only records/logs native or replacement handles on a successful result. This prevents reading stale outputs on error.
10+
11+
Failed native creation remains the provider's responsibility: Aurora does not guess how to release a pointer written alongside a failure result. C++ exceptions leave output empty and the unpublished wrapper destroyed; this helper does not claim to catch structured exceptions or make foreign binaries safe.
12+
13+
## Focused validation
14+
15+
16 Windows C++ checks exercise the production creation helper and registry: null output without native invocation, clearing stale output before callback, failure/unavailable-provider behavior, no publication on failure, successful return with no native handle, failure that writes a pointer, exception cleanup, successful publication/read and release exactly once. These are CPU failure-path checks, not GPU compatibility evidence.
16+
17+
## Global lifecycle audit remains open
18+
19+
`Nvngx_FG::getProvider` still constructs and exposes a shared provider before fallback validation completes. A concurrent call could observe that provider while another call replaces it. A mutex held across constructors is unsafe because Nukems/Enabler constructors load DLLs and can cause callbacks. A nonblocking construction gate also needs outer routing changes: current D3D12 Create treats a temporarily unavailable replacement as permission to try native NGX, which is not equivalent to a retryable initialization state. This boundary should be solved as a distinct publication/routing change, not by quietly returning null from a locked factory.
20+
21+
Shutdown needs separate ordering work too. The FFX provider's shutdown is a no-op, while DLL providers forward native shutdown; device-specific shutdown must not be replaced by a global stop that disables other devices. Handle-level locks and the creation checks in this checkpoint do not serialize native global shutdown against evaluations. No claim of fixing all lifecycle races or the game's reported crash follows from this patch.

0 commit comments

Comments
 (0)