From 74aad12938560ab63652d1f53f9479b925e31f1c Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 1 Oct 2026 20:23:08 +0300 Subject: [PATCH] Cache D3D12 recording lookups without retaining game resources --- docs/windows-game.md | 2 + docs/windows-performance.md | 34 ++++++++ docs/windows-rdna4-port.md | 21 +++++ scripts/windows/package-windows-game.ps1 | 21 ++++- scripts/windows/windows-game.ps1 | 4 +- tools/windows/d3d12_command_hooks.cpp | 77 +++++++++++++++++-- tools/windows/d3d12_command_hooks.generated.h | 62 +++++++++++++++ tools/windows/d3d12_command_probe.cpp | 33 +++++++- tools/windows/generate_d3d12_hooks.py | 4 +- tools/windows/profile_report.py | 23 ++++++ 10 files changed, 268 insertions(+), 13 deletions(-) diff --git a/docs/windows-game.md b/docs/windows-game.md index ae1d383..3374229 100644 --- a/docs/windows-game.md +++ b/docs/windows-game.md @@ -53,6 +53,8 @@ command-list split/replay, submitted state publication and suffix completion. The recording interval measures NGX calls on the recording thread, not Present. `-ProfileKernels` enables synchronizing HIP-event kernel profiling and changes frame scheduling; use it separately from a production FPS measurement. +`-ProfileCommandHooks` samples one in 64 D3D12 interceptions to distinguish +tracking/lock waits from driver calls. It is disabled in ordinary runs. The runner drains stdout/stderr concurrently as raw bytes; the earlier line-oriented PowerShell collector imposed large per-frame delays. The optional ZLUDA kernel profiler is for diagnostics only. Measurements and reproduction diff --git a/docs/windows-performance.md b/docs/windows-performance.md index 104fbfc..c13560b 100644 --- a/docs/windows-performance.md +++ b/docs/windows-performance.md @@ -138,5 +138,39 @@ The installer accepts them with `-LocalTextureKernels` only after validation, checking the exact DLL identity, object and manifest hashes. M retains its independently validated FP16-equivalent baseline. +The following no-debugger K/4K run completes 9982 frames and 9982 finite GPU +scans. Kernel event profiling is disabled; the validated private output tail +executes natively. The user reports 43-47 FPS. Mean NGX recording interval is +21.867 ms. No backend failure, CPU image copy or previous-frame output occurs. + +Random one-in-64 hook samples then identify contention in the shared routing +table used by every intercepted D3D12 command. Per-thread weak lookup caches +now bypass that table on repeated calls to a list. Table mutations invalidate +cache generations; Reset/address reuse cannot route to a previous suffix, and +thread caches do not retain game resources. The per-list and submission locks +remain. A GPU regression resets and re-splits the exact same list with changed +root constants twice per iteration, checking both prefix and suffix results. + +| Sampled command-hook access | Shared table | Per-thread weak cache | +| --- | ---: | ---: | +| Busiest recording thread, mean us/call | 0.203 | 0.066 | +| Estimated access + capture across threads, ms/s | 977.528 | 233.606 | + +The latter estimate includes lock waits summed across concurrently running +threads, not CPU execution time or serial frame latency. The two game runs +have different thread populations (31 versus 29); there is no identical frame +trace. Generated forwarding methods have separate driver/capture markers; +special-case hooks contribute access samples but lack those body markers. + +With the cache, 4770 complete K/4K frames and finite GPU scans pass, with zero +backend failures, CPU image copies or previous-frame output. Median NGX +recording interval is 19.473 ms. The user reports 49-51 FPS and 53% GPU usage +in the previously measured scene. These user readings are separate from the +sampled hook estimates and do not establish isolated WMMA utilization. +All sixteen CTest gates pass, including 64 same-object Reset/split checks in +each physical command-backend test and the software indirect-root regression. +Use `-ProfileCommandHooks -ProfileStages` to collect samples; the sampler is +disabled for ordinary play. + References: [HIP events](https://rocmdocs.amd.com/projects/HIP/en/develop/doxygen/html/group___event.html), [HIP occupancy API](https://rocm.docs.amd.com/projects/HIP/en/latest/doxygen/html/group___occupancy.html). diff --git a/docs/windows-rdna4-port.md b/docs/windows-rdna4-port.md index ea7623c..237765d 100644 --- a/docs/windows-rdna4-port.md +++ b/docs/windows-rdna4-port.md @@ -54,6 +54,27 @@ public ZIPs exclude these objects. Install/restore with private overrides restores sentinel DLL/config byte-exactly, without duplicate slash/backslash manifest paths. +Production K/4K with private output stores completes 9982 frames and 9982 +finite GPU scans; the user reports 43-47 FPS. Random per-hook CPU sampling +then localizes contention to the shared command-list routing map. A per-thread +weak lookup cache bypasses that mutex for repeated calls, invalidating on map +mutations to preserve Reset/final Release/address reuse. Queue and per-list +synchronization remain. The regression resets/re-splits the same object twice +per iteration with different root constants and verifies both GPU outputs. +All sixteen CTest gates pass. The cached game run completes 4770 K/4K frames +and 4770 finite GPU scans, zero failures/CPU image copies/aged outputs. The user +reports 49-51 FPS and 53% GPU usage. Aggregated sampled access/capture time +falls from 977.528 to 233.606 ms/s across threads; it includes parallel lock +waits and must not be treated as serial frame time. Median recording interval +is 19.473 ms. `-ProfileCommandHooks` is optional and off during ordinary play. +Detailed scope and timings are in [windows-performance.md](windows-performance.md). +Post-cache full-pipeline checks also pass: three K/4K HDR frames and four M +512x288 frames match their previously validated controls with max absolute and +relative RGB error 0, all RGBA finite. Public archive validation checks all 63 +entries and their SHA256 against package.json; NVIDIA DLLs and private texture +objects are absent. `package-windows-game.ps1 -ArchivePath ` archives only +the staged manifest, including on Windows PowerShell 5.1. + Performance diagnostics (2026-10-01): the separate ZLUDA `b0161a4` runtime passes 32 PTX launches in each context and all four native K/M frames with bit-exact RGB against their controls. All thirteen exported patches (0002 diff --git a/scripts/windows/package-windows-game.ps1 b/scripts/windows/package-windows-game.ps1 index 7b87392..8fcace2 100644 --- a/scripts/windows/package-windows-game.ps1 +++ b/scripts/windows/package-windows-game.ps1 @@ -1,7 +1,8 @@ [CmdletBinding()] param( [string]$PackageRoot, [string]$DiagnosticRoot, [string]$OptiScalerRoot, - [string]$ZludaRoot, [string]$HipRoot, [string]$KernelKRoot, [string]$KernelMRoot + [string]$ZludaRoot, [string]$HipRoot, [string]$KernelKRoot, [string]$KernelMRoot, + [string]$ArchivePath ) $ErrorActionPreference = 'Stop' $repo = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../..')) @@ -76,3 +77,21 @@ if (Get-ChildItem -LiteralPath $PackageRoot -Recurse -File | Where-Object { $_.N throw 'Proprietary NVIDIA binaries must not be in the package.' } Write-Host "Windows game development package: $PackageRoot (NVIDIA DLLs excluded)" +if ($ArchivePath) { + $ArchivePath=[IO.Path]::GetFullPath($ArchivePath) + if ($ArchivePath.StartsWith($PackageRoot+'\',[StringComparison]::OrdinalIgnoreCase)) { throw 'Place the ZIP outside the package directory.' } + Add-Type -AssemblyName System.IO.Compression,System.IO.Compression.FileSystem + New-Item -ItemType Directory -Force (Split-Path $ArchivePath) | Out-Null + $stream=[IO.File]::Open($ArchivePath,[IO.FileMode]::Create,[IO.FileAccess]::ReadWrite,[IO.FileShare]::None) + $archive=$null + try { + $archive=[IO.Compression.ZipArchive]::new($stream,[IO.Compression.ZipArchiveMode]::Create) + # Never archive caches, results, private texture outputs or a local DLL + # merely because it happens to be somewhere under PackageRoot. + foreach ($relative in @($stagedFiles)+@('package.json')) { + [void][IO.Compression.ZipFileExtensions]::CreateEntryFromFile($archive, + (Join-Path $PackageRoot $relative),$relative.Replace('\','/'),[IO.Compression.CompressionLevel]::Optimal) + } + } finally { if ($archive) { $archive.Dispose() }; $stream.Dispose() } + Write-Host "Public package archive: $ArchivePath" +} diff --git a/scripts/windows/windows-game.ps1 b/scripts/windows/windows-game.ps1 index 72ad141..b1414bf 100644 --- a/scripts/windows/windows-game.ps1 +++ b/scripts/windows/windows-game.ps1 @@ -11,6 +11,7 @@ param( [switch]$ValidateOutput, [switch]$ProfileStages, [switch]$ProfileKernels, + [switch]$ProfileCommandHooks, [switch]$VerboseRuntime, [switch]$CaptureExceptions, [string[]]$GameArguments = @('-dx12'), @@ -176,6 +177,7 @@ $settings = @{ D4R_VALIDATE_OUTPUT=$(if ($ValidateOutput) { '1' } else { $null }); D4R_PROFILE_STAGES=$(if ($ProfileStages) { '1' } else { $null }); D4R_ZLUDA_PROFILE=$(if ($ProfileKernels) { '1' } else { $null }); + D4R_PROFILE_COMMAND_HOOKS=$(if ($ProfileCommandHooks) { '1' } else { $null }); D4R_DIAG_DIR=$OutputDirectory; ZLUDA_LOG_DIR=(Join-Path $OutputDirectory 'zluda-trace'); ZLUDA_CACHE_DIR=$CacheDirectory; PATH=((GamePath 'd4r/hip/bin') + ';' + (GamePath 'd4r/zluda') + ';' + $env:PATH) } @@ -258,7 +260,7 @@ try { outputGpuChecks=$checks.Count; nonfiniteOutputs=$nonfinite; kernels=$kernels; previousFrameOutputs=@($frames | Where-Object { $_.Groups[2].Value -ne '0' }).Count; cpuImageCopyFrames=@($frames | Where-Object { $_.Groups[1].Value -ne '0' }).Count; - failures=([regex]::Matches($stderr, 'D4R_WINDOWS_FAILURE|D4R_COMMAND_FAILURE')).Count} | + failures=([regex]::Matches($stderr, 'D4R_[A-Z0-9_]*FAILURE\b')).Count} | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'summary.json') -Encoding UTF8 Compress-Archive -LiteralPath $OutputDirectory -DestinationPath ($OutputDirectory + '.zip') -Force Write-Host "Game diagnostic bundle: $OutputDirectory.zip" diff --git a/tools/windows/d3d12_command_hooks.cpp b/tools/windows/d3d12_command_hooks.cpp index cfce542..65d65cc 100644 --- a/tools/windows/d3d12_command_hooks.cpp +++ b/tools/windows/d3d12_command_hooks.cpp @@ -60,28 +60,93 @@ struct Recording { explicit Recording(ID3D12GraphicsCommandList* list) : original(list) {} }; std::unordered_map> recordings; +std::atomic recording_generation{0}; +struct RecordingCache { + ID3D12GraphicsCommandList* list = nullptr; + uint64_t generation = 0; + // A thread's last lookup must not retain suffix lists, allocators or game + // resources after Reset/final Release. Address reuse also needs a new + // generation, even if the old Recording is alive in a submit callback. + std::weak_ptr value; +}; +thread_local RecordingCache recording_cache; std::shared_ptr get_recording(ID3D12GraphicsCommandList* self, bool create = true) { if (internal) return {}; + const auto generation = recording_generation.load(std::memory_order_acquire); + if (recording_cache.list == self && recording_cache.generation == generation) { + if (auto value = recording_cache.value.lock()) return value; + } std::lock_guard lock(map_mutex); auto found = recordings.find(self); - if (found != recordings.end()) return found->second; - if (!create) return {}; - auto value = std::make_shared(self); - recordings.emplace(self, value); return value; + std::shared_ptr value; + if (found != recordings.end()) value = found->second; + else { + if (!create) return {}; + value = std::make_shared(self); + recordings.emplace(self, value); + recording_generation.fetch_add(1, std::memory_order_release); + } + recording_cache.list = self; + recording_cache.value = value; + recording_cache.generation = recording_generation.load(std::memory_order_relaxed); + return value; } void forget(ID3D12GraphicsCommandList* self) { std::shared_ptr released; { std::lock_guard lock(map_mutex); auto it = recordings.find(self); - if (it != recordings.end()) { released = std::move(it->second); recordings.erase(it); } } + if (it != recordings.end()) { + released = std::move(it->second); recordings.erase(it); + recording_generation.fetch_add(1, std::memory_order_release); + } } // Release retained COM objects without holding the map mutex. } +struct HookSample { + uint32_t random = GetCurrentThreadId() | 1u; + uint64_t calls = 0, samples = 0, marked = 0; + double access_ms = 0, capture_ms = 0, driver_ms = 0; + Clock::time_point since = Clock::now(); + bool choose() { + ++calls; random ^= random << 13; random ^= random >> 17; random ^= random << 5; + return !(random & 63u); + } + void flush() { + const double elapsed = milliseconds(since); + if (elapsed < 2000) return; + std::printf("D4R_COMMAND_HOOK_SAMPLE thread=%lu period_ms=%.3f calls=%llu samples=%llu marked=%llu access_sum_ms=%.6f capture_sum_ms=%.6f driver_sum_ms=%.6f sampling=64\n", + GetCurrentThreadId(), elapsed, (unsigned long long)calls, (unsigned long long)samples, + (unsigned long long)marked, access_ms, capture_ms, driver_ms); + calls = samples = marked = 0; access_ms = capture_ms = driver_ms = 0; since = Clock::now(); + } +}; +thread_local HookSample hook_sample; +bool sample_hooks() { static const bool enabled = std::getenv("D4R_PROFILE_COMMAND_HOOKS") != nullptr; return enabled; } struct Access { std::shared_ptr recording; std::unique_lock lock; ID3D12GraphicsCommandList* self; - explicit Access(ID3D12GraphicsCommandList* list) : recording(get_recording(list)), self(list) { + bool sampled = false; + Clock::time_point begin{}, acquired{}, driver{}; + explicit Access(ID3D12GraphicsCommandList* list) : self(list) { + sampled = !internal && sample_hooks() && hook_sample.choose(); + if (sampled) begin = Clock::now(); + recording = get_recording(list); if (recording) lock = std::unique_lock(recording->mutex); + if (sampled) acquired = Clock::now(); + } + void driver_call() { + if (sampled) driver = Clock::now(); + } + ~Access() { + if (!sampled) return; + ++hook_sample.samples; + hook_sample.access_ms += std::chrono::duration(acquired - begin).count(); + if (driver != Clock::time_point{}) { + ++hook_sample.marked; + hook_sample.capture_ms += std::chrono::duration(driver - acquired).count(); + hook_sample.driver_ms += milliseconds(driver); + } + hook_sample.flush(); } ID3D12GraphicsCommandList* target() const { return recording && recording->current ? recording->current.Get() : self; diff --git a/tools/windows/d3d12_command_hooks.generated.h b/tools/windows/d3d12_command_hooks.generated.h index 89ed165..f9242ec 100644 --- a/tools/windows/d3d12_command_hooks.generated.h +++ b/tools/windows/d3d12_command_hooks.generated.h @@ -16,6 +16,7 @@ using Fn_DrawInstanced = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* sel static Fn_DrawInstanced original_DrawInstanced = nullptr; static void STDMETHODCALLTYPE hook_DrawInstanced(ID3D12GraphicsCommandList* self, UINT vertex_count_per_instance, UINT instance_count, UINT start_vertex_location, UINT start_instance_location) { Access access(self); + access.driver_call(); return original_DrawInstanced(access.target(), vertex_count_per_instance, instance_count, start_vertex_location, start_instance_location); } @@ -23,6 +24,7 @@ using Fn_DrawIndexedInstanced = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandLi static Fn_DrawIndexedInstanced original_DrawIndexedInstanced = nullptr; static void STDMETHODCALLTYPE hook_DrawIndexedInstanced(ID3D12GraphicsCommandList* self, UINT index_count_per_instance, UINT instance_count, UINT start_vertex_location, INT base_vertex_location, UINT start_instance_location) { Access access(self); + access.driver_call(); return original_DrawIndexedInstanced(access.target(), index_count_per_instance, instance_count, start_vertex_location, base_vertex_location, start_instance_location); } @@ -30,6 +32,7 @@ using Fn_Dispatch = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, UI static Fn_Dispatch original_Dispatch = nullptr; static void STDMETHODCALLTYPE hook_Dispatch(ID3D12GraphicsCommandList* self, UINT x, UINT u, UINT z) { Access access(self); + access.driver_call(); return original_Dispatch(access.target(), x, u, z); } @@ -37,6 +40,7 @@ using Fn_CopyBufferRegion = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* static Fn_CopyBufferRegion original_CopyBufferRegion = nullptr; static void STDMETHODCALLTYPE hook_CopyBufferRegion(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_buffer, UINT64 dst_offset, ID3D12Resource *src_buffer, UINT64 src_offset, UINT64 byte_count) { Access access(self); + access.driver_call(); return original_CopyBufferRegion(access.target(), dst_buffer, dst_offset, src_buffer, src_offset, byte_count); } @@ -44,6 +48,7 @@ using Fn_CopyTextureRegion = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* static Fn_CopyTextureRegion original_CopyTextureRegion = nullptr; static void STDMETHODCALLTYPE hook_CopyTextureRegion(ID3D12GraphicsCommandList* self, const D3D12_TEXTURE_COPY_LOCATION *dst, UINT dst_x, UINT dst_y, UINT dst_z, const D3D12_TEXTURE_COPY_LOCATION *src, const D3D12_BOX *src_box) { Access access(self); + access.driver_call(); return original_CopyTextureRegion(access.target(), dst, dst_x, dst_y, dst_z, src, src_box); } @@ -51,6 +56,7 @@ using Fn_CopyResource = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self static Fn_CopyResource original_CopyResource = nullptr; static void STDMETHODCALLTYPE hook_CopyResource(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_resource, ID3D12Resource *src_resource) { Access access(self); + access.driver_call(); return original_CopyResource(access.target(), dst_resource, src_resource); } @@ -58,6 +64,7 @@ using Fn_CopyTiles = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, I static Fn_CopyTiles original_CopyTiles = nullptr; static void STDMETHODCALLTYPE hook_CopyTiles(ID3D12GraphicsCommandList* self, ID3D12Resource *tiled_resource, const D3D12_TILED_RESOURCE_COORDINATE *tile_region_start_coordinate, const D3D12_TILE_REGION_SIZE *tile_region_size, ID3D12Resource *buffer, UINT64 buffer_offset, D3D12_TILE_COPY_FLAGS flags) { Access access(self); + access.driver_call(); return original_CopyTiles(access.target(), tiled_resource, tile_region_start_coordinate, tile_region_size, buffer, buffer_offset, flags); } @@ -65,6 +72,7 @@ using Fn_ResolveSubresource = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList static Fn_ResolveSubresource original_ResolveSubresource = nullptr; static void STDMETHODCALLTYPE hook_ResolveSubresource(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_resource, UINT dst_sub_resource, ID3D12Resource *src_resource, UINT src_sub_resource, DXGI_FORMAT format) { Access access(self); + access.driver_call(); return original_ResolveSubresource(access.target(), dst_resource, dst_sub_resource, src_resource, src_sub_resource, format); } @@ -75,6 +83,7 @@ static void STDMETHODCALLTYPE hook_IASetPrimitiveTopology(ID3D12GraphicsCommandL if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_IASetPrimitiveTopology(target, primitive_topology); }); + access.driver_call(); return original_IASetPrimitiveTopology(access.target(), primitive_topology); } @@ -86,6 +95,7 @@ static void STDMETHODCALLTYPE hook_RSSetViewports(ID3D12GraphicsCommandList* sel if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_RSSetViewports(target, viewport_count, values_pointer(saved_viewports)); }); + access.driver_call(); return original_RSSetViewports(access.target(), viewport_count, viewports); } @@ -97,6 +107,7 @@ static void STDMETHODCALLTYPE hook_RSSetScissorRects(ID3D12GraphicsCommandList* if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_RSSetScissorRects(target, rect_count, values_pointer(saved_rects)); }); + access.driver_call(); return original_RSSetScissorRects(access.target(), rect_count, rects); } @@ -108,6 +119,7 @@ static void STDMETHODCALLTYPE hook_OMSetBlendFactor(ID3D12GraphicsCommandList* s if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_OMSetBlendFactor(target, values_pointer(saved_blend_factor)); }); + access.driver_call(); return original_OMSetBlendFactor(access.target(), blend_factor); } @@ -118,6 +130,7 @@ static void STDMETHODCALLTYPE hook_OMSetStencilRef(ID3D12GraphicsCommandList* se if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_OMSetStencilRef(target, stencil_ref); }); + access.driver_call(); return original_OMSetStencilRef(access.target(), stencil_ref); } @@ -129,6 +142,7 @@ static void STDMETHODCALLTYPE hook_SetPipelineState(ID3D12GraphicsCommandList* s if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetPipelineState(target, saved_pipeline_state.Get()); }); + access.driver_call(); return original_SetPipelineState(access.target(), pipeline_state); } @@ -148,6 +162,7 @@ static void STDMETHODCALLTYPE hook_SetDescriptorHeaps(ID3D12GraphicsCommandList* if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetDescriptorHeaps(target, heap_count, saved_heaps.raw.data()); }); + access.driver_call(); return original_SetDescriptorHeaps(access.target(), heap_count, heaps); } @@ -159,6 +174,7 @@ static void STDMETHODCALLTYPE hook_SetComputeRootSignature(ID3D12GraphicsCommand if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetComputeRootSignature(target, saved_root_signature.Get()); }); + access.driver_call(); return original_SetComputeRootSignature(access.target(), root_signature); } @@ -170,6 +186,7 @@ static void STDMETHODCALLTYPE hook_SetGraphicsRootSignature(ID3D12GraphicsComman if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetGraphicsRootSignature(target, saved_root_signature.Get()); }); + access.driver_call(); return original_SetGraphicsRootSignature(access.target(), root_signature); } @@ -180,6 +197,7 @@ static void STDMETHODCALLTYPE hook_SetComputeRootDescriptorTable(ID3D12GraphicsC if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetComputeRootDescriptorTable(target, root_parameter_index, base_descriptor); }); + access.driver_call(); return original_SetComputeRootDescriptorTable(access.target(), root_parameter_index, base_descriptor); } @@ -190,6 +208,7 @@ static void STDMETHODCALLTYPE hook_SetGraphicsRootDescriptorTable(ID3D12Graphics if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetGraphicsRootDescriptorTable(target, root_parameter_index, base_descriptor); }); + access.driver_call(); return original_SetGraphicsRootDescriptorTable(access.target(), root_parameter_index, base_descriptor); } @@ -200,6 +219,7 @@ static void STDMETHODCALLTYPE hook_SetComputeRoot32BitConstant(ID3D12GraphicsCom if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetComputeRoot32BitConstant(target, root_parameter_index, data, dst_offset); }); + access.driver_call(); return original_SetComputeRoot32BitConstant(access.target(), root_parameter_index, data, dst_offset); } @@ -210,6 +230,7 @@ static void STDMETHODCALLTYPE hook_SetGraphicsRoot32BitConstant(ID3D12GraphicsCo if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetGraphicsRoot32BitConstant(target, root_parameter_index, data, dst_offset); }); + access.driver_call(); return original_SetGraphicsRoot32BitConstant(access.target(), root_parameter_index, data, dst_offset); } @@ -221,6 +242,7 @@ static void STDMETHODCALLTYPE hook_SetComputeRoot32BitConstants(ID3D12GraphicsCo if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetComputeRoot32BitConstants(target, root_parameter_index, constant_count, values_pointer(saved_data), dst_offset); }); + access.driver_call(); return original_SetComputeRoot32BitConstants(access.target(), root_parameter_index, constant_count, data, dst_offset); } @@ -232,6 +254,7 @@ static void STDMETHODCALLTYPE hook_SetGraphicsRoot32BitConstants(ID3D12GraphicsC if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetGraphicsRoot32BitConstants(target, root_parameter_index, constant_count, values_pointer(saved_data), dst_offset); }); + access.driver_call(); return original_SetGraphicsRoot32BitConstants(access.target(), root_parameter_index, constant_count, data, dst_offset); } @@ -242,6 +265,7 @@ static void STDMETHODCALLTYPE hook_SetComputeRootConstantBufferView(ID3D12Graphi if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetComputeRootConstantBufferView(target, root_parameter_index, address); }); + access.driver_call(); return original_SetComputeRootConstantBufferView(access.target(), root_parameter_index, address); } @@ -252,6 +276,7 @@ static void STDMETHODCALLTYPE hook_SetGraphicsRootConstantBufferView(ID3D12Graph if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetGraphicsRootConstantBufferView(target, root_parameter_index, address); }); + access.driver_call(); return original_SetGraphicsRootConstantBufferView(access.target(), root_parameter_index, address); } @@ -262,6 +287,7 @@ static void STDMETHODCALLTYPE hook_SetComputeRootShaderResourceView(ID3D12Graphi if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetComputeRootShaderResourceView(target, root_parameter_index, address); }); + access.driver_call(); return original_SetComputeRootShaderResourceView(access.target(), root_parameter_index, address); } @@ -272,6 +298,7 @@ static void STDMETHODCALLTYPE hook_SetGraphicsRootShaderResourceView(ID3D12Graph if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetGraphicsRootShaderResourceView(target, root_parameter_index, address); }); + access.driver_call(); return original_SetGraphicsRootShaderResourceView(access.target(), root_parameter_index, address); } @@ -282,6 +309,7 @@ static void STDMETHODCALLTYPE hook_SetComputeRootUnorderedAccessView(ID3D12Graph if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetComputeRootUnorderedAccessView(target, root_parameter_index, address); }); + access.driver_call(); return original_SetComputeRootUnorderedAccessView(access.target(), root_parameter_index, address); } @@ -292,6 +320,7 @@ static void STDMETHODCALLTYPE hook_SetGraphicsRootUnorderedAccessView(ID3D12Grap if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetGraphicsRootUnorderedAccessView(target, root_parameter_index, address); }); + access.driver_call(); return original_SetGraphicsRootUnorderedAccessView(access.target(), root_parameter_index, address); } @@ -303,6 +332,7 @@ static void STDMETHODCALLTYPE hook_IASetIndexBuffer(ID3D12GraphicsCommandList* s if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_IASetIndexBuffer(target, values_pointer(saved_view)); }); + access.driver_call(); return original_IASetIndexBuffer(access.target(), view); } @@ -314,6 +344,7 @@ static void STDMETHODCALLTYPE hook_IASetVertexBuffers(ID3D12GraphicsCommandList* if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_IASetVertexBuffers(target, start_slot, view_count, values_pointer(saved_views)); }); + access.driver_call(); return original_IASetVertexBuffers(access.target(), start_slot, view_count, views); } @@ -325,6 +356,7 @@ static void STDMETHODCALLTYPE hook_SOSetTargets(ID3D12GraphicsCommandList* self, if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SOSetTargets(target, start_slot, view_count, values_pointer(saved_views)); }); + access.driver_call(); return original_SOSetTargets(access.target(), start_slot, view_count, views); } @@ -337,6 +369,7 @@ static void STDMETHODCALLTYPE hook_OMSetRenderTargets(ID3D12GraphicsCommandList* if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_OMSetRenderTargets(target, render_target_descriptor_count, values_pointer(saved_render_target_descriptors), single_descriptor_handle, values_pointer(saved_depth_stencil_descriptor)); }); + access.driver_call(); return original_OMSetRenderTargets(access.target(), render_target_descriptor_count, render_target_descriptors, single_descriptor_handle, depth_stencil_descriptor); } @@ -344,6 +377,7 @@ using Fn_ClearDepthStencilView = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandL static Fn_ClearDepthStencilView original_ClearDepthStencilView = nullptr; static void STDMETHODCALLTYPE hook_ClearDepthStencilView(ID3D12GraphicsCommandList* self, D3D12_CPU_DESCRIPTOR_HANDLE dsv, D3D12_CLEAR_FLAGS flags, FLOAT depth, UINT8 stencil, UINT rect_count, const D3D12_RECT *rects) { Access access(self); + access.driver_call(); return original_ClearDepthStencilView(access.target(), dsv, flags, depth, stencil, rect_count, rects); } @@ -351,6 +385,7 @@ using Fn_ClearRenderTargetView = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandL static Fn_ClearRenderTargetView original_ClearRenderTargetView = nullptr; static void STDMETHODCALLTYPE hook_ClearRenderTargetView(ID3D12GraphicsCommandList* self, D3D12_CPU_DESCRIPTOR_HANDLE rtv, const FLOAT color[4], UINT rect_count, const D3D12_RECT *rects) { Access access(self); + access.driver_call(); return original_ClearRenderTargetView(access.target(), rtv, color, rect_count, rects); } @@ -358,6 +393,7 @@ using Fn_ClearUnorderedAccessViewUint = void(STDMETHODCALLTYPE*)(ID3D12GraphicsC static Fn_ClearUnorderedAccessViewUint original_ClearUnorderedAccessViewUint = nullptr; static void STDMETHODCALLTYPE hook_ClearUnorderedAccessViewUint(ID3D12GraphicsCommandList* self, D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle, D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle, ID3D12Resource *resource, const UINT values[4], UINT rect_count, const D3D12_RECT *rects) { Access access(self); + access.driver_call(); return original_ClearUnorderedAccessViewUint(access.target(), gpu_handle, cpu_handle, resource, values, rect_count, rects); } @@ -365,6 +401,7 @@ using Fn_ClearUnorderedAccessViewFloat = void(STDMETHODCALLTYPE*)(ID3D12Graphics static Fn_ClearUnorderedAccessViewFloat original_ClearUnorderedAccessViewFloat = nullptr; static void STDMETHODCALLTYPE hook_ClearUnorderedAccessViewFloat(ID3D12GraphicsCommandList* self, D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle, D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle, ID3D12Resource *resource, const float values[4], UINT rect_count, const D3D12_RECT *rects) { Access access(self); + access.driver_call(); return original_ClearUnorderedAccessViewFloat(access.target(), gpu_handle, cpu_handle, resource, values, rect_count, rects); } @@ -372,6 +409,7 @@ using Fn_DiscardResource = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* s static Fn_DiscardResource original_DiscardResource = nullptr; static void STDMETHODCALLTYPE hook_DiscardResource(ID3D12GraphicsCommandList* self, ID3D12Resource *resource, const D3D12_DISCARD_REGION *region) { Access access(self); + access.driver_call(); return original_DiscardResource(access.target(), resource, region); } @@ -387,6 +425,7 @@ using Fn_ResolveQueryData = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* static Fn_ResolveQueryData original_ResolveQueryData = nullptr; static void STDMETHODCALLTYPE hook_ResolveQueryData(ID3D12GraphicsCommandList* self, ID3D12QueryHeap *heap, D3D12_QUERY_TYPE type, UINT start_index, UINT query_count, ID3D12Resource *dst_buffer, UINT64 aligned_dst_buffer_offset) { Access access(self); + access.driver_call(); return original_ResolveQueryData(access.target(), heap, type, start_index, query_count, dst_buffer, aligned_dst_buffer_offset); } @@ -398,6 +437,7 @@ using Fn_SetMarker = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self, U static Fn_SetMarker original_SetMarker = nullptr; static void STDMETHODCALLTYPE hook_SetMarker(ID3D12GraphicsCommandList* self, UINT metadata, const void *data, UINT size) { Access access(self); + access.driver_call(); return original_SetMarker(access.target(), metadata, data, size); } @@ -417,6 +457,7 @@ using Fn_AtomicCopyBufferUINT = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandLi static Fn_AtomicCopyBufferUINT original_AtomicCopyBufferUINT = nullptr; static void STDMETHODCALLTYPE hook_AtomicCopyBufferUINT(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_buffer, UINT64 dst_offset, ID3D12Resource *src_buffer, UINT64 src_offset, UINT dependent_resource_count, ID3D12Resource *const *dependent_resources, const D3D12_SUBRESOURCE_RANGE_UINT64 *dependent_sub_resource_ranges) { Access access(self); + access.driver_call(); return original_AtomicCopyBufferUINT(access.target(), dst_buffer, dst_offset, src_buffer, src_offset, dependent_resource_count, dependent_resources, dependent_sub_resource_ranges); } @@ -424,6 +465,7 @@ using Fn_AtomicCopyBufferUINT64 = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommand static Fn_AtomicCopyBufferUINT64 original_AtomicCopyBufferUINT64 = nullptr; static void STDMETHODCALLTYPE hook_AtomicCopyBufferUINT64(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_buffer, UINT64 dst_offset, ID3D12Resource *src_buffer, UINT64 src_offset, UINT dependent_resource_count, ID3D12Resource *const *dependent_resources, const D3D12_SUBRESOURCE_RANGE_UINT64 *dependent_sub_resource_ranges) { Access access(self); + access.driver_call(); return original_AtomicCopyBufferUINT64(access.target(), dst_buffer, dst_offset, src_buffer, src_offset, dependent_resource_count, dependent_resources, dependent_sub_resource_ranges); } @@ -434,6 +476,7 @@ static void STDMETHODCALLTYPE hook_OMSetDepthBounds(ID3D12GraphicsCommandList* s if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_OMSetDepthBounds(target, min, max); }); + access.driver_call(); return original_OMSetDepthBounds(access.target(), min, max); } @@ -445,6 +488,7 @@ static void STDMETHODCALLTYPE hook_SetSamplePositions(ID3D12GraphicsCommandList* if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetSamplePositions(target, sample_count, pixel_count, values_pointer(saved_sample_positions)); }); + access.driver_call(); return original_SetSamplePositions(access.target(), sample_count, pixel_count, sample_positions); } @@ -452,6 +496,7 @@ using Fn_ResolveSubresourceRegion = void(STDMETHODCALLTYPE*)(ID3D12GraphicsComma static Fn_ResolveSubresourceRegion original_ResolveSubresourceRegion = nullptr; static void STDMETHODCALLTYPE hook_ResolveSubresourceRegion(ID3D12GraphicsCommandList* self, ID3D12Resource *dst_resource, UINT dst_sub_resource_idx, UINT dst_x, UINT dst_y, ID3D12Resource *src_resource, UINT src_sub_resource_idx, D3D12_RECT *src_rect, DXGI_FORMAT format, D3D12_RESOLVE_MODE mode) { Access access(self); + access.driver_call(); return original_ResolveSubresourceRegion(access.target(), dst_resource, dst_sub_resource_idx, dst_x, dst_y, src_resource, src_sub_resource_idx, src_rect, format, mode); } @@ -462,6 +507,7 @@ static void STDMETHODCALLTYPE hook_SetViewInstanceMask(ID3D12GraphicsCommandList if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetViewInstanceMask(target, mask); }); + access.driver_call(); return original_SetViewInstanceMask(access.target(), mask); } @@ -469,6 +515,7 @@ using Fn_WriteBufferImmediate = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandLi static Fn_WriteBufferImmediate original_WriteBufferImmediate = nullptr; static void STDMETHODCALLTYPE hook_WriteBufferImmediate(ID3D12GraphicsCommandList* self, UINT count, const D3D12_WRITEBUFFERIMMEDIATE_PARAMETER *parameters, const D3D12_WRITEBUFFERIMMEDIATE_MODE *modes) { Access access(self); + access.driver_call(); return original_WriteBufferImmediate(access.target(), count, parameters, modes); } @@ -480,6 +527,7 @@ static void STDMETHODCALLTYPE hook_SetProtectedResourceSession(ID3D12GraphicsCom if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetProtectedResourceSession(target, saved_protected_resource_session.Get()); }); + access.driver_call(); return original_SetProtectedResourceSession(access.target(), protected_resource_session); } @@ -495,6 +543,7 @@ using Fn_InitializeMetaCommand = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandL static Fn_InitializeMetaCommand original_InitializeMetaCommand = nullptr; static void STDMETHODCALLTYPE hook_InitializeMetaCommand(ID3D12GraphicsCommandList* self, ID3D12MetaCommand *meta_command, const void *initialization_parameters_data, SIZE_T initialization_parameters_data_size_in_bytes) { Access access(self); + access.driver_call(); return original_InitializeMetaCommand(access.target(), meta_command, initialization_parameters_data, initialization_parameters_data_size_in_bytes); } @@ -502,6 +551,7 @@ using Fn_ExecuteMetaCommand = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList static Fn_ExecuteMetaCommand original_ExecuteMetaCommand = nullptr; static void STDMETHODCALLTYPE hook_ExecuteMetaCommand(ID3D12GraphicsCommandList* self, ID3D12MetaCommand *meta_command, const void *execution_parameters_data, SIZE_T execution_parameters_data_size_in_bytes) { Access access(self); + access.driver_call(); return original_ExecuteMetaCommand(access.target(), meta_command, execution_parameters_data, execution_parameters_data_size_in_bytes); } @@ -509,6 +559,7 @@ using Fn_BuildRaytracingAccelerationStructure = void(STDMETHODCALLTYPE*)(ID3D12G static Fn_BuildRaytracingAccelerationStructure original_BuildRaytracingAccelerationStructure = nullptr; static void STDMETHODCALLTYPE hook_BuildRaytracingAccelerationStructure(ID3D12GraphicsCommandList* self, const D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC *desc, UINT postbuild_info_descs_count, const D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_DESC *postbuild_info_descs) { Access access(self); + access.driver_call(); return original_BuildRaytracingAccelerationStructure(access.target(), desc, postbuild_info_descs_count, postbuild_info_descs); } @@ -516,6 +567,7 @@ using Fn_EmitRaytracingAccelerationStructurePostbuildInfo = void(STDMETHODCALLTY static Fn_EmitRaytracingAccelerationStructurePostbuildInfo original_EmitRaytracingAccelerationStructurePostbuildInfo = nullptr; static void STDMETHODCALLTYPE hook_EmitRaytracingAccelerationStructurePostbuildInfo(ID3D12GraphicsCommandList* self, const D3D12_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO_DESC *desc, UINT src_acceleration_structures_count, const D3D12_GPU_VIRTUAL_ADDRESS *src_acceleration_structure_data) { Access access(self); + access.driver_call(); return original_EmitRaytracingAccelerationStructurePostbuildInfo(access.target(), desc, src_acceleration_structures_count, src_acceleration_structure_data); } @@ -523,6 +575,7 @@ using Fn_CopyRaytracingAccelerationStructure = void(STDMETHODCALLTYPE*)(ID3D12Gr static Fn_CopyRaytracingAccelerationStructure original_CopyRaytracingAccelerationStructure = nullptr; static void STDMETHODCALLTYPE hook_CopyRaytracingAccelerationStructure(ID3D12GraphicsCommandList* self, D3D12_GPU_VIRTUAL_ADDRESS dst_acceleration_structure_data, D3D12_GPU_VIRTUAL_ADDRESS src_acceleration_structure_data, D3D12_RAYTRACING_ACCELERATION_STRUCTURE_COPY_MODE mode) { Access access(self); + access.driver_call(); return original_CopyRaytracingAccelerationStructure(access.target(), dst_acceleration_structure_data, src_acceleration_structure_data, mode); } @@ -534,6 +587,7 @@ static void STDMETHODCALLTYPE hook_SetPipelineState1(ID3D12GraphicsCommandList* if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_SetPipelineState1(target, saved_state_object.Get()); }); + access.driver_call(); return original_SetPipelineState1(access.target(), state_object); } @@ -541,6 +595,7 @@ using Fn_DispatchRays = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self static Fn_DispatchRays original_DispatchRays = nullptr; static void STDMETHODCALLTYPE hook_DispatchRays(ID3D12GraphicsCommandList* self, const D3D12_DISPATCH_RAYS_DESC *desc) { Access access(self); + access.driver_call(); return original_DispatchRays(access.target(), desc); } @@ -552,6 +607,7 @@ static void STDMETHODCALLTYPE hook_RSSetShadingRate(ID3D12GraphicsCommandList* s if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_RSSetShadingRate(target, base_shading_rate, values_pointer(saved_combiners)); }); + access.driver_call(); return original_RSSetShadingRate(access.target(), base_shading_rate, combiners); } @@ -563,6 +619,7 @@ static void STDMETHODCALLTYPE hook_RSSetShadingRateImage(ID3D12GraphicsCommandLi if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable { original_RSSetShadingRateImage(target, saved_shading_rate_image.Get()); }); + access.driver_call(); return original_RSSetShadingRateImage(access.target(), shading_rate_image); } @@ -570,6 +627,7 @@ using Fn_DispatchMesh = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* self static Fn_DispatchMesh original_DispatchMesh = nullptr; static void STDMETHODCALLTYPE hook_DispatchMesh(ID3D12GraphicsCommandList* self, UINT thread_group_count_x, UINT thread_group_count_y, UINT thread_group_count_z) { Access access(self); + access.driver_call(); return original_DispatchMesh(access.target(), thread_group_count_x, thread_group_count_y, thread_group_count_z); } @@ -584,6 +642,7 @@ static void STDMETHODCALLTYPE hook_OMSetFrontAndBackStencilRef(ID3D12GraphicsCom if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) { original_OMSetFrontAndBackStencilRef(target, front_stencil, back_stencil); }); + access.driver_call(); original_OMSetFrontAndBackStencilRef(access.target(), front_stencil, back_stencil); } @@ -594,6 +653,7 @@ static void STDMETHODCALLTYPE hook_RSSetDepthBias(ID3D12GraphicsCommandList* sel if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) { original_RSSetDepthBias(target, depth_bias, clamp, slope); }); + access.driver_call(); original_RSSetDepthBias(access.target(), depth_bias, clamp, slope); } @@ -604,6 +664,7 @@ static void STDMETHODCALLTYPE hook_IASetIndexBufferStripCutValue(ID3D12GraphicsC if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) { original_IASetIndexBufferStripCutValue(target, value); }); + access.driver_call(); original_IASetIndexBufferStripCutValue(access.target(), value); } @@ -615,6 +676,7 @@ using Fn_DispatchGraph = void(STDMETHODCALLTYPE*)(ID3D12GraphicsCommandList* sel static Fn_DispatchGraph original_DispatchGraph = nullptr; static void STDMETHODCALLTYPE hook_DispatchGraph(ID3D12GraphicsCommandList* self, const void* desc) { Access access(self); + access.driver_call(); original_DispatchGraph(access.target(), desc); } diff --git a/tools/windows/d3d12_command_probe.cpp b/tools/windows/d3d12_command_probe.cpp index a2847f8..74c7aa9 100644 --- a/tools/windows/d3d12_command_probe.cpp +++ b/tools/windows/d3d12_command_probe.cpp @@ -82,6 +82,7 @@ struct Callback { ID3D12Resource* readback; uint32_t expected_seed = seed; bool prefix_verified = false; + uint32_t expected_multiplier = 17, expected_addend = 10; }; void WINAPI boundary(ID3D12CommandQueue* queue, void* context) { auto& callback = *static_cast(context); @@ -93,7 +94,8 @@ 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.expected_seed); + callback.prefix_verified = verify(callback.readback, callback.expected_multiplier, + callback.expected_addend, callback.expected_seed); } } @@ -263,13 +265,38 @@ int main(int argc, char** argv) { ID3D12CommandList* batch[] = {a.list.Get(), b.list.Get(), c.list.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, expected_seed)) throw std::runtime_error("Queue order or suffix root state mismatch"); + if (!no_hooks) for (unsigned reuse = 0; reuse < 2; ++reuse) { + // Populate the same thread's routing cache with a split + // list, Reset that exact COM object, then split again with + // different root state. A cached previous Recording would + // keep targeting the old suffix or replay the old Seed. + dx(b.allocator->Reset(), "Reset reused allocator"); + dx(b.list->Reset(b.allocator.Get(), initial_pso ? pso.Get() : nullptr), "Reset reused logical list"); + b.list->SetComputeRootSignature(root.Get()); + if (!initial_pso) b.list->SetPipelineState(pso.Get()); + b.list->SetComputeRootUnorderedAccessView(0, output->GetGPUVirtualAddress()); + b.list->SetComputeRootShaderResourceView(1, input->GetGPUVirtualAddress()); + callback.expected_seed = seed + iteration + reuse + 1; + callback.expected_multiplier = 25; callback.expected_addend = 100; + callback.prefix_verified = false; + b.list->SetComputeRoot32BitConstant(2, callback.expected_seed, 0); + b.list->Dispatch(count / 64, 1, 1); + if (record(b.list.Get(), boundary, &callback) != 1) throw std::runtime_error("Record reused split failed"); + b.list->Dispatch(count / 64, 1, 1); + transition(b.list.Get(), output.Get(), D3D12_RESOURCE_STATE_UNORDERED_ACCESS, D3D12_RESOURCE_STATE_COPY_SOURCE); + b.list->CopyResource(readback.Get(), output.Get()); + transition(b.list.Get(), output.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + b.submit(device.Get(), queue.Get()); + if (!callback.prefix_verified || !verify(readback.Get(), 25, 100, callback.expected_seed)) + throw std::runtime_error("Reset reused logical list retained stale routing/root state"); + } } if (live() != 0) throw std::runtime_error("D3D12 recording metadata leaked after command-list Release"); } 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", + 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 reset_reuse=%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); + args.iterations, indirect_enabled, root_indirect, no_hooks ? 0 : 2 * args.iterations); return 0; } catch (const std::exception& failure) { std::fprintf(stderr, "FAIL D3D12_COMMAND_BACKEND %s\n", failure.what()); diff --git a/tools/windows/generate_d3d12_hooks.py b/tools/windows/generate_d3d12_hooks.py index 6dadbd2..4d25db3 100644 --- a/tools/windows/generate_d3d12_hooks.py +++ b/tools/windows/generate_d3d12_hooks.py @@ -95,7 +95,7 @@ def generate(source): captured.append(argument) lines += [' if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {', f' original_{name}(' + ', '.join(['target'] + captured) + ');', ' });'] - lines += [f' return original_{name}({call});', '}'] + lines += [' access.driver_call();', f' return original_{name}({call});', '}'] registrations.append(f' attach_method(table[{slot}], reinterpret_cast(hook_{name}), reinterpret_cast(&original_{name}), "{name}");') registrations[-1] = f' if (version >= {version})' + registrations[-1][3:] slot += 1 @@ -112,7 +112,7 @@ def generate(source): 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) + ');', '}'] + lines += [' access.driver_call();', f' original_{name}(' + ', '.join(['access.target()'] + names) + ');', '}'] registrations.append(f' if (version >= {version}) attach_method(table[{slot}], reinterpret_cast(hook_{name}), reinterpret_cast(&original_{name}), "{name}");') slot += 1 lines.append('') diff --git a/tools/windows/profile_report.py b/tools/windows/profile_report.py index 1464396..f01f8b0 100644 --- a/tools/windows/profile_report.py +++ b/tools/windows/profile_report.py @@ -41,6 +41,7 @@ def report(directory, metadata_directory=None): stages = collections.defaultdict(list) commands = collections.defaultdict(list) kernels = collections.defaultdict(list) + hook_threads = collections.defaultdict(list) for path in sorted(directory.glob('*.log')): text = path.read_text(encoding='utf-8-sig', errors='replace') for name, value in re.findall(r'D4R_STAGE name=(\S+) cpu_ms=([0-9.]+)', text): @@ -51,18 +52,37 @@ def report(directory, metadata_directory=None): if name != 'interval_ms' or number > 0: commands[kind.lower() + '.' + name].append(number) for line in text.splitlines(): + if line.startswith('D4R_COMMAND_HOOK_SAMPLE '): + fields = dict(re.findall(r'(\w+)=(\S+)', line)) + if all(name in fields for name in ('thread', 'period_ms', 'calls', 'samples', 'marked', + 'access_sum_ms', 'capture_sum_ms', 'driver_sum_ms')): + hook_threads[fields['thread']].append(fields) if not line.startswith('D4R_KERNEL_PROFILE '): continue fields = dict(re.findall(r'(\w+)=("[^"]+"|\S+)', line)) kernels[(fields['kernel'].strip('"'), fields['backend'], fields['phase'])].append(fields) result = dict(cpuStages={name: stats(values) for name, values in stages.items()}, commandStages={name: stats(values) for name, values in commands.items()}, + commandHookSamples=[], kernels=[], notes=[ 'CPU stage times include waits and host work; they are not isolated GPU timings.', 'Command record intervals are between NGX recording calls on one thread, not Present/FPS.', + 'Hook estimates use random one-in-64 samples; driver timing covers generated forwarding methods only.', + 'Hook access includes lock waits; summing threads does not measure serial frame latency or CPU execution time.', 'HIP-event profiling synchronizes every sampled launch and changes scheduling.', 'Occupancy is an API prediction, not a measured hardware counter.', 'Bandwidth and WMMA utilization require additional supported hardware tooling.']) + for thread, rows in hook_threads.items(): + sums = {name: sum(float(row[name]) for row in rows) for name in + ('period_ms', 'calls', 'samples', 'marked', 'access_sum_ms', 'capture_sum_ms', 'driver_sum_ms')} + if not sums['samples'] or not sums['period_ms']: + continue + scale = sums['calls'] / sums['samples'] + estimate = scale * (sums['access_sum_ms'] + sums['capture_sum_ms']) + result['commandHookSamples'].append(dict(thread=int(thread), periods=len(rows), **sums, + accessMeanUs=1000 * sums['access_sum_ms'] / sums['samples'], + captureMeanUsPerSample=1000 * sums['capture_sum_ms'] / sums['samples'], + estimatedAccessCaptureMs=estimate, estimatedAccessCaptureMsPerSecond=1000 * estimate / sums['period_ms'])) for (name, backend, phase), rows in kernels.items(): item = dict(kernel=name, backend=backend, phase=phase, gpuMs=stats([float(row['gpu_ms']) for row in rows]), @@ -93,6 +113,9 @@ def main(): print(f"CPU {name}: n={row['samples']} mean={row['mean']:.3f} ms median={row['median']:.3f} ms p95={row['p95']:.3f} ms") for name, row in result['commandStages'].items(): print(f"COMMAND {name}: n={row['samples']} mean={row['mean']:.3f} ms median={row['median']:.3f} ms p95={row['p95']:.3f} ms") + for row in result['commandHookSamples']: + print(f"HOOK thread={row['thread']} samples={int(row['samples'])} access={row['accessMeanUs']:.3f} us " + f"capture={row['captureMeanUsPerSample']:.3f} us estimated_cpu={row['estimatedAccessCaptureMsPerSecond']:.3f} ms/s") for row in result['kernels']: times = row['gpuMs'] print(f"GPU {row['kernel']} {row['backend']} {row['phase']}: n={times['samples']} mean={times['mean']:.6f} ms total={times['total']:.3f} ms")