Skip to content

[PIX] Add RayQuery invocation logging pass for inline raytracing - #9008

Open
Austin Kinross (austinkinross) wants to merge 1 commit into
microsoft:mainfrom
austinkinross:aukinros/rayquery-log-pass
Open

Austin Kinross (austinkinross) wants to merge 1 commit into
microsoft:mainfrom
austinkinross:aukinros/rayquery-log-pass

Conversation

@austinkinross

Copy link
Copy Markdown
Member

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. 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 31 PixTest::DxilPIXRayQueryLog_* cases and HLSLFileCheck lit tests for the pass. All 31 pass, along with the 6 existing DxilPIXDXRInvocationsLog_* cases (37 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.

Copilot AI balanced review requested due to automatic review settings October 6, 2026 17:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Medium severity · 3 Low severity

Open (9)
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.

Comment on lines +493 to +496
IsRayQueryOp(&InstructionToCheck,
DXIL::OpCode::AcceptHitAndEndSearch) ||
IsRayQueryOp(&InstructionToCheck, DXIL::OpCode::IgnoreHit) ||
IsRayQueryOp(&InstructionToCheck, DXIL::OpCode::Discard)) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

Comment on lines +572 to +574
AddEmitAnnotations(DM, Context, InstructionToInstrument,
InstructionToInstrument, "allocate", "query",
"EXITED");

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

Comment on lines +1805 to +1809
Value *DemandIncrement =
AfterHitBuilder.CreateSelect(ShouldWrite, One, Zero, "IrtDemandDelta");
Value *DemandValue = AfterHitBuilder.CreateCall(
AtomicFunction,
{AtomicOpcode,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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.

Comment on lines +2288 to +2292
if (DM.GetEntryFunction() != nullptr ||
DM.GetPatchConstantFunction() != nullptr) {
return PIXPassHelpers::GetEntryFunction(DM) == &TargetFunction;
}
return DM.HasDxilFunctionProps(&TargetFunction);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Fixed: the hull patch-constant function is now treated as a termination root so its RayQuery state is flushed. New test added.

Comment thread lib/DxilPIXPasses/PixPassHelpers.cpp Outdated
Comment thread lib/DxilPIXPasses/PixPassHelpers.cpp Outdated
PixPassHelpers.cpp
DxilPIXAddTidToAmplificationShaderPayload.cpp
DxilPIXDXRInvocationsLog.cpp
DxilPIXRayQueryLog.cpp

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Fixed: added a PIX entry to docs/ReleaseNotes.md under Upcoming Release.

Comment thread tools/clang/unittests/HLSL/PixTest.cpp Outdated
Comment on lines 1174 to 1183
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;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Fixed: removed the dead assembly block from the mesh helper.

Comment thread tools/clang/unittests/HLSL/PixTest.cpp Outdated
}
)x";

auto compiled = Compile(m_dllSupport, source, L"cs_6_5", {});

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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
Copilot AI balanced review requested due to automatic review settings October 6, 2026 21:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +519 to +520
FunctionContext Context;
Context.TargetFunction = &TargetFunction;
Comment on lines +15 to +18
// 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),
Comment on lines +605 to +606
auto TraceRayInline =
DxilInst_RayQuery_TraceRayInline(InstructionToInstrument);

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

2 participants