diff --git a/CMakeLists.txt b/CMakeLists.txt index 0149548..b992245 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,6 +140,20 @@ target_link_libraries(d4r_d3d12_hip_interop_probe PRIVATE d3d12 dxgi dxguid) target_link_libraries(d4r_d3d12_dlss_probe PRIVATE d3d12 dxgi dxguid) target_link_libraries(d4r_d3d12_command_probe PRIVATE d3d12 dxgi dxguid) add_dependencies(d4r_d3d12_command_probe d4r_nvngx_windows) +set(D4R_AGILITY_ROOT "" CACHE PATH "Optional unpacked Microsoft.Direct3D.D3D12 1.619.5 debug SDK") +if(EXISTS "${D4R_AGILITY_ROOT}/build/native/bin/x64/d3d12SDKLayers.dll") + add_executable(d4r_d3d12_command_debug_probe EXCLUDE_FROM_ALL tools/windows/d3d12_command_probe.cpp) + target_compile_definitions(d4r_d3d12_command_debug_probe PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX __HIP_PLATFORM_AMD__ D4R_DIAGNOSTIC_AGILITY) + target_include_directories(d4r_d3d12_command_debug_probe PRIVATE "${D4R_HIP_ROOT}/include") + target_link_libraries(d4r_d3d12_command_debug_probe PRIVATE version dbghelp d3d12 dxgi dxguid) + if(MINGW) + target_link_options(d4r_d3d12_command_debug_probe PRIVATE -static) + endif() + foreach(dll D3D12Core.dll d3d12SDKLayers.dll) + configure_file("${D4R_AGILITY_ROOT}/build/native/bin/x64/${dll}" "${CMAKE_CURRENT_BINARY_DIR}/debug-d3d12/${dll}" COPYONLY) + endforeach() + configure_file("${D4R_AGILITY_ROOT}/LICENSE.txt" "${CMAKE_CURRENT_BINARY_DIR}/debug-d3d12/LICENSE.txt" COPYONLY) +endif() add_dependencies(d4r_hip_gfx1201_probe d4r_probe_code_object) add_dependencies(d4r_gfx12_wmma_probe d4r_wmma_code_object) add_dependencies(d4r_k_module_probe d4r_native_gfx1201_compile) @@ -164,8 +178,12 @@ install(FILES kernels/tools/model_enc3.py kernels/tools/swin_model.py kernels/to install(FILES docs/windows-rdna4-port.md DESTINATION docs) include(CTest) add_test(NAME d3d12_command_backend COMMAND d4r_d3d12_command_probe - --hip-root "${D4R_HIP_ROOT}" --module "$" --iterations 32) + --hip-root "${D4R_HIP_ROOT}" --module "$" --interop-mode indirect --iterations 32) set_tests_properties(d3d12_command_backend PROPERTIES TIMEOUT 120 LABELS "hardware;d3d12;command_lists;lifetime") +add_test(NAME d3d12_command_root_reset_warp COMMAND d4r_d3d12_command_probe + --hip-root "${D4R_HIP_ROOT}" --module "$" --interop-mode indirect-root-reset --iterations 32) +set_tests_properties(d3d12_command_root_reset_warp PROPERTIES TIMEOUT 120 LABELS "software;d3d12;command_lists;lifetime" + ENVIRONMENT "D4R_COMMAND_PROBE_WARP=1;D4R_COMMAND_PROBE_NO_HIP=1") add_test(NAME hip_gfx1201 COMMAND d4r_hip_gfx1201_probe --hip-root "${D4R_HIP_ROOT}" --module "${probe_object}") set_tests_properties(hip_gfx1201 PROPERTIES TIMEOUT 90 LABELS "hardware;gfx1201") diff --git a/docs/windows-rdna4-port.md b/docs/windows-rdna4-port.md index 2ba78ea..699fcea 100644 --- a/docs/windows-rdna4-port.md +++ b/docs/windows-rdna4-port.md @@ -102,10 +102,14 @@ The backend intercepts documented D3D12 COM methods using pinned open-source [MinHook 1.3.4](https://github.com/TsudaKageyu/minhook/releases/tag/v1.3.4), commit `c3fcafdc10146beb5919319d0683e44e3c30d537`. No NVIDIA instructions, proprietary DLL contents or private driver interfaces are patched. The typed forwarding -code is generated from public SDK CommandList0..7 declarations. The additional -CommandList8..10 declarations were audited in Microsoft's current public -DirectX-Headers, `adbd6f3ba40795c46a8d0f33af00bcb57ff0f0a4`; coverage of those -newer methods is the next implementation step. +code is generated from public SDK CommandList0..7 declarations, with typed +CommandList8..10 additions audited in Microsoft's current public +DirectX-Headers, `adbd6f3ba40795c46a8d0f33af00bcb57ff0f0a4`. The AMD runtime +exposes CommandList10: eighty public-method hooks now install successfully. +SetProgram descriptors are copied by value. Work-graph replay omits INITIALIZE +to preserve already initialized backing memory; see the +[Microsoft work-graph specification](https://microsoft.github.io/DirectX-Specs/d3d/WorkGraphs.html#d3d12_set_work_graph_flags). +Full GPU work-graph execution is not a tested gate yet. At NGX Evaluate, the caller's resource references and typed parameter values are copied into an owned snapshot. The recorded prefix is closed. Subsequent calls @@ -142,11 +146,61 @@ frames are bit-exact against `d3d12-k-final`. Complete CTest: 12/12 PASS Enable this backend with `D4R_D3D12_COMMAND_BACKEND=1`; the diagnostic runner sets it through `-CommandListBackend`. Before game testing, remaining gates are -newer command methods, indirect-state reconstruction, enhanced barrier layouts, +enhanced barrier layouts, actual game texture formats and interaction with OptiScaler's own state hooks. Closed lists, active render/query scopes, GPU predication and unknown indirect state are currently explicitly rejected instead of guessing their semantics. +## Indirect commands and Windows diagnostics (2026-10-01) + +CreateCommandSignature interception stores its public argument description +using ID3D12Object::SetPrivateData, owned by the original COM object. There is +no driver-private introspection or independent signature-lifetime table. +After ExecuteIndirect, replay restores exactly the documented zero/NULL values +for affected root constants, views, VB slots and IB; unaffected state remains +inherited. See [Microsoft's indirect drawing specification](https://learn.microsoft.com/en-us/windows/win32/direct3d12/indirect-drawing). +Signatures created before interception remain explicitly untracked. + +The RX 9070 XT passes 32 queue/backend iterations alternating direct Dispatch +and dispatch-only ExecuteIndirect. Bindings, ordering, temporary host argument +copies and final Release are verified (`d3d12-indirect-plain-final`). Microsoft +WARP passes 32 backend iterations with an indirect signature that changes a +root constant, followed by direct Dispatch without rebinding: both prefix and +suffix observe the required zero (`d3d12-indirect-root-warp-backend.log`). +WARP is reported as software, never as gfx1201. +CTest now passes 13/13 (twelve physical GPU gates plus the WARP root-reset +regression), `d3d12-command-v10-final-ctest.log`. Four ordinary recorded M +frames still execute forty native transformer launches and remain bit-exact +against the previous CommandList7 implementation (`d3d12-m-command-v10.zip`, +`d3d12-m-command-v10-reference.log`). + +**Unresolved native AMD root-mutating indirect regression:** that same valid +root-constant/Dispatch signature removes the AMD device (0x887a0006). It also +fails with d4r hooks disabled and with HIP omitted, under inbox D3D12Core and +Agility 1.619.5, with UPLOAD and DEFAULT argument buffers. DRED stops inside +ExecuteIndirect and reports 0x80015c000 when the argument-buffer GPU VA is +0x200057000. The exact fault address is four times that allocation's VA; this +is an observation, not a proven driver cause. This workload passes on WARP. +Do not classify native root-mutating indirect execution as validated. The +default physical test covers dispatch-only signatures; the root mutation case +is a separate reproducible diagnostic: + +```powershell +powershell -NoProfile -File scripts/windows/test-d3d12-command.ps1 -NoHooks -NoHip -Mode indirect-root-reset -Iterations 2 -OutputDirectory test-results/amd-root-indirect-reproducer +``` + +Logs retain stdout/stderr, device-removed reason, DRED breadcrumbs/fault address, +loaded DLL versions and exception minidumps via D4R_DIAG_DIR. `-Warp` selects +the software comparison. No game or NVIDIA DLL is required. + +Optional app-local debug target: set D4R_AGILITY_ROOT to unpacked official +Microsoft.Direct3D.D3D12 1.619.5. NuGet ZIP SHA256: +`0e9bcf32aac9a79343ede9b21e4864950ee54577e3d8e19bfcdf002bb4e9bfd6`. +On this machine, the public debug interfaces still return SDK_COMPONENT_MISSING +despite app-local SDKLayers loading. Installing Windows Graphics Tools failed +with DISM's component-store-corrupted error. No repair, reboot, security change +or game runtime dependency was introduced. DRED works without that layer. + ## Source baseline (checked 2026-09-29) * d4r: `ed1ab60`, main, 14 commits; https://github.com/countervolts/d4r. diff --git a/scripts/windows/test-d3d12-command.ps1 b/scripts/windows/test-d3d12-command.ps1 new file mode 100644 index 0000000..061945f --- /dev/null +++ b/scripts/windows/test-d3d12-command.ps1 @@ -0,0 +1,52 @@ +param( + [int]$Iterations = 32, + [switch]$NoHooks, + [switch]$SmallSeed, + [switch]$DebugLayer, + [switch]$Warp, + [switch]$NoHip, + [ValidateSet('basic','indirect','indirect-root-reset')][string]$Mode = 'indirect', + [string]$OutputDirectory = 'test-results/d3d12-command-development' +) +$ErrorActionPreference = 'Stop' +$repo = (Resolve-Path "$PSScriptRoot/../..").Path +Push-Location $repo +$names = @('D4R_COMMAND_PROBE_NO_HOOKS','D4R_COMMAND_PROBE_SMALL_SEED','D4R_COMMAND_DEBUG_SDK','D4R_DIAG_DIR','D4R_COMMAND_PROBE_WARP','D4R_COMMAND_PROBE_NO_HIP') +$saved = @{} +foreach ($name in $names) { $saved[$name] = [Environment]::GetEnvironmentVariable($name,'Process') } +try { + if ($Iterations -lt 1 -or $Iterations -gt 1024) { throw 'Iterations must be 1..1024' } + $cmake = "$repo/.tools/python/cmake/data/bin/cmake.exe" + $build = "$repo/build/windows-rdna4-therock" + $hip = "$repo/.tools/therock-10.2.0a20260929/_rocm_sdk_core" + $output = [IO.Path]::GetFullPath((Join-Path $repo $OutputDirectory)) + New-Item -ItemType Directory -Force -Path $output | Out-Null + $target = 'd4r_d3d12_command_probe' + if ($DebugLayer) { + $sdk = "$repo/.tools/agility-1.619.5" + if (!(Test-Path "$sdk/build/native/bin/x64/d3d12SDKLayers.dll")) { throw 'Unpack Microsoft.Direct3D.D3D12 1.619.5 into .tools/agility-1.619.5 for this optional debug probe' } + & $cmake -S . -B $build "-DD4R_AGILITY_ROOT=$sdk" > "$output/configure.log" + if ($LASTEXITCODE) { throw 'Debug probe configuration failed' } + $target = 'd4r_d3d12_command_debug_probe' + } + & $cmake --build $build --target d4r_nvngx_windows $target > "$output/build.log" + if ($LASTEXITCODE) { throw "Build failed: $output/build.log" } + [Environment]::SetEnvironmentVariable($names[0], $(if ($NoHooks) { '1' } else { $null }), 'Process') + [Environment]::SetEnvironmentVariable($names[1], $(if ($SmallSeed) { '1' } else { $null }), 'Process') + [Environment]::SetEnvironmentVariable($names[2], $(if ($DebugLayer) { '1' } else { $null }), 'Process') + [Environment]::SetEnvironmentVariable($names[3], $output, 'Process') + [Environment]::SetEnvironmentVariable($names[4], $(if ($Warp) { '1' } else { $null }), 'Process') + [Environment]::SetEnvironmentVariable($names[5], $(if ($NoHip -or $Warp) { '1' } else { $null }), 'Process') + # PowerShell 5 treats native stderr as ErrorRecord: retain it without + # converting it into a terminating exception, then check the process code. + $ErrorActionPreference = 'Continue' + & "$build/$target.exe" --hip-root $hip --module "$build/d4r_nvngx.dll" --interop-mode $Mode --iterations $Iterations > "$output/stdout.log" 2> "$output/stderr.log" + $code = $LASTEXITCODE + $ErrorActionPreference = 'Stop' + Get-Content "$output/stdout.log", "$output/stderr.log" | Select-String 'D3D12_VALIDATION|DRED|DEVICE_REMOVED|PROBE_GPU|DIFFERENT|FAIL|PASS|installed' + Write-Host "D3D12 command diagnostic logs: $output" + if ($code) { throw "D3D12 probe exit=$code" } +} finally { + foreach ($name in $names) { [Environment]::SetEnvironmentVariable($name,$saved[$name],'Process') } + Pop-Location +} diff --git a/tools/windows/d3d12_command_hooks.cpp b/tools/windows/d3d12_command_hooks.cpp index e4fc2e1..55581df 100644 --- a/tools/windows/d3d12_command_hooks.cpp +++ b/tools/windows/d3d12_command_hooks.cpp @@ -1,4 +1,5 @@ #include "d3d12_command_hooks.h" +#include "d3d12_newer_commands.h" #include #include #include @@ -87,6 +88,40 @@ void attach_method(void* target, void* replacement, void** original, const char* #include "d3d12_command_hooks.generated.h" +static constexpr GUID signature_description_tag = {0xbdda6148,0x46f0,0x4d82,{0xa1,0x90,0x08,0x47,0x7e,0xda,0x32,0x12}}; +using SignatureFn = HRESULT(STDMETHODCALLTYPE*)(ID3D12Device*, const D3D12_COMMAND_SIGNATURE_DESC*, ID3D12RootSignature*, REFIID, void**); +SignatureFn original_signature = nullptr; +HRESULT STDMETHODCALLTYPE hook_signature(ID3D12Device* device, const D3D12_COMMAND_SIGNATURE_DESC* desc, ID3D12RootSignature* root, REFIID iid, void** out) { + const auto result = original_signature(device, desc, root, iid, out); + if (SUCCEEDED(result) && out && *out && desc && desc->NumArgumentDescs <= 4096) try { + ComPtr signature; + if (SUCCEEDED(static_cast(*out)->QueryInterface(IID_PPV_ARGS(signature.GetAddressOf())))) { + std::vector value(sizeof(UINT) + desc->NumArgumentDescs * sizeof(D3D12_INDIRECT_ARGUMENT_DESC)); + std::memcpy(value.data(), &desc->NumArgumentDescs, sizeof(UINT)); + if (desc->NumArgumentDescs) + std::memcpy(value.data() + sizeof(UINT), desc->pArgumentDescs, value.size() - sizeof(UINT)); + const auto tagged = signature->SetPrivateData(signature_description_tag, UINT(value.size()), value.data()); + if (FAILED(tagged)) std::fprintf(stderr, "D4R_SIGNATURE_TRACK_FAILURE result=0x%08x\n", unsigned(tagged)); + } + } catch (const std::exception& failure) { + // Preserve the successful public API call. An untracked signature is + // rejected at a later split instead of unwinding into the application. + std::fprintf(stderr, "D4R_SIGNATURE_TRACK_FAILURE %s\n", failure.what()); + } + return result; +} +void STDMETHODCALLTYPE hook_SetProgram(ID3D12GraphicsCommandList* self, const NewProgramDescription* desc) { + Access access(self); + if (access.recording && desc) { + auto saved = *desc; + // INITIALIZE changes backing memory. Rebinding an already initialized + // work graph in the suffix must preserve its prefix's GPU state. + if (saved.type == 5) saved.work_graph.flags &= ~1u; + access.recording->state.emplace_back([saved](ID3D12GraphicsCommandList* target) { original_SetProgram(target, &saved); }); + } + original_SetProgram(access.target(), desc); +} + using ReleaseFn = ULONG(STDMETHODCALLTYPE*)(IUnknown*); ReleaseFn original_release = nullptr; ULONG STDMETHODCALLTYPE hook_release(IUnknown* self) { @@ -183,7 +218,52 @@ void STDMETHODCALLTYPE hook_ExecuteBundle(ID3D12GraphicsCommandList* self, ID3D1 void STDMETHODCALLTYPE hook_ExecuteIndirect(ID3D12GraphicsCommandList* self, ID3D12CommandSignature* command_signature, UINT max_command_count, ID3D12Resource* arg_buffer, UINT64 arg_buffer_offset, ID3D12Resource* count_buffer, UINT64 count_buffer_offset) { Access access(self); - if (access.recording) access.recording->indirect_state_unknown = true; + if (access.recording) { + UINT bytes = 0; + (void)command_signature->GetPrivateData(signature_description_tag, &bytes, nullptr); + if (bytes < sizeof(UINT) || bytes > sizeof(UINT) + 4096 * sizeof(D3D12_INDIRECT_ARGUMENT_DESC)) { + access.recording->indirect_state_unknown = true; + } else { + std::vector description(bytes); + UINT count = 0; + const auto result = command_signature->GetPrivateData(signature_description_tag, &bytes, description.data()); + std::memcpy(&count, description.data(), sizeof(count)); + if (FAILED(result) || count > 4096 || bytes != sizeof(UINT) + count * sizeof(D3D12_INDIRECT_ARGUMENT_DESC)) access.recording->indirect_state_unknown = true; + else { + std::vector arguments(count); + std::memcpy(arguments.data(), description.data() + sizeof(UINT), count * sizeof(D3D12_INDIRECT_ARGUMENT_DESC)); + bool compute = false; + for (const auto& arg : arguments) compute |= arg.Type == D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH || arg.Type == D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH_RAYS; + // Microsoft documents that only bindings modified by the + // signature are reset to zero/NULL after ExecuteIndirect. + access.recording->state.emplace_back([arguments, compute](ID3D12GraphicsCommandList* target) { + for (const auto& arg : arguments) switch (arg.Type) { + // Public SDK addition after the pinned MinGW header: + // INCREMENTING_CONSTANT has the same first two UINTs. + case static_cast(11): + (compute ? original_SetComputeRoot32BitConstant : original_SetGraphicsRoot32BitConstant) + (target, arg.Constant.RootParameterIndex, 0, arg.Constant.DestOffsetIn32BitValues); break; + case D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT: { + std::vector zeros(arg.Constant.Num32BitValuesToSet); + auto setter = compute ? original_SetComputeRoot32BitConstants : original_SetGraphicsRoot32BitConstants; + setter(target, arg.Constant.RootParameterIndex, UINT(zeros.size()), zeros.data(), arg.Constant.DestOffsetIn32BitValues); break; + } + case D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT_BUFFER_VIEW: + (compute ? original_SetComputeRootConstantBufferView : original_SetGraphicsRootConstantBufferView)(target, arg.ConstantBufferView.RootParameterIndex, 0); break; + case D3D12_INDIRECT_ARGUMENT_TYPE_SHADER_RESOURCE_VIEW: + (compute ? original_SetComputeRootShaderResourceView : original_SetGraphicsRootShaderResourceView)(target, arg.ShaderResourceView.RootParameterIndex, 0); break; + case D3D12_INDIRECT_ARGUMENT_TYPE_UNORDERED_ACCESS_VIEW: + (compute ? original_SetComputeRootUnorderedAccessView : original_SetGraphicsRootUnorderedAccessView)(target, arg.UnorderedAccessView.RootParameterIndex, 0); break; + case D3D12_INDIRECT_ARGUMENT_TYPE_INDEX_BUFFER_VIEW: original_IASetIndexBuffer(target, nullptr); break; + case D3D12_INDIRECT_ARGUMENT_TYPE_VERTEX_BUFFER_VIEW: { + D3D12_VERTEX_BUFFER_VIEW empty{}; original_IASetVertexBuffers(target, arg.VertexBuffer.Slot, 1, &empty); break; + } + default: break; + } + }); + } + } + } original_ExecuteIndirect(access.target(), command_signature, max_command_count, arg_buffer, arg_buffer_offset, count_buffer, count_buffer_offset); } @@ -265,8 +345,13 @@ void install(ID3D12Device* device) { ComPtr newest; const GUID* ids[] = {&IID_ID3D12GraphicsCommandList, &IID_ID3D12GraphicsCommandList1, &IID_ID3D12GraphicsCommandList2, &IID_ID3D12GraphicsCommandList3, &IID_ID3D12GraphicsCommandList4, &IID_ID3D12GraphicsCommandList5, - &IID_ID3D12GraphicsCommandList6, &IID_ID3D12GraphicsCommandList7}; - for (unsigned n = 0; n < 8; ++n) if (SUCCEEDED(list->QueryInterface(*ids[n], reinterpret_cast(newest.ReleaseAndGetAddressOf())))) version = n; + &IID_ID3D12GraphicsCommandList6, &IID_ID3D12GraphicsCommandList7, + &command_list8_iid, &command_list9_iid, &command_list10_iid}; + for (unsigned n = 0; n < 11; ++n) { + ComPtr candidate; + if (SUCCEEDED(list->QueryInterface(*ids[n], reinterpret_cast(candidate.GetAddressOf())))) { newest = std::move(candidate); version = n; } + } + if (newest.Get() != static_cast(list.Get())) throw std::runtime_error("D3D12 command-list versions need a separate interface identity adapter"); auto table = *reinterpret_cast(list.Get()); attach_recording_methods(table, version); attach_method(table[2], reinterpret_cast(hook_release), reinterpret_cast(&original_release), "Release"); @@ -276,6 +361,8 @@ void install(ID3D12Device* device) { // Queue inherits IUnknown, Object, DeviceChild; UpdateTileMappings, // CopyTileMappings precede ExecuteCommandLists in the public SDK. attach_method(queueTable[10], reinterpret_cast(hook_execute), reinterpret_cast(&original_execute), "ExecuteCommandLists"); + auto deviceTable = *reinterpret_cast(device); + attach_method(deviceTable[device_signature_slot], reinterpret_cast(hook_signature), reinterpret_cast(&original_signature), "CreateCommandSignature"); for (const auto& hook : attached) { const auto result = MH_QueueEnableHook(hook.target); if (result != MH_OK) throw std::runtime_error(MH_StatusToString(result)); diff --git a/tools/windows/d3d12_command_hooks.generated.h b/tools/windows/d3d12_command_hooks.generated.h index e68ccda..a7f8dd1 100644 --- a/tools/windows/d3d12_command_hooks.generated.h +++ b/tools/windows/d3d12_command_hooks.generated.h @@ -577,6 +577,47 @@ using Fn_Barrier = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UIN static Fn_Barrier original_Barrier = nullptr; static void STDMETHODCALLTYPE hook_Barrier(ID3D12GraphicsCommandList* self, UINT32 barrier_groups_count, const D3D12_BARRIER_GROUP *barrier_groups); +using Fn_OMSetFrontAndBackStencilRef = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UINT front_stencil, UINT back_stencil); +static Fn_OMSetFrontAndBackStencilRef original_OMSetFrontAndBackStencilRef = nullptr; +static void STDMETHODCALLTYPE hook_OMSetFrontAndBackStencilRef(ID3D12GraphicsCommandList* self, UINT front_stencil, UINT back_stencil) { + Access access(self); + if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) { + original_OMSetFrontAndBackStencilRef(target, front_stencil, back_stencil); + }); + original_OMSetFrontAndBackStencilRef(access.target(), front_stencil, back_stencil); +} + +using Fn_RSSetDepthBias = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, FLOAT depth_bias, FLOAT clamp, FLOAT slope); +static Fn_RSSetDepthBias original_RSSetDepthBias = nullptr; +static void STDMETHODCALLTYPE hook_RSSetDepthBias(ID3D12GraphicsCommandList* self, FLOAT depth_bias, FLOAT clamp, FLOAT slope) { + Access access(self); + if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) { + original_RSSetDepthBias(target, depth_bias, clamp, slope); + }); + original_RSSetDepthBias(access.target(), depth_bias, clamp, slope); +} + +using Fn_IASetIndexBufferStripCutValue = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, D3D12_INDEX_BUFFER_STRIP_CUT_VALUE value); +static Fn_IASetIndexBufferStripCutValue original_IASetIndexBufferStripCutValue = nullptr; +static void STDMETHODCALLTYPE hook_IASetIndexBufferStripCutValue(ID3D12GraphicsCommandList* self, D3D12_INDEX_BUFFER_STRIP_CUT_VALUE value) { + Access access(self); + if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) { + original_IASetIndexBufferStripCutValue(target, value); + }); + original_IASetIndexBufferStripCutValue(access.target(), value); +} + +using Fn_SetProgram = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, const NewProgramDescription* desc); +static Fn_SetProgram original_SetProgram = nullptr; +static void STDMETHODCALLTYPE hook_SetProgram(ID3D12GraphicsCommandList* self, const NewProgramDescription* desc); + +using Fn_DispatchGraph = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, const void* desc); +static Fn_DispatchGraph original_DispatchGraph = nullptr; +static void STDMETHODCALLTYPE hook_DispatchGraph(ID3D12GraphicsCommandList* self, const void* desc) { + Access access(self); + original_DispatchGraph(access.target(), desc); +} + static void attach_recording_methods(void** table, unsigned version) { if (version >= 0) attach_method(table[9], reinterpret_cast(hook_Close), reinterpret_cast(&original_Close), "Close"); if (version >= 0) attach_method(table[10], reinterpret_cast(hook_Reset), reinterpret_cast(&original_Reset), "Reset"); @@ -650,4 +691,11 @@ static void attach_recording_methods(void** table, unsigned version) { if (version >= 5) attach_method(table[78], reinterpret_cast(hook_RSSetShadingRateImage), reinterpret_cast(&original_RSSetShadingRateImage), "RSSetShadingRateImage"); if (version >= 6) attach_method(table[79], reinterpret_cast(hook_DispatchMesh), reinterpret_cast(&original_DispatchMesh), "DispatchMesh"); if (version >= 7) attach_method(table[80], reinterpret_cast(hook_Barrier), reinterpret_cast(&original_Barrier), "Barrier"); + if (version >= 8) attach_method(table[81], reinterpret_cast(hook_OMSetFrontAndBackStencilRef), reinterpret_cast(&original_OMSetFrontAndBackStencilRef), "OMSetFrontAndBackStencilRef"); + if (version >= 9) attach_method(table[82], reinterpret_cast(hook_RSSetDepthBias), reinterpret_cast(&original_RSSetDepthBias), "RSSetDepthBias"); + if (version >= 9) attach_method(table[83], reinterpret_cast(hook_IASetIndexBufferStripCutValue), reinterpret_cast(&original_IASetIndexBufferStripCutValue), "IASetIndexBufferStripCutValue"); + if (version >= 10) attach_method(table[84], reinterpret_cast(hook_SetProgram), reinterpret_cast(&original_SetProgram), "SetProgram"); + if (version >= 10) attach_method(table[85], reinterpret_cast(hook_DispatchGraph), reinterpret_cast(&original_DispatchGraph), "DispatchGraph"); } + +static constexpr unsigned device_signature_slot = 41; \ No newline at end of file diff --git a/tools/windows/d3d12_command_probe.cpp b/tools/windows/d3d12_command_probe.cpp index 97f4f80..50dd8b3 100644 --- a/tools/windows/d3d12_command_probe.cpp +++ b/tools/windows/d3d12_command_probe.cpp @@ -1,12 +1,31 @@ #include "d3d12_external.h" #include #include +#include #include using namespace d4r::diag; using namespace d4r::win; +#if defined(D4R_DIAGNOSTIC_AGILITY) +extern "C" { +__declspec(dllexport) extern const UINT D3D12SDKVersion = 619; +__declspec(dllexport) extern const char* D3D12SDKPath = ".\\debug-d3d12\\"; +} +#endif namespace { constexpr unsigned count = 256, bytes = count * sizeof(uint32_t), seed = 0x12345; +void messages(ID3D12Device* device) { + ComPtr info; + if (FAILED(device->QueryInterface(IID_PPV_ARGS(info.GetAddressOf())))) return; + for (UINT64 i = 0; i < info->GetNumStoredMessagesAllowedByRetrievalFilter(); ++i) { + SIZE_T size = 0; (void)info->GetMessage(i, nullptr, &size); + std::vector buffer(size); + auto* value = reinterpret_cast(buffer.data()); + if (SUCCEEDED(info->GetMessage(i, value, &size))) + std::fprintf(stderr, "D3D12_VALIDATION severity=%u id=%u %s\n", unsigned(value->Severity), unsigned(value->ID), value->pDescription); + } + info->ClearStoredMessages(); +} void drain(ID3D12Device* device, ID3D12CommandQueue* queue) { ComPtr fence; dx(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(fence.GetAddressOf())), "Probe fence"); Handle event; event.value = CreateEventW(nullptr, FALSE, FALSE, nullptr); @@ -45,12 +64,12 @@ void upload(ID3D12Resource* buffer, uint32_t multiplier, uint32_t addend) { for (unsigned i = 0; i < count; ++i) values[i] = i * multiplier + addend; buffer->Unmap(0, nullptr); } -bool verify(ID3D12Resource* buffer, uint32_t multiplier, uint32_t addend) { +bool verify(ID3D12Resource* buffer, uint32_t multiplier, uint32_t addend, uint32_t root_seed = seed) { void* data = nullptr; D3D12_RANGE range{0, bytes}; dx(buffer->Map(0, &range, &data), "Probe verification map"); const auto* values = static_cast(data); bool passed = true; - for (unsigned i = 0; i < count; ++i) if (values[i] != i * multiplier + addend + seed) { - std::fprintf(stderr, "COMMAND_DIFFERENT index=%u expected=%u actual=%u\n", i, i * multiplier + addend + seed, values[i]); + for (unsigned i = 0; i < count; ++i) if (values[i] != i * multiplier + addend + root_seed) { + std::fprintf(stderr, "COMMAND_DIFFERENT index=%u expected=%u actual=%u\n", i, i * multiplier + addend + root_seed, values[i]); passed = false; break; } D3D12_RANGE noWrite{}; buffer->Unmap(0, &noWrite); return passed; @@ -61,6 +80,7 @@ struct Callback { ID3D12Resource* output; ID3D12Resource* replacement; ID3D12Resource* readback; + uint32_t expected_seed = seed; bool prefix_verified = false; }; void WINAPI boundary(ID3D12CommandQueue* queue, void* context) { @@ -73,24 +93,45 @@ void WINAPI boundary(ID3D12CommandQueue* queue, void* context) { list.list->CopyResource(callback.input, callback.replacement); transition(list.list.Get(), callback.input, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); list.submit(callback.device, queue); - callback.prefix_verified = verify(callback.readback, 17, 10); + callback.prefix_verified = verify(callback.readback, 17, 10, callback.expected_seed); } } int main(int argc, char** argv) { start(); + ComPtr device; + std::unique_ptr debug_layers; try { Args args(argc, argv); if (args.module.empty()) throw std::runtime_error("--module d4r_nvngx.dll is required; NVIDIA DLLs are not needed"); - HipApi hip(args.hip_root); hipDeviceProp_t props{}; hip.select_gfx1201(args.device, props); + if (std::getenv("D4R_COMMAND_DEBUG_SDK")) { + wchar_t exe[32768]{}; GetModuleFileNameW(nullptr, exe, 32768); + debug_layers = std::make_unique(std::filesystem::path(exe).parent_path() / L"debug-d3d12/d3d12SDKLayers.dll"); + ComPtr debug; + auto get_interface = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"d3d12.dll"), "D3D12GetInterface")); + if (!get_interface) throw std::runtime_error("D3D12GetInterface is unavailable"); + dx(get_interface(CLSID_D3D12Debug, IID_PPV_ARGS(debug.GetAddressOf())), "Diagnostic debug layer(public CLSID)"); + debug->EnableDebugLayer(); + } + ComPtr dred_settings; + if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(dred_settings.GetAddressOf())))) { + dred_settings->SetAutoBreadcrumbsEnablement(D3D12_DRED_ENABLEMENT_FORCED_ON); + dred_settings->SetPageFaultEnablement(D3D12_DRED_ENABLEMENT_FORCED_ON); + } + std::unique_ptr hip; + hipDeviceProp_t props{}; + const bool no_hip = std::getenv("D4R_COMMAND_PROBE_NO_HIP") != nullptr; + if (!no_hip) { hip = std::make_unique(args.hip_root); hip->select_gfx1201(args.device, props); } ComPtr factory; dx(CreateDXGIFactory1(IID_PPV_ARGS(factory.GetAddressOf())), "Probe DXGI"); ComPtr adapter; for (UINT i = 0; ; ++i) { dx(factory->EnumAdapters1(i, adapter.ReleaseAndGetAddressOf()), "Probe adapter"); DXGI_ADAPTER_DESC1 desc{}; dx(adapter->GetDesc1(&desc), "Probe adapter desc"); - if (!std::memcmp(&desc.AdapterLuid, props.luid, sizeof(LUID))) break; + if (no_hip ? desc.VendorId == 0x1002 : !std::memcmp(&desc.AdapterLuid, props.luid, sizeof(LUID))) break; + } + if (std::getenv("D4R_COMMAND_PROBE_WARP")) { + dx(factory->EnumWarpAdapter(IID_PPV_ARGS(adapter.ReleaseAndGetAddressOf())), "Probe WARP adapter"); } - ComPtr device; dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(device.GetAddressOf())), "Probe D3D12"); ComPtr queue; D3D12_COMMAND_QUEUE_DESC qd{}; qd.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; @@ -118,9 +159,37 @@ int main(int argc, char** argv) { using Install = unsigned(*)(ID3D12Device*); using Record = unsigned(*)(ID3D12GraphicsCommandList*, void(WINAPI*)(ID3D12CommandQueue*, void*), void*); using Live = unsigned long long(*)(); - if (shim.symbol("d4r_D3D12_InstallCommandBackend")(device.Get()) != 1) throw std::runtime_error("Command backend installation failed"); + const bool no_hooks = std::getenv("D4R_COMMAND_PROBE_NO_HOOKS") != nullptr; + if (!no_hooks && shim.symbol("d4r_D3D12_InstallCommandBackend")(device.Get()) != 1) throw std::runtime_error("Command backend installation failed"); auto record = shim.symbol("d4r_D3D12_RecordDiagnosticBoundary"); auto live = shim.symbol("d4r_D3D12_LiveRecordings"); + const bool root_indirect = args.interop_mode == "indirect-root-reset"; + const bool indirect_enabled = args.interop_mode != "basic"; + if (args.interop_mode != "direct" && args.interop_mode != "basic" && args.interop_mode != "indirect" && !root_indirect) + throw std::runtime_error("Command probe mode must be basic, indirect, or indirect-root-reset"); + D3D12_INDIRECT_ARGUMENT_DESC arguments[2]{}; + arguments[0].Type = D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT; + arguments[0].Constant.RootParameterIndex = 2; + arguments[0].Constant.Num32BitValuesToSet = 1; + arguments[1].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH; + D3D12_COMMAND_SIGNATURE_DESC indirect_desc{}; + indirect_desc.ByteStride = (root_indirect ? 4 : 3) * sizeof(uint32_t); + indirect_desc.NumArgumentDescs = root_indirect ? 2 : 1; + indirect_desc.pArgumentDescs = arguments + (root_indirect ? 0 : 1); + ComPtr indirect; + dx(device->CreateCommandSignature(&indirect_desc, root_indirect ? root.Get() : nullptr, IID_PPV_ARGS(indirect.GetAddressOf())), "Probe indirect signature"); + auto indirect_upload = make_buffer(device.Get(), 4 * sizeof(uint32_t), D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ); + auto indirect_buffer = make_buffer(device.Get(), 4 * sizeof(uint32_t), D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST); + void* mapped = nullptr; D3D12_RANGE no_read{}; + dx(indirect_upload->Map(0, &no_read, &mapped), "Probe indirect arguments"); + const uint32_t indirect_seed = std::getenv("D4R_COMMAND_PROBE_SMALL_SEED") ? 1u : seed; + const uint32_t dispatch[] = {indirect_seed, count / 64, 1, 1}; + std::memcpy(mapped, dispatch, sizeof(dispatch)); indirect_upload->Unmap(0, nullptr); + { + NativeList copy(device.Get()); copy.list->CopyResource(indirect_buffer.Get(), indirect_upload.Get()); + transition(copy.list.Get(), indirect_buffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT); + copy.submit(device.Get(), queue.Get()); + } for (unsigned iteration = 0; iteration < args.iterations; ++iteration) { auto input = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST); auto output = output_buffer(device.Get()); @@ -128,6 +197,9 @@ int main(int argc, char** argv) { auto second = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_UPLOAD, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_GENERIC_READ); auto readback = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST); auto prefix = make_buffer(device.Get(), bytes, D3D12_HEAP_TYPE_READBACK, D3D12_HEAP_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST); + input->SetName(L"Probe input"); output->SetName(L"Probe output"); indirect_buffer->SetName(L"Probe indirect"); + std::printf("PROBE_GPU iteration=%u input=0x%llx output=0x%llx indirect=0x%llx\n", iteration, + input->GetGPUVirtualAddress(), output->GetGPUVirtualAddress(), indirect_buffer->GetGPUVirtualAddress()); upload(first.Get(), 17, 10); upload(second.Get(), 25, 100); { NativeList a(device.Get()), b(device.Get()), c(device.Get()); @@ -140,24 +212,71 @@ int main(int argc, char** argv) { uint32_t temporary = seed; b.list->SetComputeRoot32BitConstants(2, 1, &temporary, 0); temporary = 0xdeadbeef; // Deep-copy check: replay must retain Seed. + const uint32_t expected_seed = root_indirect && iteration % 2 ? 0 : seed; + if (indirect_enabled && iteration % 2) { + b.list->ExecuteIndirect(indirect.Get(), 1, indirect_buffer.Get(), root_indirect ? 0 : sizeof(uint32_t), nullptr, 0); + D3D12_RESOURCE_BARRIER uav{}; uav.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; uav.UAV.pResource = output.Get(); + b.list->ResourceBarrier(1, &uav); + // Only the root-mutating signature resets Seed to zero. + // A dispatch-only signature must preserve every binding. + } b.list->Dispatch(count / 64, 1, 1); - Callback callback{device.Get(), input.Get(), output.Get(), second.Get(), prefix.Get()}; - if (record(b.list.Get(), boundary, &callback) != 1) throw std::runtime_error("Record split failed"); + Callback callback{device.Get(), input.Get(), output.Get(), second.Get(), prefix.Get(), expected_seed}; + if (no_hooks) { + dx(b.list->Close(), "Close native prefix"); + messages(device.Get()); + ID3D12CommandList* prefix_batch[] = {a.list.Get(), b.list.Get()}; + queue->ExecuteCommandLists(2, prefix_batch); drain(device.Get(), queue.Get()); + boundary(queue.Get(), &callback); + dx(b.allocator->Reset(), "Reset native prefix allocator"); + dx(b.list->Reset(b.allocator.Get(), pso.Get()), "Reset native suffix"); + b.list->SetComputeRootSignature(root.Get()); + b.list->SetComputeRootUnorderedAccessView(0, output->GetGPUVirtualAddress()); + b.list->SetComputeRootShaderResourceView(1, input->GetGPUVirtualAddress()); + b.list->SetComputeRoot32BitConstant(2, expected_seed, 0); + } else if (record(b.list.Get(), boundary, &callback) != 1) throw std::runtime_error("Record split failed"); b.list->Dispatch(count / 64, 1, 1); // No rebinding after the split. dx(b.list->Close(), "Close logical list B"); transition(c.list.Get(), output.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE); c.list->CopyResource(readback.Get(), output.Get()); transition(c.list.Get(), output.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); dx(c.list->Close(), "Close consumer C"); + messages(device.Get()); ID3D12CommandList* batch[] = {a.list.Get(), b.list.Get(), c.list.Get()}; - queue->ExecuteCommandLists(3, batch); drain(device.Get(), queue.Get()); - if (!callback.prefix_verified || !verify(readback.Get(), 25, 100)) throw std::runtime_error("Queue order or suffix root state mismatch"); + queue->ExecuteCommandLists(no_hooks ? 2 : 3, batch + (no_hooks ? 1 : 0)); drain(device.Get(), queue.Get()); + if (!callback.prefix_verified || !verify(readback.Get(), 25, 100, expected_seed)) throw std::runtime_error("Queue order or suffix root state mismatch"); } if (live() != 0) throw std::runtime_error("D3D12 recording metadata leaked after command-list Release"); } - std::printf("PASS D3D12_COMMAND_BACKEND architecture=gfx1201 iterations=%u batch_order=1 root_state=1 deep_copy=1 live_recordings=0 frame_age=0\n", args.iterations); + const bool warp = std::getenv("D4R_COMMAND_PROBE_WARP") != nullptr; + std::printf("PASS D3D12_COMMAND_BACKEND adapter=%s architecture=%s backend=%u iterations=%u batch_order=1 root_state=1 deep_copy=1 indirect=%u indirect_reset=%u live_recordings=0 frame_age=0\n", + warp ? "WARP" : "AMD", warp ? "software" : no_hip ? "not_queried" : "gfx1201", !no_hooks, + args.iterations, indirect_enabled, root_indirect); return 0; } catch (const std::exception& failure) { - std::fprintf(stderr, "FAIL D3D12_COMMAND_BACKEND %s\n", failure.what()); loaded_modules(); return 4; + std::fprintf(stderr, "FAIL D3D12_COMMAND_BACKEND %s\n", failure.what()); + if (device) { + messages(device.Get()); + std::fprintf(stderr, "DEVICE_REMOVED result=0x%08x\n", unsigned(device->GetDeviceRemovedReason())); + ComPtr dred; + if (SUCCEEDED(device->QueryInterface(IID_PPV_ARGS(dred.GetAddressOf())))) { + D3D12_DRED_AUTO_BREADCRUMBS_OUTPUT breadcrumbs{}; + if (SUCCEEDED(dred->GetAutoBreadcrumbsOutput(&breadcrumbs))) + for (auto* node = breadcrumbs.pHeadAutoBreadcrumbNode; node; node = node->pNext) { + UINT completed = node->pLastBreadcrumbValue ? *node->pLastBreadcrumbValue : 0; + std::fprintf(stderr, "DRED list=%p completed=%u total=%u\n", node->pCommandList, completed, node->BreadcrumbCount); + for (UINT i = completed > 2 ? completed - 2 : 0; i < std::min(node->BreadcrumbCount, completed + 3); ++i) + std::fprintf(stderr, "DRED command=%u operation=%u\n", i, unsigned(node->pCommandHistory[i])); + } + D3D12_DRED_PAGE_FAULT_OUTPUT fault{}; + if (SUCCEEDED(dred->GetPageFaultAllocationOutput(&fault))) + std::fprintf(stderr, "DRED fault_address=0x%llx\n", fault.PageFaultVA); + for (auto* allocation = fault.pHeadExistingAllocationNode; allocation; allocation = allocation->pNext) + std::fprintf(stderr, "DRED existing=%ls type=%u\n", allocation->ObjectNameW ? allocation->ObjectNameW : L"(unnamed)", unsigned(allocation->AllocationType)); + for (auto* allocation = fault.pHeadRecentFreedAllocationNode; allocation; allocation = allocation->pNext) + std::fprintf(stderr, "DRED freed=%ls type=%u\n", allocation->ObjectNameW ? allocation->ObjectNameW : L"(unnamed)", unsigned(allocation->AllocationType)); + } + } + loaded_modules(); return 4; } } diff --git a/tools/windows/d3d12_newer_commands.h b/tools/windows/d3d12_newer_commands.h new file mode 100644 index 0000000..b85db5e --- /dev/null +++ b/tools/windows/d3d12_newer_commands.h @@ -0,0 +1,46 @@ +#pragma once +// Copyright (c) Microsoft Corporation. +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// ABI subset of Microsoft's MIT-licensed DirectX-Headers, commit adbd6f3b. +// No pointers to CPU-owned nested structures exist in SetProgram's descriptor. +// The generic/raytracing variants use only the ProgramIdentifier field. +namespace d4r::win::commands { +struct NewWorkGraphDescription { + UINT64 program_identifier[4]; + UINT flags; + D3D12_GPU_VIRTUAL_ADDRESS_RANGE backing_memory; + D3D12_GPU_VIRTUAL_ADDRESS_RANGE_AND_STRIDE root_arguments; +}; +struct NewProgramDescription { + UINT type; + union { + UINT64 generic_program_identifier[4]; + UINT64 raytracing_program_identifier[4]; + NewWorkGraphDescription work_graph; + }; +}; +static_assert(sizeof(NewWorkGraphDescription) == 80 && sizeof(NewProgramDescription) == 88, + "Public Windows x64 SetProgram ABI"); +inline constexpr GUID command_list8_iid = {0xee936ef9,0x599d,0x4d28,{0x93,0x8e,0x23,0xc4,0xad,0x05,0xce,0x51}}; +inline constexpr GUID command_list9_iid = {0x34ed2808,0xffe6,0x4c2b,{0xb1,0x1a,0xca,0xbd,0x2b,0x0c,0x59,0xe1}}; +inline constexpr GUID command_list10_iid = {0x7013c015,0xd161,0x4b63,{0xa0,0x8c,0x23,0x85,0x52,0xdd,0x8a,0xcc}}; +} diff --git a/tools/windows/generate_d3d12_hooks.py b/tools/windows/generate_d3d12_hooks.py index d6a6e5f..f85669b 100644 --- a/tools/windows/generate_d3d12_hooks.py +++ b/tools/windows/generate_d3d12_hooks.py @@ -1,7 +1,7 @@ """Generate typed command forwarding from public MinGW D3D12 SDK declarations. Run with --header --output d3d12_command_hooks.generated.h. -Every recording method through CommandList7 is forwarded. State setters copy +Every recording method through CommandList10 is forwarded. State setters copy their host arguments; COM object arguments retain a reference. SDK slot numbers are derived from the public interface inheritance, never a driver binary. """ @@ -9,6 +9,17 @@ import argparse import pathlib import re +# Public additions verified against Microsoft's DirectX-Headers adbd6f3b. +# The old MinGW SDK has the needed scalar types but lacks the work-graph +# descriptor names; NewProgramDescription is a typed public-ABI mirror. +NEWER = [ + (8, 'OMSetFrontAndBackStencilRef', 'UINT front_stencil, UINT back_stencil'), + (9, 'RSSetDepthBias', 'FLOAT depth_bias, FLOAT clamp, FLOAT slope'), + (9, 'IASetIndexBufferStripCutValue', 'D3D12_INDEX_BUFFER_STRIP_CUT_VALUE value'), + (10, 'SetProgram', 'const NewProgramDescription* desc'), + (10, 'DispatchGraph', 'const void* desc'), +] + POINTER_COUNTS = { 'RSSetViewports': {'viewports': 'viewport_count'}, 'RSSetScissorRects': {'rects': 'rect_count'}, @@ -89,7 +100,26 @@ def generate(source): registrations[-1] = f' if (version >= {version})' + registrations[-1][3:] slot += 1 lines.append('') + for version, name, arguments in NEWER: + declarations = arguments.split(', ') + names = [re.search(r'(\w+)$', argument)[1] for argument in declarations] + signature = ', '.join(['ID3D12GraphicsCommandList* self'] + declarations) + lines += [f'using Fn_{name} = void(STDMETHODCALLTYPE*)({signature});', f'static Fn_{name} original_{name} = nullptr;'] + if name == 'SetProgram': + lines.append(f'static void STDMETHODCALLTYPE hook_{name}({signature});') + else: + lines += [f'static void STDMETHODCALLTYPE hook_{name}({signature}) {{', ' Access access(self);'] + if name != 'DispatchGraph': + lines += [' if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) {', + f' original_{name}(' + ', '.join(['target'] + names) + ');', ' });'] + lines += [f' original_{name}(' + ', '.join(['access.target()'] + names) + ');', '}'] + registrations.append(f' if (version >= {version}) attach_method(table[{slot}], reinterpret_cast(hook_{name}), reinterpret_cast(&original_{name}), "{name}");') + slot += 1 + lines.append('') lines += ['static void attach_recording_methods(void** table, unsigned version) {'] + registrations + ['}', ''] + device = re.search(r'typedef struct ID3D12DeviceVtbl \{(.*?)\n\} ID3D12DeviceVtbl;', source, re.S)[1] + fields = list(dict.fromkeys(re.findall(r'\(STDMETHODCALLTYPE \*(\w+)\)', device))) + lines.append(f'static constexpr unsigned device_signature_slot = {fields.index("CreateCommandSignature")};') return '\n'.join(lines)