Track Windows D3D12 indirect state and newer command interfaces

This commit is contained in:
Codex committed 2026-10-01 14:15:01 +03:00
1 parent 63607298ea
commit 312c6bb50d
8 files changed
+478 -24

No files matched your search

+19 -1
View File
@@ -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_dlss_probe PRIVATE d3d12 dxgi dxguid)
target_link_libraries(d4r_d3d12_command_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) 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_hip_gfx1201_probe d4r_probe_code_object)
add_dependencies(d4r_gfx12_wmma_probe d4r_wmma_code_object) add_dependencies(d4r_gfx12_wmma_probe d4r_wmma_code_object)
add_dependencies(d4r_k_module_probe d4r_native_gfx1201_compile) 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) install(FILES docs/windows-rdna4-port.md DESTINATION docs)
include(CTest) include(CTest)
add_test(NAME d3d12_command_backend COMMAND d4r_d3d12_command_probe add_test(NAME d3d12_command_backend COMMAND d4r_d3d12_command_probe
--hip-root "${D4R_HIP_ROOT}" --module "$<TARGET_FILE:d4r_nvngx_windows>" --iterations 32) --hip-root "${D4R_HIP_ROOT}" --module "$<TARGET_FILE:d4r_nvngx_windows>" --interop-mode indirect --iterations 32)
set_tests_properties(d3d12_command_backend PROPERTIES TIMEOUT 120 LABELS "hardware;d3d12;command_lists;lifetime") 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 "$<TARGET_FILE:d4r_nvngx_windows>" --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 add_test(NAME hip_gfx1201 COMMAND d4r_hip_gfx1201_probe
--hip-root "${D4R_HIP_ROOT}" --module "${probe_object}") --hip-root "${D4R_HIP_ROOT}" --module "${probe_object}")
set_tests_properties(hip_gfx1201 PROPERTIES TIMEOUT 90 LABELS "hardware;gfx1201") set_tests_properties(hip_gfx1201 PROPERTIES TIMEOUT 90 LABELS "hardware;gfx1201")
+59 -5
View File
@@ -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 [MinHook 1.3.4](https://github.com/TsudaKageyu/minhook/releases/tag/v1.3.4), commit
`c3fcafdc10146beb5919319d0683e44e3c30d537`. No NVIDIA instructions, proprietary `c3fcafdc10146beb5919319d0683e44e3c30d537`. No NVIDIA instructions, proprietary
DLL contents or private driver interfaces are patched. The typed forwarding DLL contents or private driver interfaces are patched. The typed forwarding
code is generated from public SDK CommandList0..7 declarations. The additional code is generated from public SDK CommandList0..7 declarations, with typed
CommandList8..10 declarations were audited in Microsoft's current public CommandList8..10 additions audited in Microsoft's current public
DirectX-Headers, `adbd6f3ba40795c46a8d0f33af00bcb57ff0f0a4`; coverage of those DirectX-Headers, `adbd6f3ba40795c46a8d0f33af00bcb57ff0f0a4`. The AMD runtime
newer methods is the next implementation step. 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 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 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 Enable this backend with `D4R_D3D12_COMMAND_BACKEND=1`; the diagnostic runner
sets it through `-CommandListBackend`. Before game testing, remaining gates are 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. actual game texture formats and interaction with OptiScaler's own state hooks.
Closed lists, active render/query scopes, GPU predication and unknown indirect Closed lists, active render/query scopes, GPU predication and unknown indirect
state are currently explicitly rejected instead of guessing their semantics. 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) ## Source baseline (checked 2026-09-29)
* d4r: `ed1ab60`, main, 14 commits; https://github.com/countervolts/d4r. * d4r: `ed1ab60`, main, 14 commits; https://github.com/countervolts/d4r.
+52
View File
@@ -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
}
+90 -3
View File
@@ -1,4 +1,5 @@
#include "d3d12_command_hooks.h" #include "d3d12_command_hooks.h"
#include "d3d12_newer_commands.h"
#include <MinHook.h> #include <MinHook.h>
#include <atomic> #include <atomic>
#include <memory> #include <memory>
@@ -87,6 +88,40 @@ void attach_method(void* target, void* replacement, void** original, const char*
#include "d3d12_command_hooks.generated.h" #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<ID3D12CommandSignature> signature;
if (SUCCEEDED(static_cast<IUnknown*>(*out)->QueryInterface(IID_PPV_ARGS(signature.GetAddressOf())))) {
std::vector<uint8_t> 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*); using ReleaseFn = ULONG(STDMETHODCALLTYPE*)(IUnknown*);
ReleaseFn original_release = nullptr; ReleaseFn original_release = nullptr;
ULONG STDMETHODCALLTYPE hook_release(IUnknown* self) { 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, 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) { UINT max_command_count, ID3D12Resource* arg_buffer, UINT64 arg_buffer_offset, ID3D12Resource* count_buffer, UINT64 count_buffer_offset) {
Access access(self); 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<uint8_t> 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<D3D12_INDIRECT_ARGUMENT_DESC> 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<D3D12_INDIRECT_ARGUMENT_TYPE>(11):
(compute ? original_SetComputeRoot32BitConstant : original_SetGraphicsRoot32BitConstant)
(target, arg.Constant.RootParameterIndex, 0, arg.Constant.DestOffsetIn32BitValues); break;
case D3D12_INDIRECT_ARGUMENT_TYPE_CONSTANT: {
std::vector<UINT> 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); 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<IUnknown> newest; ComPtr<IUnknown> newest;
const GUID* ids[] = {&IID_ID3D12GraphicsCommandList, &IID_ID3D12GraphicsCommandList1, &IID_ID3D12GraphicsCommandList2, const GUID* ids[] = {&IID_ID3D12GraphicsCommandList, &IID_ID3D12GraphicsCommandList1, &IID_ID3D12GraphicsCommandList2,
&IID_ID3D12GraphicsCommandList3, &IID_ID3D12GraphicsCommandList4, &IID_ID3D12GraphicsCommandList5, &IID_ID3D12GraphicsCommandList3, &IID_ID3D12GraphicsCommandList4, &IID_ID3D12GraphicsCommandList5,
&IID_ID3D12GraphicsCommandList6, &IID_ID3D12GraphicsCommandList7}; &IID_ID3D12GraphicsCommandList6, &IID_ID3D12GraphicsCommandList7,
for (unsigned n = 0; n < 8; ++n) if (SUCCEEDED(list->QueryInterface(*ids[n], reinterpret_cast<void**>(newest.ReleaseAndGetAddressOf())))) version = n; &command_list8_iid, &command_list9_iid, &command_list10_iid};
for (unsigned n = 0; n < 11; ++n) {
ComPtr<IUnknown> candidate;
if (SUCCEEDED(list->QueryInterface(*ids[n], reinterpret_cast<void**>(candidate.GetAddressOf())))) { newest = std::move(candidate); version = n; }
}
if (newest.Get() != static_cast<IUnknown*>(list.Get())) throw std::runtime_error("D3D12 command-list versions need a separate interface identity adapter");
auto table = *reinterpret_cast<void***>(list.Get()); auto table = *reinterpret_cast<void***>(list.Get());
attach_recording_methods(table, version); attach_recording_methods(table, version);
attach_method(table[2], reinterpret_cast<void*>(hook_release), reinterpret_cast<void**>(&original_release), "Release"); attach_method(table[2], reinterpret_cast<void*>(hook_release), reinterpret_cast<void**>(&original_release), "Release");
@@ -276,6 +361,8 @@ void install(ID3D12Device* device) {
// Queue inherits IUnknown, Object, DeviceChild; UpdateTileMappings, // Queue inherits IUnknown, Object, DeviceChild; UpdateTileMappings,
// CopyTileMappings precede ExecuteCommandLists in the public SDK. // CopyTileMappings precede ExecuteCommandLists in the public SDK.
attach_method(queueTable[10], reinterpret_cast<void*>(hook_execute), reinterpret_cast<void**>(&original_execute), "ExecuteCommandLists"); attach_method(queueTable[10], reinterpret_cast<void*>(hook_execute), reinterpret_cast<void**>(&original_execute), "ExecuteCommandLists");
auto deviceTable = *reinterpret_cast<void***>(device);
attach_method(deviceTable[device_signature_slot], reinterpret_cast<void*>(hook_signature), reinterpret_cast<void**>(&original_signature), "CreateCommandSignature");
for (const auto& hook : attached) { for (const auto& hook : attached) {
const auto result = MH_QueueEnableHook(hook.target); const auto result = MH_QueueEnableHook(hook.target);
if (result != MH_OK) throw std::runtime_error(MH_StatusToString(result)); if (result != MH_OK) throw std::runtime_error(MH_StatusToString(result));
@@ -577,6 +577,47 @@ using Fn_Barrier = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UIN
static Fn_Barrier original_Barrier = nullptr; static Fn_Barrier original_Barrier = nullptr;
static void STDMETHODCALLTYPE hook_Barrier(ID3D12GraphicsCommandList* self, UINT32 barrier_groups_count, const D3D12_BARRIER_GROUP *barrier_groups); 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) { static void attach_recording_methods(void** table, unsigned version) {
if (version >= 0) attach_method(table[9], reinterpret_cast<void*>(hook_Close), reinterpret_cast<void**>(&original_Close), "Close"); if (version >= 0) attach_method(table[9], reinterpret_cast<void*>(hook_Close), reinterpret_cast<void**>(&original_Close), "Close");
if (version >= 0) attach_method(table[10], reinterpret_cast<void*>(hook_Reset), reinterpret_cast<void**>(&original_Reset), "Reset"); if (version >= 0) attach_method(table[10], reinterpret_cast<void*>(hook_Reset), reinterpret_cast<void**>(&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<void*>(hook_RSSetShadingRateImage), reinterpret_cast<void**>(&original_RSSetShadingRateImage), "RSSetShadingRateImage"); if (version >= 5) attach_method(table[78], reinterpret_cast<void*>(hook_RSSetShadingRateImage), reinterpret_cast<void**>(&original_RSSetShadingRateImage), "RSSetShadingRateImage");
if (version >= 6) attach_method(table[79], reinterpret_cast<void*>(hook_DispatchMesh), reinterpret_cast<void**>(&original_DispatchMesh), "DispatchMesh"); if (version >= 6) attach_method(table[79], reinterpret_cast<void*>(hook_DispatchMesh), reinterpret_cast<void**>(&original_DispatchMesh), "DispatchMesh");
if (version >= 7) attach_method(table[80], reinterpret_cast<void*>(hook_Barrier), reinterpret_cast<void**>(&original_Barrier), "Barrier"); if (version >= 7) attach_method(table[80], reinterpret_cast<void*>(hook_Barrier), reinterpret_cast<void**>(&original_Barrier), "Barrier");
if (version >= 8) attach_method(table[81], reinterpret_cast<void*>(hook_OMSetFrontAndBackStencilRef), reinterpret_cast<void**>(&original_OMSetFrontAndBackStencilRef), "OMSetFrontAndBackStencilRef");
if (version >= 9) attach_method(table[82], reinterpret_cast<void*>(hook_RSSetDepthBias), reinterpret_cast<void**>(&original_RSSetDepthBias), "RSSetDepthBias");
if (version >= 9) attach_method(table[83], reinterpret_cast<void*>(hook_IASetIndexBufferStripCutValue), reinterpret_cast<void**>(&original_IASetIndexBufferStripCutValue), "IASetIndexBufferStripCutValue");
if (version >= 10) attach_method(table[84], reinterpret_cast<void*>(hook_SetProgram), reinterpret_cast<void**>(&original_SetProgram), "SetProgram");
if (version >= 10) attach_method(table[85], reinterpret_cast<void*>(hook_DispatchGraph), reinterpret_cast<void**>(&original_DispatchGraph), "DispatchGraph");
} }
static constexpr unsigned device_signature_slot = 41;
+133 -14
View File
@@ -1,12 +1,31 @@
#include "d3d12_external.h" #include "d3d12_external.h"
#include <dxgi1_6.h> #include <dxgi1_6.h>
#include <d3dcompiler.h> #include <d3dcompiler.h>
#include <d3d12sdklayers.h>
#include <array> #include <array>
using namespace d4r::diag; using namespace d4r::diag;
using namespace d4r::win; 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 { namespace {
constexpr unsigned count = 256, bytes = count * sizeof(uint32_t), seed = 0x12345; constexpr unsigned count = 256, bytes = count * sizeof(uint32_t), seed = 0x12345;
void messages(ID3D12Device* device) {
ComPtr<ID3D12InfoQueue> 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<uint8_t> buffer(size);
auto* value = reinterpret_cast<D3D12_MESSAGE*>(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) { void drain(ID3D12Device* device, ID3D12CommandQueue* queue) {
ComPtr<ID3D12Fence> fence; dx(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(fence.GetAddressOf())), "Probe fence"); ComPtr<ID3D12Fence> 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); 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; for (unsigned i = 0; i < count; ++i) values[i] = i * multiplier + addend;
buffer->Unmap(0, nullptr); 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"); void* data = nullptr; D3D12_RANGE range{0, bytes}; dx(buffer->Map(0, &range, &data), "Probe verification map");
const auto* values = static_cast<const uint32_t*>(data); const auto* values = static_cast<const uint32_t*>(data);
bool passed = true; bool passed = true;
for (unsigned i = 0; i < count; ++i) if (values[i] != i * multiplier + addend + seed) { 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 + seed, values[i]); std::fprintf(stderr, "COMMAND_DIFFERENT index=%u expected=%u actual=%u\n", i, i * multiplier + addend + root_seed, values[i]);
passed = false; break; passed = false; break;
} }
D3D12_RANGE noWrite{}; buffer->Unmap(0, &noWrite); return passed; D3D12_RANGE noWrite{}; buffer->Unmap(0, &noWrite); return passed;
@@ -61,6 +80,7 @@ struct Callback {
ID3D12Resource* output; ID3D12Resource* output;
ID3D12Resource* replacement; ID3D12Resource* replacement;
ID3D12Resource* readback; ID3D12Resource* readback;
uint32_t expected_seed = seed;
bool prefix_verified = false; bool prefix_verified = false;
}; };
void WINAPI boundary(ID3D12CommandQueue* queue, void* context) { 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); 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); transition(list.list.Get(), callback.input, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE);
list.submit(callback.device, queue); 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) { int main(int argc, char** argv) {
start(); start();
ComPtr<ID3D12Device> device;
std::unique_ptr<Library> debug_layers;
try { try {
Args args(argc, argv); Args args(argc, argv);
if (args.module.empty()) throw std::runtime_error("--module d4r_nvngx.dll is required; NVIDIA DLLs are not needed"); 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<Library>(std::filesystem::path(exe).parent_path() / L"debug-d3d12/d3d12SDKLayers.dll");
ComPtr<ID3D12Debug> debug;
auto get_interface = reinterpret_cast<decltype(&D3D12GetInterface)>(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<ID3D12DeviceRemovedExtendedDataSettings> 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<HipApi> hip;
hipDeviceProp_t props{};
const bool no_hip = std::getenv("D4R_COMMAND_PROBE_NO_HIP") != nullptr;
if (!no_hip) { hip = std::make_unique<HipApi>(args.hip_root); hip->select_gfx1201(args.device, props); }
ComPtr<IDXGIFactory4> factory; dx(CreateDXGIFactory1(IID_PPV_ARGS(factory.GetAddressOf())), "Probe DXGI"); ComPtr<IDXGIFactory4> factory; dx(CreateDXGIFactory1(IID_PPV_ARGS(factory.GetAddressOf())), "Probe DXGI");
ComPtr<IDXGIAdapter1> adapter; ComPtr<IDXGIAdapter1> adapter;
for (UINT i = 0; ; ++i) { for (UINT i = 0; ; ++i) {
dx(factory->EnumAdapters1(i, adapter.ReleaseAndGetAddressOf()), "Probe adapter"); dx(factory->EnumAdapters1(i, adapter.ReleaseAndGetAddressOf()), "Probe adapter");
DXGI_ADAPTER_DESC1 desc{}; dx(adapter->GetDesc1(&desc), "Probe adapter desc"); 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<ID3D12Device> device;
dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(device.GetAddressOf())), "Probe D3D12"); dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(device.GetAddressOf())), "Probe D3D12");
ComPtr<ID3D12CommandQueue> queue; ComPtr<ID3D12CommandQueue> queue;
D3D12_COMMAND_QUEUE_DESC qd{}; qd.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; 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 Install = unsigned(*)(ID3D12Device*);
using Record = unsigned(*)(ID3D12GraphicsCommandList*, void(WINAPI*)(ID3D12CommandQueue*, void*), void*); using Record = unsigned(*)(ID3D12GraphicsCommandList*, void(WINAPI*)(ID3D12CommandQueue*, void*), void*);
using Live = unsigned long long(*)(); using Live = unsigned long long(*)();
if (shim.symbol<Install>("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<Install>("d4r_D3D12_InstallCommandBackend")(device.Get()) != 1) throw std::runtime_error("Command backend installation failed");
auto record = shim.symbol<Record>("d4r_D3D12_RecordDiagnosticBoundary"); auto record = shim.symbol<Record>("d4r_D3D12_RecordDiagnosticBoundary");
auto live = shim.symbol<Live>("d4r_D3D12_LiveRecordings"); auto live = shim.symbol<Live>("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<ID3D12CommandSignature> 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) { 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 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()); 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 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 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); 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); upload(first.Get(), 17, 10); upload(second.Get(), 25, 100);
{ {
NativeList a(device.Get()), b(device.Get()), c(device.Get()); NativeList a(device.Get()), b(device.Get()), c(device.Get());
@@ -140,24 +212,71 @@ int main(int argc, char** argv) {
uint32_t temporary = seed; uint32_t temporary = seed;
b.list->SetComputeRoot32BitConstants(2, 1, &temporary, 0); b.list->SetComputeRoot32BitConstants(2, 1, &temporary, 0);
temporary = 0xdeadbeef; // Deep-copy check: replay must retain Seed. 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); b.list->Dispatch(count / 64, 1, 1);
Callback callback{device.Get(), input.Get(), output.Get(), second.Get(), prefix.Get()}; Callback callback{device.Get(), input.Get(), output.Get(), second.Get(), prefix.Get(), expected_seed};
if (record(b.list.Get(), boundary, &callback) != 1) throw std::runtime_error("Record split failed"); 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. b.list->Dispatch(count / 64, 1, 1); // No rebinding after the split.
dx(b.list->Close(), "Close logical list B"); dx(b.list->Close(), "Close logical list B");
transition(c.list.Get(), output.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE); transition(c.list.Get(), output.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE);
c.list->CopyResource(readback.Get(), output.Get()); c.list->CopyResource(readback.Get(), output.Get());
transition(c.list.Get(), output.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); transition(c.list.Get(), output.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
dx(c.list->Close(), "Close consumer C"); dx(c.list->Close(), "Close consumer C");
messages(device.Get());
ID3D12CommandList* batch[] = {a.list.Get(), b.list.Get(), c.list.Get()}; ID3D12CommandList* batch[] = {a.list.Get(), b.list.Get(), c.list.Get()};
queue->ExecuteCommandLists(3, batch); drain(device.Get(), queue.Get()); 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)) throw std::runtime_error("Queue order or suffix root state mismatch"); 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"); 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; return 0;
} catch (const std::exception& failure) { } 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<ID3D12DeviceRemovedExtendedData> 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;
} }
} }
+46
View File
@@ -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}};
}
+31 -1
View File
@@ -1,7 +1,7 @@
"""Generate typed command forwarding from public MinGW D3D12 SDK declarations. """Generate typed command forwarding from public MinGW D3D12 SDK declarations.
Run with --header <SDK/include/d3d12.h> --output d3d12_command_hooks.generated.h. Run with --header <SDK/include/d3d12.h> --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 their host arguments; COM object arguments retain a reference. SDK slot numbers
are derived from the public interface inheritance, never a driver binary. are derived from the public interface inheritance, never a driver binary.
""" """
@@ -9,6 +9,17 @@ import argparse
import pathlib import pathlib
import re 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 = { POINTER_COUNTS = {
'RSSetViewports': {'viewports': 'viewport_count'}, 'RSSetViewports': {'viewports': 'viewport_count'},
'RSSetScissorRects': {'rects': 'rect_count'}, 'RSSetScissorRects': {'rects': 'rect_count'},
@@ -89,7 +100,26 @@ def generate(source):
registrations[-1] = f' if (version >= {version})' + registrations[-1][3:] registrations[-1] = f' if (version >= {version})' + registrations[-1][3:]
slot += 1 slot += 1
lines.append('') 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<void*>(hook_{name}), reinterpret_cast<void**>(&original_{name}), "{name}");')
slot += 1
lines.append('')
lines += ['static void attach_recording_methods(void** table, unsigned version) {'] + registrations + ['}', ''] 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) return '\n'.join(lines)