Anyhit payload lost when calling “AcceptHitAndEndSearch()” or "IgnoreHit()"


I’m running into a bug with Vulkan raytracing on my RTX 4090 (using HLSL as my shading language, and compiling to SPIRV)

Whenever I call AcceptHitAndEndSearch() or IgnoreHit() in an anyhit program, all previous writes to the ray payload are lost.

This is an issue, since there are times where it is important to be able to terminate traversal early in an anyhit program, or to process the hit primitive without decreasing the ray T current value.

For example, I might be using an order-independent transparency method, using an anyhit shader to combine color values up until a saturation point, at which point ray traversal should end.

Or, I might want to implement a range query, tracing a zero-length ray against a set of bounding boxes, and when overlap becomes severe, I need to be able to terminate traversal in an anyhit program, storing results in a ray payload as the ray progresses through primitives.

I have a reproducer of the bug here: GitHub - natevm/GPRT-AHBugRepro

A screen capture of the bug is here. When I check the radio button, AcceptHitAndEndSearch() is conditionally called. The bug is that the spheres go black, despite having written to the ray payload before terminating traversal.

1 Like

I would like to add a +1 here to see if this behaviour can be changed / fixed (perhaps optionally, through a flag).

What my use case is for, is transparent objects using alpha blending, using path tracing and NRD denoiser. I was told by the NRD author(s) that it simply does not support stochastic transparency, and the workaround was to use rasterization in a separate pass for primary visibility transparencies only. But this is unacceptable, since I need the transparency to work in reflected / indirect rays as well.

So my solution I tried, to avoid having noisy translucencies (that NRD couldn’t denoise properly), was to store the transparency + albedo in a separate channel in the payload, and always call IgnoreHit no matter what the alpha was (other than 0), and store the alpha/base colour separately in the gbuffer, then denoise the opaque stuff, then blend it back in, during compositing pose-denoise, but pre-upscaling. DLSS would then take care of the rest of noise remaining.

I’m using Vulkan RT with HLSL shaders (compiled to SPIR-V through DXC.exe), rather than DX12/DXR, but I expect it has the same issues or bugs in either API.

I discovered that this bug with the AcceptHitAndExit and IgnoreHit calls has to do with these intrinsics being called by a nested function calls inside the anyhit entry point. If I call these intrinsics in the main body, these functions behave as expected.

I will test this myself shortly in my AnyHit and report back, but I trust it’s probably the same for me. However, my original bug wasn’t using any nested function calls, and I’ve been using HLSL → dxc → SPV pipeline for ~2 years. I admit I haven’t tested it recently so it may indeed be fixed (or could have just been a bug in my code, but I don’t think so since it’s dead simple).

Welcome to the NVIDIA developer forums @natemorrical !

Good to hear that you could figure out how to fix your issue!

I found a work around, but this isn’t a solution. There does appear to be a bug in NVIDIA’s drivers here. If it’s part of the spec that I’m allowed to call IgnoreHit / AcceptHit from nested functions, that shouldn’t give me undefined behavior…

Thanks for clarifying this!

I forwarded the information, let’s see if we can get some engineering response.

Good news! This is already being tracked as an internal bug.

As it is with these things I cannot make any statement on whether it can be fixed and how long it might take.

But I will try to keep track of it and update here when it has been resolved.

1 Like

Thanks natemorrical and belakampis1 for reporting the issue!

We believe this is a DXC codegen bug when translating from HLSL to SPIRV and not a bug in NVIDIA’s drivers.

You can find exact details of the issue here:

In order to make this work, you will have to implement ‘unwind/cleanup’ semantics to copy out to payload manually in the shader as I have explained in the DXC bug above