[Root cause] NaniteRasterizer.usf, BRICK_TRACE_WORK_REDISTRIBUTION path in
MicropolyRasterize.
Groupshared declarations:
groupshared uint GroupWorkEnd[32];
groupshared uint3 GroupBrickData[32];
groupshared uint GroupSourceLaneAndPixelPos[64];
Inside the per-32-pixel work-redistribution loop:
const uint MarkBufferValue = GroupWorkEnd[ GroupThreadIndex ]; // unmarked = 0xFFFFFFFF
const uint BrickStartMask = WaveBallot( MarkBufferValue != 0xFFFFFFFFu ).x;
const int BrickStartIndex = firstbitlow( BrickStartMask & LaneMask );
const uint BrickLaneData = WaveReadLaneAt( MarkBufferValue, BrickStartIndex );
const uint BrickLane = BrickLaneData & 0xFFu;
const uint3 BrickData = GroupBrickData[ BrickLane ]; // <-- OOB read
For the tail lanes of the final (partial) 32-pixel chunk, no end-mark exists at or
after the lane, so:
- BrickStartMask & LaneMask == 0 -> firstbitlow(0) == -1
- WaveReadLaneAt(x, -1) is undefined; on NVIDIA the shuffle index is masked (& 31)
-> reads lane 31, which is unmarked (0xFFFFFFFF)
- BrickLane = 0xFF = 255
- GroupBrickData[255] indexes a 32-element array -> LDS out-of-bounds.
Byte offset = 384 + 255*12 = 3444 = 0xD74, exactly the Aftermath fault address.
BrickData is only *used* under `if (bActive)` (bActive = PixelIndex < TotalPixels),
which is false for exactly these tail lanes, so the read value is dead. In normal
execution the OOB LDS read is silently tolerated; under Aftermath / GPU-crash-
debugging bounds checking (and/or on Blackwell) it becomes a hard out-of-range fault
and hangs the device.
The sibling unpack site in ProcessBrickPixelBatchFromQueue already masks the same
lane value with `& 31u`; the read in the redistribution loop is missing the guard.
[Suggested fix]
const uint3 BrickData = GroupBrickData[ BrickLane & 31u ];
or skip lanes where (BrickStartMask & LaneMask) == 0. Behavior-preserving: such lanes
always fail the following bActive test.
[Attachment Removed]
重现步骤
Environment:
- UE 5.8.0 (GitHub source, release branch, CL ab58c879a25b / 5.8.0-0+UE5)
- D3D12, SM6, Nanite enabled, r.Nanite.Foliage=1
- GPU: NVIDIA RTX 5060 Ti (Blackwell); seen on drivers 591.86 and 610.62(but should happen on every NVIDIA cards regardless of driver versions)
- Editor launched with: -nvaftermathall -gpucrashdebugging
Steps:
1. Open a scene containing Nanite foliage that exercises the Nanite voxel (brick)
rasterizer (breadcrumb path Nanite::VisBuffer > DrawGeometry > PatchSplit).
2. Launch with -gpucrashdebugging -nvaftermathall so GPU-side bounds checking traps
the out-of-range access.
3. Render the foliage in the viewport / PIE. The GPU hangs within seconds to minutes.
Result: DXGI_ERROR_DEVICE_HUNG, deterministic (identical fault address every time).
On the clean-repro requirement: the defect is entirely inside stock engine shader
code (Engine/Shaders/Private/Nanite/NaniteRasterizer.usf); no third-party plugins or
project content are involved, and the code can be verified directly in your 5.8
source. The source-level OOB read exists regardless of GPU/driver -- GPU crash
debugging / Blackwell is only what turns the silent OOB into a hard fault. We can
provide the exact foliage asset + GPU dump on request.
[Attachment Removed]
Hi, thanks for calling this out. We have encountered this crash ourselves and shipped a patch for this issue in 5.8.1. If you don’t plan to get the hotfix, you can still grab CL 55449171 and apply it to your own build. I hope that helps, but please let me know if you have any further questions.
[Attachment Removed]
No worries! If anything, it gives others an opportunity to find a solution to this bug on our forums now, so it’s still good that you reached out. I’ll close out this ticket then, since I assume everything is resolved.
[Attachment Removed]
Hi, thanks for the reply. The patch helps a lot!
I figured out the bug several weeks ago and fixed it in our own build, but my eps account expired then so I uploaded this bug until today when the account restored but I forgot to check the github log, apologies
Thanks again!
[Attachment Removed]