Skip to content

Commit 3b81080

Browse files
committed
fix: isolate D3D12 recording and correct early UAV tracking
1 parent dbff16b commit 3b81080

5 files changed

Lines changed: 147 additions & 4 deletions

File tree

‎.github/workflows/just_build_no_signature.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ jobs:
144144
& $env:ComSpec /d /c $command
145145
if ($LASTEXITCODE) { throw "Compatibility guard test failed: $name" }
146146
}
147-
foreach ($script in @('test_xell_module_routing.py','test_october_compatibility.py','test_vulkan_timing_results.py','test_depth_copy_resource.py','test_hud_publication.py','test_hud_copy_failure.py','test_shutdown_routing.py','test_provider_publication_routes.py','test_provider_dll_boundary.py','test_combo_release.py','test_combo_shutdown.py','test_native_operation_routes.py','test_native_module_publication.py','test_native_init_scopes.py', 'test_exported_init_chains.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','test_provider_handle_api.py','test_upstream_resource_guards.py','test_provider_settings.py','test_provider_shutdown_drain.py','test_ffx_release.py','test_ffx_create.py','test_combo_create.py','test_provider_init_allocation.py')) {
147+
foreach ($script in @('test_d3d12_tracking_isolation.py','test_xell_module_routing.py','test_october_compatibility.py','test_vulkan_timing_results.py','test_depth_copy_resource.py','test_hud_publication.py','test_hud_copy_failure.py','test_shutdown_routing.py','test_provider_publication_routes.py','test_provider_dll_boundary.py','test_combo_release.py','test_combo_shutdown.py','test_native_operation_routes.py','test_native_module_publication.py','test_native_init_scopes.py', 'test_exported_init_chains.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','test_provider_handle_api.py','test_upstream_resource_guards.py','test_provider_settings.py','test_provider_shutdown_drain.py','test_ffx_release.py','test_ffx_create.py','test_combo_create.py','test_provider_init_allocation.py')) {
148148
$command = 'call "' + $vcvars + '" && python tests\' + $script + ' --compiler cl'
149149
& $env:ComSpec /d /c $command
150150
if ($LASTEXITCODE) { throw "Compatibility routing test failed: $script" }

‎OptiScaler/hooks/D3D12_Hooks.cpp‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ static thread_local bool lateInProgressSetGraphicsRootUnorderedAccessView = fals
207207
static std::shared_mutex rootSigParameterCountMutex;
208208
static ankerl::unordered_dense::map<ID3D12RootSignature*, UINT> rootSigParameterCount;
209209

210-
static bool isUpscalerActive = false;
210+
// Internal work on this thread must not suppress another command-list recorder.
211+
static thread_local bool isUpscalerActive = false;
211212

212213
// Intel Atomic Extension
213214
struct UE_D3D12_RESOURCE_DESC
@@ -538,7 +539,7 @@ VALIDATE_HOOK(hkSetComputeRootUnorderedAccessView, PFN_SetComputeRootUnorderedAc
538539
static void hkSetComputeRootUnorderedAccessView(ID3D12GraphicsCommandList* commandList, UINT RootParameterIndex,
539540
D3D12_GPU_VIRTUAL_ADDRESS BufferLocation)
540541
{
541-
if (lateInProgressSetComputeRootUnorderedAccessView && !isUpscalerActive && commandList != nullptr)
542+
if (!lateInProgressSetComputeRootUnorderedAccessView && !isUpscalerActive && commandList != nullptr)
542543
{
543544
std::unique_lock<std::shared_mutex> lock(rootStatesMutex);
544545
auto& table = rootStates[commandList];
@@ -668,7 +669,7 @@ VALIDATE_HOOK(hkSetGraphicsRootUnorderedAccessView, PFN_SetGraphicsRootUnordered
668669
static void hkSetGraphicsRootUnorderedAccessView(ID3D12GraphicsCommandList* commandList, UINT RootParameterIndex,
669670
D3D12_GPU_VIRTUAL_ADDRESS BufferLocation)
670671
{
671-
if (lateInProgressSetGraphicsRootUnorderedAccessView && !isUpscalerActive && commandList != nullptr)
672+
if (!lateInProgressSetGraphicsRootUnorderedAccessView && !isUpscalerActive && commandList != nullptr)
672673
{
673674
std::unique_lock<std::shared_mutex> lock(rootStatesMutex);
674675
auto& table = rootStates[commandList];

‎docs/CODEX_HANDOFF.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2026-10-05 D3D12 改为分拆吸收:线程隔离与 UAV 跟踪
2+
3+
继续评审发现私有 COM 状态会引入新的模块卸载要求(当前 DLL_PROCESS_DETACH 没有完整解绑路径)。不为整套重构强加进程永久驻留,也不把原型视为已集成。先独立吸收上游 500ed335 的线程局部跟踪思路,保留原有状态表/设备逻辑;并修正 compute/graphics early UAV 条件缺少 `!` 导致直接调用漏记、late 链重复记录的问题。生产差异只含该 TLS 声明及两个条件。新增 test_d3d12_tracking_isolation.py,提取真实函数/声明执行,86 项检查及三个负向变异通过,加入 Windows 构建流程。完整构建尚待确认,勿提前宣称已发布。大型候选与私有状态原型继续隔离,不自动扩展模块 pin/unload 重构。
4+
15
## 2026-10-05 D3D12 ClearState 候选与私有状态原型
26

37
继续适配:实验 patch 增加 ClearState early/late Hook 的加载/注册/恢复清理,状态检查从 68 增至 78,原三个负向变异仍通过。独立 `patches/experimental/D3D12PrivateState.h` 原型使用 D3D12 私有接口持有 shared_ptr 状态,测试 29 个生命周期断言及 1000 次读取,通过两个引用泄漏负向变异。已验证替身对象同地址重建不会读旧记录。原型尚未接入 Hook;包装对象身份、模块卸载后的回调代码存活、初始化串行与失败调用点仍是门槛。真实 D3D12/Detours/MSVC 集成未验证,不重复构建正式 DLL、不发布。详见实验 README,生产代码保持 v1.1.2。

‎docs/D3D12_STATE_REFACTOR_REVIEW_20261005.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,15 @@ BLOCKER 3: recorded empty heap state is not restored
6161
用户确认继续后,新增 `patches/experimental/d3d12-state-semantics.patch`(固定官方基线,不应用到 Aurora 生产文件)。通过已知/未知堆标志及首次 Reset 建档修正上述三个缺口。新增 `tests/test_d3d12_state_candidate.py`:68 项 CPU 检查通过,三个撤回修正的负向变异均被断言拒绝,涵盖 late→early Hook 链与 TLS 跨线程隔离。原复现脚本保留不变,用于证明未修正基线的问题。
6262

6363
额外确认了依赖缺口:官方新版需要 Aurora 当前没有的 HUDfix 持久绑定接口。生产适配仍未完成;需处理这一差异与 ClearState/对象生命周期,不能将当前局部验证表述为整套重构已闭环。未改生产 DLL、未推送、未发布新包。实验补丁说明见 `patches/experimental/README.md`。
64+
65+
## 分拆吸收决定与生产小修正
66+
67+
继续审查确认私有接口原型需要在 D3D12 对象持有回调期间保证 DLL 代码存活。当前 `dllmain.cpp` 的 DLL_PROCESS_DETACH 路径并未完整解除所有 Hook;不能凭 CPU 引用计数测试声称支持动态卸载。强制 pin 模块会改变卸载行为,因此本轮不引入这一新策略。
68+
69+
将能独立验证的改进先落地:
70+
71+
- `isUpscalerActive` 使用 thread_local,与官方候选一致;一个线程暂停跟踪不再影响其他命令列表记录线程。该 bool 不是嵌套深度计数,本轮不声称解决同线程任意重入。
72+
- compute/graphics early UAV 两处条件由 `lateInProgress...` 修正为 `!lateInProgress...`,与其他入口和上游“记录一次、链式抑制”的行为一致。旧代码会在直接 early 调用时漏记,late→early 时重复记录。本次不声称此缺陷已被证明是巫师3闪退原因。
73+
- 保留 Aurora 原有设备捕获、关闭、Intel、NR exposure、状态存储和 SL1/6X 逻辑。生产 C++ 差异为一个 TLS 声明和两个条件,不引入实验头文件。
74+
75+
新增 `tests/test_d3d12_tracking_isolation.py`:86 项 CPU 检查,覆盖 compute/graphics、early/late 串联、零地址、参数越界、空命令列表、暂停跟踪、线程隔离和原调用转发;三个负向变异(全局开关/两处条件反转)都被断言拒绝。为计数重复记录,测试将锁类型替换为计数替身,其余函数体直接提取生产源文件;跨线程操作通过 join 串行,测试本身不制造共享表数据竞争。已加入 Windows 构建检查,完整 DLL 结果以 Actions 为准。
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""Run shipped UAV hook bodies and the tracking switch with CPU stand-ins.
2+
3+
Checks recording/forwarding and ordered thread isolation, not GPU restoration.
4+
"""
5+
import argparse
6+
from pathlib import Path
7+
import re
8+
import subprocess
9+
import tempfile
10+
from test_shutdown_routing import function
11+
12+
ROOT = Path(__file__).resolve().parents[1]
13+
14+
15+
def main():
16+
parser = argparse.ArgumentParser()
17+
parser.add_argument('--compiler', required=True)
18+
parser.add_argument('--driver')
19+
args = parser.parse_args()
20+
source = (ROOT/'OptiScaler/hooks/D3D12_Hooks.cpp').read_text(encoding='utf-8')
21+
cpp = r'''
22+
#include <vector>
23+
#include <unordered_map>
24+
#include <mutex>
25+
#include <thread>
26+
#include <iostream>
27+
#include <cstdlib>
28+
using UINT=unsigned;using D3D12_GPU_VIRTUAL_ADDRESS=unsigned long long;
29+
struct ID3D12GraphicsCommandList {};
30+
enum class RootEntryType {Invalid,UAV};
31+
struct RootState {RootEntryType type=RootEntryType::Invalid;D3D12_GPU_VIRTUAL_ADDRESS bufferLocation=0;};
32+
// Count actual record attempts, including duplicate late -> early writes.
33+
struct CountingLock {int acquisitions=0;void lock(){++acquisitions;}void unlock(){}};
34+
CountingLock rootStatesMutex;
35+
std::unordered_map<ID3D12GraphicsCommandList*,std::vector<RootState>> rootStates;
36+
struct D3D12Hooks {static void SetRootSignatureTracking(bool);};
37+
int calls=0;ID3D12GraphicsCommandList* lastCmd=nullptr;UINT lastIndex=0;
38+
D3D12_GPU_VIRTUAL_ADDRESS lastAddress=0;
39+
void original(ID3D12GraphicsCommandList* cmd,UINT i,D3D12_GPU_VIRTUAL_ADDRESS address){
40+
++calls;lastCmd=cmd;lastIndex=i;lastAddress=address;
41+
}
42+
using HookFn=decltype(&original);
43+
struct Hook {HookFn o_earlyHook=original,o_lateHook=original;};
44+
Hook s_SetComputeRootUnorderedAccessView,s_SetGraphicsRootUnorderedAccessView;
45+
'''
46+
names = ['isUpscalerActive', 'lateInProgressSetComputeRootUnorderedAccessView',
47+
'lateInProgressSetGraphicsRootUnorderedAccessView']
48+
for name in names:
49+
match = re.search(r'^static (?:thread_local )?bool '+name+r' = false;', source, re.M)
50+
assert match, name
51+
cpp += match[0]+'\n'
52+
cpp += function(source, 'void D3D12Hooks::SetRootSignatureTracking(')+'\n'
53+
for pipeline in ['Compute', 'Graphics']:
54+
for suffix in ['', 'Late']:
55+
body = function(source, 'static void hkSet'+pipeline+'RootUnorderedAccessView'+suffix+'(')
56+
# Substitute only the lock type to count duplicate recording; retain the hook body.
57+
cpp += body.replace('std::shared_mutex', 'CountingLock')+'\n'
58+
cpp += r'''
59+
int main(){int checks=0;auto check=[&](bool ok){++checks;if(!ok){std::cerr<<"FAIL "<<checks;std::exit(2);}};
60+
ID3D12GraphicsCommandList a,b;
61+
for(bool graphics:{false,true}){
62+
auto early=graphics?hkSetGraphicsRootUnorderedAccessView:hkSetComputeRootUnorderedAccessView;
63+
auto late=graphics?hkSetGraphicsRootUnorderedAccessViewLate:hkSetComputeRootUnorderedAccessViewLate;
64+
auto& hook=graphics?s_SetGraphicsRootUnorderedAccessView:s_SetComputeRootUnorderedAccessView;
65+
auto& nesting=graphics?lateInProgressSetGraphicsRootUnorderedAccessView:lateInProgressSetComputeRootUnorderedAccessView;
66+
hook.o_lateHook=early;
67+
for(bool latePath:{false,true}){
68+
auto call=latePath?late:early;
69+
rootStates.clear();rootStates[&a].resize(2);calls=0;rootStatesMutex.acquisitions=0;
70+
D3D12Hooks::SetRootSignatureTracking(true);
71+
call(&a,1,123);check(rootStates[&a][1].type==RootEntryType::UAV);
72+
check(rootStates[&a][1].bufferLocation==123);check(calls==1);
73+
check(lastCmd==&a&&lastIndex==1&&lastAddress==123);check(rootStatesMutex.acquisitions==1);
74+
call(&a,1,0);check(rootStates[&a][1].bufferLocation==0);check(calls==2);
75+
call(&a,9,77);check(rootStates[&a].size()==2&&rootStates[&a][1].bufferLocation==0);
76+
check(calls==3&&lastIndex==9);
77+
call(nullptr,0,88);check(calls==4&&lastCmd==nullptr);check(!rootStates.contains(nullptr));
78+
D3D12Hooks::SetRootSignatureTracking(false);int writes=rootStatesMutex.acquisitions;
79+
call(&a,1,99);check(calls==5);check(rootStatesMutex.acquisitions==writes);
80+
check(rootStates[&a][1].bufferLocation==0);check(!nesting);
81+
// Join orders access to all stand-ins, so the test itself has no data race.
82+
rootStates[&b].resize(1);bool workerEnabled=false;
83+
std::thread worker([&]{workerEnabled=!isUpscalerActive;call(&b,0,321);});worker.join();
84+
check(workerEnabled);check(rootStates[&b][0].bufferLocation==321);check(isUpscalerActive);
85+
check(calls==6);D3D12Hooks::SetRootSignatureTracking(true);check(!isUpscalerActive);
86+
}
87+
nesting=true;rootStates[&a][0]={};calls=0;int writes=rootStatesMutex.acquisitions;
88+
early(&a,0,999);check(calls==1);check(rootStatesMutex.acquisitions==writes);
89+
check(rootStates[&a][0].type==RootEntryType::Invalid);nesting=false;
90+
}
91+
std::cout<<checks<<" D3D12 tracking checks passed (CPU stand-ins)\n";
92+
}
93+
'''
94+
mutations = {
95+
'global-tracking-switch': ('static thread_local bool isUpscalerActive', 'static bool isUpscalerActive'),
96+
'compute-uav-inversion': ('if (!lateInProgressSetComputeRootUnorderedAccessView &&',
97+
'if (lateInProgressSetComputeRootUnorderedAccessView &&'),
98+
'graphics-uav-inversion': ('if (!lateInProgressSetGraphicsRootUnorderedAccessView &&',
99+
'if (lateInProgressSetGraphicsRootUnorderedAccessView &&'),
100+
}
101+
with tempfile.TemporaryDirectory(prefix='aurora-d3d12-tracking-') as directory:
102+
path = Path(directory)
103+
cases = {'fixed': cpp}
104+
for name, (old, new) in mutations.items():
105+
assert cpp.count(old) == 1, name
106+
cases[name] = cpp.replace(old, new)
107+
for name, text in cases.items():
108+
test, exe = path/'test.cpp', path/'test.exe'
109+
test.write_text(text, encoding='utf-8')
110+
command = [args.compiler]+([args.driver] if args.driver else [])
111+
if Path(args.compiler).stem.lower() == 'cl':
112+
command += ['/nologo', '/EHsc', '/std:c++20', str(test), '/Fe:'+str(exe)]
113+
else:
114+
command += ['-std=c++20', str(test), '-o', str(exe)]
115+
subprocess.run(command, cwd=path, check=True)
116+
result = subprocess.run([str(exe)], capture_output=True, text=True, timeout=25)
117+
if name == 'fixed':
118+
assert result.returncode == 0, result
119+
print(result.stdout.strip())
120+
else:
121+
assert result.returncode == 2 and 'FAIL ' in result.stderr, (name, result)
122+
print('Mutation rejected:', name)
123+
124+
125+
if __name__ == '__main__':
126+
main()

0 commit comments

Comments
 (0)