protonscr

[NVIDIA RTX 2060 / Turing] Forza Horizon 6: Xid 109 GPU hang on sparse model buffer commits — persists after forza-workarounds

vkd3dclosed
HansKristian-Work/vkd3d-proton#3053 · opened 2026-05-20 by DBOYttt · updated 2026-05-21 · 4 comments · github
DDBOYttt 2026-05-20 github

System

GPU NVIDIA GeForce RTX 2060 (Turing, 6 GB VRAM)
NVIDIA driver 580.159.04
OS Fedora Linux 44 (Workstation)
Kernel 7.0.7-200.fc44.x86_64
Proton Proton Experimental experimental-11.0-20260506b
VKD3D-Proton forza-workarounds CI build — merge commit 3d98c83dd7ce3a00a22165eccacbd71b057e7846 (2026-05-20, includes all 5 commits from PR #3033)
VKD3D_CONFIG no_upload_hvv,single_queue

Game

Forza Horizon 6 (Steam AppID 2483190)

All NVIDIA-specific features disabled in-game: Frame Generation = Off, NVIDIA Reflex = Off, RT = Off.

Symptom

Kernel Xid 109 (CTX_SWITCH_TIMEOUT) causes a GPU hard hang. Game reports FHC00 Video Card Crash. Reproducible every session at two distinct crash points described below.

NVRM: Xid (PCI:0000:01:00): 109, pid=…, name=forzahorizon6.e, channel 0x…, errorString CTX SWITCH TIMEOUT

Crash history and what each change affected

VKD3D build VKD3D_CONFIG AA/upscaling Crash point Game uptime
Proton Exp default (commit 5204) no_upload_hvv NoAA Open world first entry ~20 min
3.0.1 no_upload_hvv NoAA Same ~20 min
forza-workarounds merge no_upload_hvv NoAA section_two_transition_in, model buffer 580/640 MB (91%) ~5 min
forza-workarounds merge no_upload_hvv,single_queue + lower geometry NoAA wait_for_car_loading_flow_load_cars, model buffer 380/805 MB (47%) ~36 min
forza-workarounds merge no_upload_hvv,single_queue + lower geometry DLSS enabled wait_for_free_roam_start, model buffer 198/201 MB (98.5%) ~5.5 min
forza-workarounds merge no_upload_hvv,single_queue + lower geometry DLSS disabled (NoAA) wait_for_free_roam_start, model buffer 198/201 MB (98.5%) ~2 min

Crash report detail (latest best run — race loading, 36 min)

From the game's own CrashReport.xml:

<REASON Value="hang55"/>
<REASON_INFO Value="0x0000000000005653"/>
<UI_SCENE Value="EmptySceneWithSpinner"/>
<UPTIME Value="2186.177002"/>
<UPTIME_DRIVING Value="988.017029"/>

<VRAM_BUDGET Value="5510987776"/>   <!-- 5.1 GB -->
<VRAM_USED Value="4396548096"/>     <!-- 4.1 GB = 81% of budget -->

<SYSTEM_MODEL_BUFFER_ALLOCATOR_TYPE Value="Sparse"/>
<SYSTEM_MODEL_BUFFER_ALLOCATOR_STATUS Value="380054816, 805306368, 2147483648"/>
<!-- 380 MB used / 805 MB soft limit / 2 GB hard max -->

<DIRECT_WRITE_CONSTANT_BUFFER_ALLOCATOR_TYPE Value="Sparse"/>

<LAST_STATE_MACHINE_STACK Value="wait_for_loading_complete_no_snapshot,
  wait_for_car_loading_flow_load_cars, prepare_offline_race_activation,
  activate_offline_race, activate_race, activate_race_flow,
  event_select_and_activate, campaign_menu, prepare_and_menu,
  activation_flow, root"/>

<D3D_HR_ERROR Value="0x00000000"/>  <!-- no D3D12 error reported -->

Earlier crash detail (open-world section transition, partially fixed by single_queue)

<REASON Value="hang55"/>
<REASON_INFO Value="0x00000000000055fe"/>
<UI_SCENE Value="InitialDriveSeasonTransition"/>
<LAST_STATE_MACHINE_STACK Value="wait_for_next_frame, section_transition_common, section_two_transition_in"/>

<SYSTEM_MODEL_BUFFER_ALLOCATOR_TYPE Value="Sparse"/>
<SYSTEM_MODEL_BUFFER_ALLOCATOR_STATUS Value="608869904, 671088640, 2147483648"/>
<!-- 580 MB used / 640 MB soft limit = 91% -->

DLSS Super Resolution also triggers the bug — and corrupts the shader cache

Enabling DLSS Super Resolution (not Frame Generation) caused an immediate regression: the crash moved from the race-loading boundary (~36 min) back to the initial open-world loading phase (wait_for_free_roam_start, ~5 min). The model buffer soft limit during this phase is only 201 MB and DLSS pushed usage to 198 MB (98.5%) — the same sparse commit boundary, just hit much earlier because DLSS initialization loads additional GPU resources.

<!-- DLSS crash -->
<UserConfig_AATypeId Value="DLSS"/>
<UserConfig_AAModeId Value="Auto"/>
<UPTIME Value="334.249023"/>
<UPTIME_DRIVING Value="0.000000"/>   <!-- hadn't started driving -->
<SYSTEM_MODEL_BUFFER_ALLOCATOR_STATUS Value="198507792, 201326592, 2147483648"/>
<!-- 198 MB / 201 MB soft limit = 98.5% -->
<LAST_STATE_MACHINE_STACK Value="wait_for_event, wait_for_free_roam_start, root"/>

Disabling DLSS (back to NoAA) did not restore stability — the game continued crashing at the same wait_for_free_roam_start boundary within ~2 minutes. Root cause: toggling DLSS on and off corrupted the Fossilize pipeline cache (1.9 GB total — 442 MB Fossilize .foz files + 1.5 GB NVIDIA shader cache), leaving it with a mix of DLSS and non-DLSS shader variants. The game was stalling during runtime shader compilation at the exact moment of the large sparse commit batch, changing the timing enough to consistently trigger the race.

Fix: deleting both caches (shadercache/2483190/fozpipelinesv6/ and shadercache/2483190/nvidiav1/) and allowing them to rebuild from scratch restored stability to the previous level (able to reach race-loading boundary again).

This confirms that the crash is sensitive to the exact timing of GPU operations during the sparse page commit window — anything that changes shader compilation timing (new pipeline variants, cache misses) can move the crash point earlier or later within the same session.

Hypothesis

All crashes share the pattern:

  • SYSTEM_MODEL_BUFFER_ALLOCATOR_TYPE = Sparse — the game commits large batches of sparse buffer pages during scene/asset transitions
  • No D3D12 error (D3D_HR_ERROR = 0x00000000) — the hang is not reported by the D3D12 runtime
  • Xid 109 (GPU context switch timeout) — the GPU channel stalls, consistent with accessing not-yet-committed sparse pages (use-before-alloc)
  • The crash is timing-sensitive: it occurs at specific sparse commit boundaries (201 MB, 640 MB, 805 MB soft limits) and anything that changes GPU scheduling during those windows (upscaling modes, shader cache state) shifts when the hang triggers

single_queue serialises the submission ordering and pushed the crash from the 640 MB boundary (~5 min) to the 805 MB boundary (~36 min). It does not prevent the hang at either boundary — it only changes which one is hit first.

What partially worked (workarounds)

  1. forza-workarounds CI build (PR #3033 merge) — replaced VKD3D-Proton DLLs in Proton Experimental with the CI artifact from the forza-workarounds branch. Moved crash from ~20 min (first open-world entry) to ~5 min (section_two_transition_in).

  2. VKD3D_CONFIG=no_upload_hvv,single_queue — adding single_queue moved the crash from section_two_transition_in (~5 min, 640 MB boundary) to wait_for_car_loading_flow_load_cars (~36 min, 805 MB boundary).

  3. Lowering Environment Geometry Quality (Medium → Low) — reduced model buffer peak from 91% to 47% at the 640 MB boundary, helping single_queue get past it.

  4. Keeping DLSS / Reflex / Frame Generation off — DLSS Super Resolution alone (without FG) is enough to push the 201 MB initial-load boundary to 98.5%, causing an earlier crash than the 640 MB boundary.

The race-loading crash at ~36 min remains unsolved. Opponent car loading triggers a fresh large sparse commit while VRAM is already at 81%, and single_queue alone does not prevent the hang at that boundary.

Notes

  • Issue #3050 (Intel Arc / xe VM_BIND retry loop) is a distinct failure mode; this report is NVIDIA only
  • The Mesa MR #41680 mentioned in PR #3033 comments addresses the AMD/RADV side; an equivalent synchronization fix may be needed for the NVIDIA Vulkan path in vkd3d-proton
  • An RTX 4080 user in PR #3033 comments reports the same crash at ~10 min, confirming this is not Turing-specific
DDBOYttt 2026-05-20 github

Workaround update — 2026-05-20

Additional regression: RT Reflections (even at Low) worsens cold-cache loading

After the cache-corruption incident described above (deleting 442 MB Fossilize + 1.5 GB NVIDIA cache), the game crashed consistently at `wait_for_free_roam_start` within 73–91 seconds even after rebuilding some cache — faster than before the caches were cleared.

Root cause: with a cold shader cache the NVIDIA driver must compile pipeline states at runtime during the initial world load. The concurrent GPU work (sparse page commits + runtime shader compilation) was enough to trigger Xid 109 even with the forza-workarounds use-before-alloc fix active. Anything that increases the number of shader variants (RT pipelines, DLSS variants, etc.) makes the window narrower.

Fix that got past initial loading:

Setting RT Reflections = Off (was Low) in the game graphics settings:

<!-- UserConfigSelections -->
<option id="RTReflectionQuality" value="0"/>  <!-- was value="1" (Low) -->

RT shaders are among the most expensive pipeline variants to compile. Removing them from the compile queue during loading reduced concurrent GPU pressure enough that the game pushed through `wait_for_free_roam_start` while the caches are still rebuilding.

Current working configuration

Setting Value
VKD3D-Proton forza-workarounds CI build (merge 3d98c83d)
VKD3D_CONFIG no_upload_hvv,single_queue
RT Reflections Off (value=0)
RT GI Off
DLSS / FG / Reflex Off
Fossilize cache ~107 MB (rebuilding; was 442 MB before incident)
NVIDIA shader cache ~263 MB (rebuilding; was 1.5 GB before incident)

Current best session result

<UPTIME Value="2011.903076"/>          <!-- 33.5 minutes total -->
<UPTIME_DRIVING Value="885.358032"/>   <!-- 14.75 minutes driving -->
<LAST_STATE_MACHINE_STACK Value="wait_for_event_start, main_loop_internal, main_loop, root"/>
<SYSTEM_MODEL_BUFFER_ALLOCATOR_STATUS Value="590112608, 805306368, 2147483648"/>
<!-- 562 MB used / 768 MB soft limit = 73% -->
<VRAM_USED Value="4070899712"/>   <!-- 3.79 GB / 5.19 GB budget -->

Game loaded successfully, completed a race, and played open world for ~33 minutes before crashing at event/race loading — same 805 MB sparse commit boundary as previously reported. This is consistent with the pre-incident behaviour.

The shader caches are continuing to grow with each session. The crash at the 805 MB boundary during race/event loading is the remaining unfixed issue.

Summary of timing sensitivity

This crash is highly sensitive to GPU scheduling during sparse commit windows:

  • Warm cache (442 MB foz + 1.5 GB NVIDIA): crashes at 36 min (805 MB boundary)
  • Cold cache + RT Reflections On: crashes at 73–91 sec (initial load, 335 MB boundary)
  • Cold cache + RT Reflections Off: passes initial load, crashes at ~33 min (805 MB boundary)

The underlying race (use-before-alloc on sparse page commit) appears to be partially mitigated by the forza-workarounds build but not fully covered for all commit sizes/timing patterns, particularly the large 590 MB commit during race/event loading.

Gglowtape 2026-05-20 github

The work-around branch seems to rely on the descriptor heap implementation. Is that even in the 580 branch of drivers?

Mmati865 2026-05-21 github

No, it's added in 595 branch but not working very well. Vulkan beta drivers 596 improve that a lot.

HHansKristian-Work maintainer 2026-05-21 github

Slop. Root cause is known and NV is aware. Closing.

Proton versions

Launch options