Track Windows D3D12 indirect state and newer command interfaces
This commit is contained in:
1 parent
63607298ea
commit
312c6bb50d
8 files changed
+478
-24
No files matched your search
+19
-1
@@ -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 "$<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")
|
||||
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
|
||||
--hip-root "${D4R_HIP_ROOT}" --module "${probe_object}")
|
||||
set_tests_properties(hip_gfx1201 PROPERTIES TIMEOUT 90 LABELS "hardware;gfx1201")
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "d3d12_command_hooks.h"
|
||||
#include "d3d12_newer_commands.h"
|
||||
#include <MinHook.h>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
@@ -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<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*);
|
||||
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<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);
|
||||
}
|
||||
|
||||
@@ -265,8 +345,13 @@ void install(ID3D12Device* device) {
|
||||
ComPtr<IUnknown> 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<void**>(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<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());
|
||||
attach_recording_methods(table, version);
|
||||
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,
|
||||
// CopyTileMappings precede ExecuteCommandLists in the public SDK.
|
||||
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) {
|
||||
const auto result = MH_QueueEnableHook(hook.target);
|
||||
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 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<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");
|
||||
@@ -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 >= 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 >= 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;
|
||||
@@ -1,12 +1,31 @@
|
||||
#include "d3d12_external.h"
|
||||
#include <dxgi1_6.h>
|
||||
#include <d3dcompiler.h>
|
||||
#include <d3d12sdklayers.h>
|
||||
#include <array>
|
||||
|
||||
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<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) {
|
||||
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);
|
||||
@@ -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<const uint32_t*>(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<ID3D12Device> device;
|
||||
std::unique_ptr<Library> 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<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<IDXGIAdapter1> 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<ID3D12Device> device;
|
||||
dx(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(device.GetAddressOf())), "Probe D3D12");
|
||||
ComPtr<ID3D12CommandQueue> 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<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 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) {
|
||||
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<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;
|
||||
}
|
||||
}
|
||||
@@ -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}};
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Generate typed command forwarding from public MinGW D3D12 SDK declarations.
|
||||
|
||||
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
|
||||
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<void*>(hook_{name}), reinterpret_cast<void**>(&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)
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user