RenderThread C++ exception on pre-RTX DXR GPUs after r.RayTracing.UseReferenceBasedResidency=0

UE 5.7.4, Win64 Shipping, D3D12. Lumen GI + Reflections, r.RayTracing=True,

MegaLights on, r.Lumen.HardwareRayTracing=1 from Medium scalability.

We’re getting ~3.5k shipped crashes where RenderThread 0 dies on a real C++ throw

(0xE06D7363, RaiseException -> _CxxThrowException) and the minidump has zero frames

for that thread, so we assume it comes out of the D3D12 UMD. GameThread just picks

it up later in CheckRenderingThreadHealth.

What’s odd is the hardware: the top GPUs are exclusively pre-RTX DXR-capable cards

(GTX 1660/1650/1060/1070/1080, 4-8 GB) and Intel iGPUs — no RTX at all, across all

three vendors. The regression lines up with us flipping

r.RayTracing.UseReferenceBasedResidency from 1 to 0.

Three things we’d like your view on:

- Is an unwalkable C++ throw on the render thread a known signature of a D3D12

allocation failure, and how would you get a usable callstack for it in Shipping?

- How much does UseReferenceBasedResidency=0 cost in VRAM in 5.7, and is 0 still

supported?

- GRHISupportsRayTracing is true on Pascal/GTX 16-series because the driver emulates

DXR. Is there a finer capability flag we should use to keep hardware Lumen and

MegaLights off those cards, or is a DeviceProfile/VRAM rule the intended way?

[Attachment Removed]

Hi Aleksandr,

Thanks for reaching out. I want to call out that our own D3D12 out-of-memory path does not throw an exception. On E_OUTOFMEMORY, HandleFailedD3D12Result calls TerminateOnOutOfMemory (D3D12Util.cpp:1045), which logs, sets bIsOOM, and then exits silently by default, because r.GPUCrashOnOutOfMemory defaults to 0 (RHI.cpp:1100, exit at D3D12Util.cpp:814). The reasoning is that a VRAM exhaustion is not necessarily an engine bug, but the side effect is that genuine VRAM exhaustion in 5.7 Shipping produces no crash report at all. So an unwalkable throw is not a known signature of our allocation failure, but it is very plausibly the driver’s own allocation failure on a path we cannot unwind.

To get something usable in Shipping, set r.GPUCrashOnOutOfMemory=1 so VRAM exhaustion produces a real report instead of a silent exit. Make sure you have **r.GPUCrashDebugging.Breadcrumbs=**1, and add r.D3D12.LightweightDRED=1 for the device removed bucket, which gives you the last executed operations on all PC hardware at low cost. Full r.D3D12.DRED=1 gives the most detail but incurs real GPU overhead, so we tend to keep it off on Intel. Aftermath will only help you on the NVIDIA subset, and on the emulated DXR cards, it will not resolve driver internal frames either, so breadcrumbs plus DRED plus the OOM report is the combination that will actually tell you which pass was in flight.

How much does UseReferenceBasedResidency=0 cost in VRAM in 5.7, and is 0 still supported?

On the VRAM cost, this is where the regression almost certainly comes from. With r.RayTracing.UseReferenceBasedResidency=1, resident acceleration structure memory is budgeted. UpdateReferenceBasedResidency evicts unreferenced geometry, using least-recently-used and largest-first, whenever the resident plus in-flight plus requested total exceeds r.RayTracing.ResidentGeometryMemoryPoolSizeInMB, and only streams referenced geometry back in while under budget. That pool defaults to 400 MB, and only r.RayTracing.NumAlwaysResidentLODs, is pinned outside it.

With it set to 0, that entire budget is bypassed. Geometry is queued as resident at registration time (RayTracingGeometryManager.cpp:327). When LOD ranges change, UpdateReferenceBasedResidency never runs, and nothing is ever evicted, so the resident BLAS becomes every loaded mesh in the level instead of 400 MB. There is a second cost that is easy to miss: on-demand streaming of the VB/IB copies needed for dynamic geometry updates is force disabled when residency is off (RayTracingGeometryManager.cpp:1133), so those stay resident too. On a 4 GB card, that is the whole story, and emulated DXR makes it worse, since Pascal and the GTX 16 series implement DXR in compute, and their acceleration structures and build scratch are larger than on RT core hardware for the same mesh set.

UseReferenceBasedResidency=0 is still supported in the sense that it is present and functional in 5.7, 5.8, and Main, all still defaulting to 1, but it is a debug path rather than a shipping configuration, and nothing in the engine bounds VRAM when it is off. If the original motivation was hitching from eviction and rebuild churn, the better lever is to raise r.RayTracing.ResidentGeometryMemoryPoolSizeInMB per device profile, and leave residency on, so high-VRAM machines get a large pool and 4 GB machines stay bounded. Bumping r.RayTracing.NumAlwaysResidentLODs will likely help if specific assets are visibly popping.

Is there a finer capability flag we should use to keep hardware Lumen and MegaLights off those cards, or is a DeviceProfile/VRAM rule the intended way?

Unfortunately, for the capability question, there is no finer flag. FRHIGlobals::FRayTracing exposes Supported, SupportsShaders, SupportsInlineRayTracing, SupportsPSOAdditions, SupportsDispatchIndirect, compaction and alignments, and none of those distinguish hardware RT cores from driver emulation. The best way would likely be to create a DeviceProfile for the GPUs that you intend to target.

[Attachment Removed]