Repository navigation
[PIX] Add RayQuery invocation logging pass for inline raytracing - #9008
Austin Kinross (austinkinross) wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Resource-range collisions, patch-constant roots, and exit-only shaders currently produce incorrect instrumentation behavior.
Review effort: Balanced
Findings: 6
Open (9)
Termination ops incorrectly trigger instrumentation setup · New Allocation annotations miss trace sites discovered later · New Filtered events still perform UAV atomics and payload work · New Patch-constant functions are omitted as shader termination roots · New Overbroad PIX resource name matching causes false ownership · New Misses register ranges containing the requested register · New Missing release note for the new optimizer pass · New Dead assembly result is never consumed · New Use explicit CComPtr types instead of auto · New
What changed in this PR
Adds PIX instrumentation for inline RayQuery tracing across shader stages.
Changes:
- Adds query/candidate logging, filtering, identity capture, and side-table output.
- Extends PIX UAV and root-signature handling.
- Registers the pass and adds extensive validation coverage.
| File | Description |
|---|---|
utils/hct/hctdb.py |
Registers the pass and options. |
tools/clang/unittests/HLSL/PixTest.cpp |
Adds comprehensive unit tests. |
tools/clang/test/HLSLFileCheckLit/passes/pix/rayquery-log.hlsl |
Tests core instrumentation contracts. |
tools/clang/test/HLSLFileCheckLit/passes/pix/rayquery-log-raster-identity.hlsl |
Tests raster-stage identities. |
tools/clang/test/HLSLFileCheckLit/passes/pix/rayquery-log-domain.hlsl |
Tests domain-stage identity. |
tools/clang/test/HLSLFileCheck/pix/RayQueryLog.hlsl |
Adds legacy FileCheck coverage. |
lib/DxilPIXPasses/PixPassHelpers.h |
Extends UAV helper APIs. |
lib/DxilPIXPasses/PixPassHelpers.cpp |
Adds collision checks and binding support. |
lib/DxilPIXPasses/DxilPIXRayQueryLog.cpp |
Implements RayQuery logging. |
lib/DxilPIXPasses/CMakeLists.txt |
Builds the new pass. |
include/dxc/DxilPIXPasses/DxilPIXPasses.h |
Declares pass creation and initialization. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| IsRayQueryOp(&InstructionToCheck, | ||
| DXIL::OpCode::AcceptHitAndEndSearch) || | ||
| IsRayQueryOp(&InstructionToCheck, DXIL::OpCode::IgnoreHit) || | ||
| IsRayQueryOp(&InstructionToCheck, DXIL::OpCode::Discard)) { |
There was a problem hiding this comment.
🤖 Fixed in f243e07: a function containing only discard/IgnoreHit/AcceptHit and no real RayQuery operation is now left unmodified. Covered by a new regression test.
| AddEmitAnnotations(DM, Context, InstructionToInstrument, | ||
| InstructionToInstrument, "allocate", "query", | ||
| "EXITED"); |
There was a problem hiding this comment.
🤖 Fixed: a pre-pass now registers every TraceRayInline site in TraceSitesByKey before instrumentation, so emit=allocate annotations see all sites (including loops). New test added.
| Value *DemandIncrement = | ||
| AfterHitBuilder.CreateSelect(ShouldWrite, One, Zero, "IrtDemandDelta"); | ||
| Value *DemandValue = AfterHitBuilder.CreateCall( | ||
| AtomicFunction, | ||
| {AtomicOpcode, |
There was a problem hiding this comment.
🤖 Fixed: filtered events (ROI, sampling, inactive emit, helper lane) now branch around the counter atomics and record stores entirely. The zero-increment select and scratch slot are gone. New unit and lit tests check this.
| if (DM.GetEntryFunction() != nullptr || | ||
| DM.GetPatchConstantFunction() != nullptr) { | ||
| return PIXPassHelpers::GetEntryFunction(DM) == &TargetFunction; | ||
| } | ||
| return DM.HasDxilFunctionProps(&TargetFunction); |
There was a problem hiding this comment.
🤖 Fixed: the hull patch-constant function is now treated as a termination root so its RayQuery state is flushed. New test added.
| PixPassHelpers.cpp | ||
| DxilPIXAddTidToAmplificationShaderPayload.cpp | ||
| DxilPIXDXRInvocationsLog.cpp | ||
| DxilPIXRayQueryLog.cpp |
There was a problem hiding this comment.
🤖 Fixed: added a PIX entry to docs/ReleaseNotes.md under Upcoming Release.
| CComPtr<IDxcAssembler> pInputAssembler; | ||
| VERIFY_SUCCEEDED( | ||
| m_dllSupport.CreateInstance(CLSID_DxcAssembler, &pInputAssembler)); | ||
| CComPtr<IDxcOperationResult> pInputAssembleResult; | ||
| HRESULT inputAssembleResult = | ||
| pInputAssembler->AssembleToContainer(dxil, &pInputAssembleResult); | ||
| WEX::Logging::Log::Comment( | ||
| WEX::Common::String().Format(L"RayQuery input assemble result=0x%08x", | ||
| static_cast<unsigned>(inputAssembleResult))); | ||
| CComPtr<IDxcOptimizer> pOptimizer; |
There was a problem hiding this comment.
🤖 Fixed: removed the dead assembly block from the mesh helper.
| } | ||
| )x"; | ||
|
|
||
| auto compiled = Compile(m_dllSupport, source, L"cs_6_5", {}); |
There was a problem hiding this comment.
🤖 Fixed: now uses explicit CComPtr.
## Why
PIX can show DispatchRays invocations through the existing DXR invocation log, but it has no way to show TraceRayInline work. Inline RayQuery traces can come from any shader stage, so PIX cannot tell which rays a shader traced, what they hit, or which thread traced them.
## Change
This adds the `hlsl-dxil-pix-rayquery-log` module pass (`DxilPIXRayQueryLog.cpp`), which records each inline trace to PIX-owned UAVs.
- **Trace state:** each RayQuery allocation gets shadow allocas. TraceRayInline saves the ray, instance mask, effective flags (dynamic | template), AS dynamic index, trace-site ID and a per-thread trace invocation counter.
- **Retirement:** a trace writes one 64-byte query record when Proceed returns false (completed), on Abort (aborted), before the same query is traced again (retraced), and when the query is reallocated or the shader ends via return (non-GS entry), discard, IgnoreHit or AcceptHitAndEndSearch (exited). Committed getters are read only for completed/aborted records, and hit indices only when CommittedStatus reports a hit.
- **Candidates:** with `logCandidates=1`, each Proceed that returns true also writes a 48-byte candidate record to a separate log. Both record kinds carry thread identity, trace-site ID and the invocation counter, so PIX can join candidates to their query record. The dword layout is documented at the top of `DxilPIXRayQueryLog.cpp`.
- **Buffers:** counters in u2/u4 and logs in u3/u5, all in register space 0xFFFFFFFE. Each counter holds a saturating demand count and a reservation clamped to `maxNumEntriesInLog`; stores are skipped once the log is full. `maxNumEntriesInLog=0` skips instrumentation.
- **Thread identity:** per stage, e.g. thread ID for CS/AS/MS, DispatchRaysIndex for DXR shaders, VertexID/InstanceID for VS, SV_Position for PS, PrimitiveID plus GSInstanceID/OutputControlPointID for GS/HS, and PrimitiveID plus a quantized domain location for DS. Missing VS inputs are added to the signature, and SV_Position is added for PS when `upstreamSVPositionRow` is given.
- **Filtering:** `roiMin/Max{X,Y,Z}` filter by identity, `sampleRate` keeps a hash-selected subset of identity/ray combinations, and helper lanes are skipped on SM 6.6+ PS. Filtered events branch around the counter atomics and record stores rather than executing them. Trace-site IDs start at `traceSiteBase`; the pass fails if one does not fit in 16 bits.
- **Side table:** the pass prints a `PIX_RAYQUERY_LOG_V1` table describing trace sites and emit points.
- **Handle resolution:** handles that reach a RayQuery op through phis, selects, or local-memory loads are mapped back to their allocation. A load uses the last matching store earlier in its block; otherwise, for constant-index slots with no may-alias clobber, SSAUpdater runs over each block's last store (including stores that reach the load on a loop back edge). Dynamic-index loads, or slots clobbered by a dynamic array write such as `queries[i] = q`, fall back to a runtime compare of the handle against each allocation, inserted right after the handle definition so it dominates all uses.
- **Known limitation:** that fallback only considers allocations that dominate the handle definition, so it can miss one created in a sibling branch or later in a loop.
- **PixPassHelpers:** takes the register space as a parameter, rejects PIX UAV bindings that collide with an existing non-PIX resource or a root-signature UAV/descriptor-table range, recognizes AllocateRayQuery2, and creates handles at the resource's lower bound instead of index 0. These helpers are shared with the other PIX passes.
- **hctdb.py:** registers the new pass and its options.
## Validation
Added 37 `PixTest::DxilPIXRayQueryLog_*` cases and HLSLFileCheck lit tests for the pass. All 37 pass, along with the 6 existing `DxilPIXDXRInvocationsLog_*` cases (43 `PixTest::DxilPIX*` total).
The pass is also exercised end to end in the PIX repo, which has native and managed API tests plus thorough PIX UI tests that run it on real D3D12 devices in the PIX test lab. Those tests are part of the PIX change that consumes this pass and are not included here.
Co-authored-by: Copilot <[email protected]>
Copilot-Session: ffbc4911-fa2d-490d-bcd9-c76cb41f98ce
cf89972 to
f243e07
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Cross-function and pixel-sample identity issues can produce incorrect records, and one FileCheck test cannot match the generated IR.
Review effort: Balanced
Findings: 2
Open (12)
Shadow state is not propagated across function calls · New Update legacy FileCheck for the generated IR shape · New Preserve SV_SampleIndex in pixel shader query identity · New Reject subCallIndex values that exceed 16-bit range · New Patch-constant functions are omitted as shader termination roots Filtered events still perform UAV atomics and payload work Allocation annotations miss trace sites discovered later Termination ops incorrectly trigger instrumentation setup Use the explicit concrete wrapper type instead of auto · New Use explicit CComPtr types instead of auto Dead assembly result is never consumed Missing release note for the new optimizer pass
Resolved since last review (2)
| FunctionContext Context; | ||
| Context.TargetFunction = &TargetFunction; |
| // CHECK-DAG: icmp ult i32 %IrtSlot{{.*}}, 24 | ||
| // CHECK-DAG: IrtSelectedSlot = select i1 %IrtShouldStore{{.*}}, i32 %IrtSlot{{.*}}, i32 24 | ||
| // CHECK-DAG: mul i32 %IrtSelectedSlot{{.*}}, 48 | ||
| // CHECK-DAG: mul i32 %IrtSelectedSlot{{.*}}, 64 |
| Builder.CreateAnd(PixelY, HlslOP->GetU32Const(0xFFFFu)), | ||
| HlslOP->GetU32Const(16)), | ||
| "IrtPixelIdentityLo"); | ||
| return {IdentityLo, HlslOP->GetU32Const(0)}; |
| Value *Header = StoreBuilder.CreateOr( | ||
| HlslOP->GetU32Const(RecordType | (RecordFormatVersion << 4)), | ||
| StoreBuilder.CreateShl( | ||
| HlslOP->GetU32Const(Options.SubCallIndex & 0xFFFFu), |
| auto TraceRayInline = | ||
| DxilInst_RayQuery_TraceRayInline(InstructionToInstrument); |



Why
PIX can show DispatchRays invocations through the existing DXR invocation log, but it has no way to show TraceRayInline work. Inline RayQuery traces can come from any shader stage, so PIX cannot tell which rays a shader traced, what they hit, or which thread traced them.
Change
This adds the
hlsl-dxil-pix-rayquery-logmodule pass (DxilPIXRayQueryLog.cpp), which records each inline trace to PIX-owned UAVs.logCandidates=1, each Proceed that returns true also writes a 48-byte candidate record to a separate log. Both record kinds carry thread identity, trace-site ID and the invocation counter, so PIX can join candidates to their query record. The dword layout is documented at the top ofDxilPIXRayQueryLog.cpp.maxNumEntriesInLog; stores are skipped once the log is full.maxNumEntriesInLog=0skips instrumentation.upstreamSVPositionRowis given.roiMin/Max{X,Y,Z}filter by identity,sampleRatekeeps a hash-selected subset of identity/ray combinations, and helper lanes are skipped on SM 6.6+ PS. Trace-site IDs start attraceSiteBase; the pass fails if one does not fit in 16 bits.PIX_RAYQUERY_LOG_V1table describing trace sites and emit points.queries[i] = q, fall back to a runtime compare of the handle against each allocation, inserted right after the handle definition so it dominates all uses.Validation
Added 31
PixTest::DxilPIXRayQueryLog_*cases and HLSLFileCheck lit tests for the pass. All 31 pass, along with the 6 existingDxilPIXDXRInvocationsLog_*cases (37PixTest::DxilPIX*total).The pass is also exercised end to end in the PIX repo, which has native and managed API tests plus thorough PIX UI tests that run it on real D3D12 devices in the PIX test lab. Those tests are part of the PIX change that consumes this pass and are not included here.