Skip to content

[WSL / D3D12, NVIDIA] GPU timeouts (TDR, DXGI_ERROR_DEVICE_HUNG) after a depth-stencil CopyResource under sustained load #42283

Open

Description

@chaoyen1980

Windows Version

Windows 11 25H2, build 26200.9711

WSL Version

WSL 3.0.5.0, kernel 6.18.58.1-1, WSLg 1.0.79

Are you using WSL 1, WSL 2, or WSLC?

  • WSL 2
  • WSL 1
  • WSLC

Kernel Version

6.18.58.1-1

Distro Version

Ubuntu 24.04

Other Software

  • Windows 11 25H2 (26200.9711), WSL 3.0.5.0, kernel 6.18.58.1-1, WSLg 1.0.79

  • Direct3D (WSL) 1.611.1-81528511, DXCore 10.0.26100.1

  • NVIDIA RTX PRO 500 Blackwell Generation Laptop GPU, driver 32.0.15.9595 (also the Intel iGPU, not used)

  • The translation layer: Wine/Proton 11 with a D3D12 passthrough to the WSL runtime (patches available)

Repro Steps

There is no standalone reproduction yet; the hang happens in a game.

  1. WSL2 with WSLg on an NVIDIA GPU (versions above), Ubuntu 24.04.
  2. Steam for Linux in WSL, running Diablo IV (Steam build) through Wine/Proton 11 with a D3D12 passthrough: every D3D12 call is forwarded unchanged to /usr/lib/wsl/lib/libd3d12.so (no vkd3d, no Vulkan).
  3. Play normally (town, open world, dungeons) at 3440x1440 or 1920x1200.
  4. After 8–45 minutes the GPU times out: all displays freeze for ~2–5 s, Windows logs nvlddmkm event 153 (usually two, 3–4 s apart), and the game's device reports DXGI_ERROR_DEVICE_HUNG (0x887A0006).

Seen 16 times on 2026-11-13 and 7 times on 2026-11-14 (session table in the attached report). No GPU timeouts in the 30 days before, outside these sessions.

Where the GPU stops (our own breadcrumbs via WriteBufferImmediate; DRED returns no data on WSL), in all 7 captured hangs, on the direct queue:
DrawIndexedInstanced xN into depth A (R24G8_TYPELESS, 128x128 or 256x256)
ResourceBarrier A DEPTH_WRITE -> COPY_SOURCE, B SRV -> COPY_DEST
CopyResource(B, A) <- GPU stops here
ResourceBarrier B COPY_DEST -> SRV <- or here
A standalone program looping exactly this sequence (attached, depthcopy/)
ran >8 million iterations on WSL without a hang, so it does not reproduce alone.

Expected Behavior

The same workload runs without GPU timeouts, as it does on Windows with the
same GPU and driver.

Actual Behavior

A GPU timeout (TDR) every 8–45 minutes of play: nvlddmkm event 153, all
displays freeze for a few seconds, DXGI_ERROR_DEVICE_HUNG.
Copying the depth texture per subresource, or through a buffer
(texture -> buffer -> texture), does not prevent it.
Recovering takes 15 s to over 3 minutes, because the game has to release tens
of thousands of objects through the WSL runtime first (details in the report);
after more than about a minute the game's online session is lost.

Diagnostic Logs

Attachments: GPU timeout events (nvlddmkm 153), passthrough removal logs, and the standalone stress program (depthcopy). Full write-up in the first comment.

02-attachments.zip

Activity

  1. github-actions commented on Nov 15, 2026

    @github-actions
    Contributor

    No logs.etl found in the archive. Make sure that you ran collect-wsl-logs.ps1 as administrator and that the logs.etl file is in the archive.

    Diagnostic information
    appxpackage.txt not found
    optional-components.txt not found
    No logs.etl found in archive.
    Error while parsing the logs. See action page for details
    
  2. chaoyen1980 commented on Nov 15, 2026

    @chaoyen1980
    Author

    Summary
    A D3D12 game (Diablo IV, Steam build) runs on WSL2 through a Wine passthrough
    to /usr/lib/wsl/lib/libd3d12.so: every D3D12 call is forwarded unchanged to
    the WSL runtime and the NVIDIA WSL driver. After 15–45 minutes of play the GPU
    times out. Windows logs nvlddmkm event 153, every display freezes for about
    2 s, and the device reports DXGI_ERROR_DEVICE_HUNG. With TdrDelay=10 the hang
    still happens (event pairs 13 s apart), so the GPU is stuck, not slow. No
    TDRs occurred on this machine in the previous 30 days outside these sessions.

    Where the GPU stops
    Our own breadcrumbs (WriteBufferImmediate MARKER_IN/MARKER_OUT around every
    operation; DRED returns no auto-breadcrumbs or page-fault data on WSL) caught
    7 hangs. All of them are in the same short sequence on the direct queue, in a
    pass that renders a 128×128 or 256×256 view:

    DrawIndexedInstanced ×N into depth A (R24G8_TYPELESS, ALLOW_DEPTH_STENCIL, committed, DEFAULT heap)
    PSO: DSV D24_UNORM_S8_UINT, 4 BGRA8 RTs, depth LESS_EQUAL write,
    stencil REPLACE/ALWAYS front, NEVER back, write mask 0xFC, cull back, CCW
    ResourceBarrier: A DEPTH_WRITE -> COPY_SOURCE, B PIXEL|NON_PIXEL_SHADER_RESOURCE -> COPY_DEST
    CopyResource(B, A) (same size and format)
    ResourceBarrier: B COPY_DEST -> PIXEL|NON_PIXEL_SHADER_RESOURCE (or DEPTH_WRITE), A COPY_SOURCE -> DEPTH_WRITE
    DrawInstanced(3) / DrawIndexedInstanced sampling B
    The GPU is either inside the CopyResource or inside the barrier right after
    it. Splitting the copy per subresource (CopyTextureRegion per plane) moves the
    hang to the following barrier. The compute queue is merely waiting on the
    direct queue (no cross-queue deadlock).

    What does not reproduce it
    A standalone program that loops exactly this sequence, with the same formats,
    sizes, pipeline states, an async compute queue with cross-queue waits, and
    130% VRAM over-commitment with continuous resource churn, ran more than
    8 million iterations through the same passthrough on WSL without a hang.
    Native Windows passes too. So the trigger depends on state we cannot see from
    the API.

    How often
    Sessions on 2026-11-14 (one game launch each; times are local):

    Launch GPU timeout Time to timeout Recovery (removal to new swap chain)
    08:29 08:59 30 min 17 s
    09:42 none (left at 10:45) > 63 min
    10:46 11:02 16 min none: stopped with 415 resources unreleased
    11:08 none (game exited at 13:20 for an unrelated reason) > 2 h 12 min
    13:31 13:46 15 min not counted (our diagnostics interfered)
    14:08 14:29 21 min 66 s
    14:40 15:24 44 min 3 min 8 s
    On 2026-11-13, a day of testing on the same machine, Windows logged 16 timeouts
    (nvlddmkm event 153, in pairs).

    Recovery takes minutes
    After DXGI_ERROR_DEVICE_HUNG the game releases everything and creates a new
    device (a D3D12 device is a per-adapter singleton, so the old one must be
    fully released first). On WSL this takes 17 s to more than 3 minutes,
    growing with play time, because the game by then holds tens of thousands of
    objects:

    At 15:24: about 51,000 device references, among them 21,700 pipeline
    states and 27,900 resources (almost all small block-compressed textures,
    64–512 px, each a separate CreateCommittedResource in a DEFAULT heap).
    Pipeline states were released at only 150–500 per second (2–7 ms each).
    A CPU profile of the game process during this phase: game code 38%,
    glibc free 26%, libnvwgf2umx.so 24%, libd3d12core.so 6%, the
    passthrough under 1%. For comparison, a test program releases 4,000
    pipeline states that share one shader in 16 ms (about 4 µs each), so the
    cost seems to be per unique pipeline.
    A recovery longer than about a minute disconnects the game from its online
    service, so for the player every timeout ends the session.

    Related observations in the same sessions
    After some timeouts the game keeps a few resources of the removed device
    (12 small buffers in one case, 415 resources in another), so its next
    D3D12CreateDevice() returns DXGI_ERROR_DEVICE_REMOVED and the game
    cannot recover. This is the game's side, worked around in the
    passthrough; mentioned because it is part of what the player sees.
    Glyphs and parts of map textures go missing after long play (UI text,
    e.g. "16.7%" drawn as "16. %"), but only with the game in Traditional
    Chinese; in English it does not happen. Probably the game's glyph cache,
    which changes far more often with Chinese text; not investigated further here.
    Submission is about 10× more expensive than native: ExecuteCommandLists
    230–450 µs, command list Reset/Close 60–75 µs, allocator Reset 18–30 µs,
    queue Signal 35–40 µs, queue Wait 85 µs (measured per call around the WSL
    runtime).
    Environment
    Windows 11 25H2 (26200.9711), WSL 3.0.5.0, kernel 6.18.58.1-1, WSLg 1.0.79
    Direct3D (WSL) 1.611.1-81528511, DXCore 10.0.26100.1
    NVIDIA RTX PRO 500 Blackwell Generation Laptop GPU, driver 32.0.15.9595 (also the Intel iGPU, not used)
    The translation layer: Wine/Proton 11 with a D3D12 passthrough to the WSL runtime (patches available)
    Attachments (02-attachments/)
    windows-gpu-timeouts.txt: every nvlddmkm event 153 on this machine
    (2026-11-12 to 2026-11-14).
    passthrough-removal-log.txt: launches, device removals, and the resources
    still alive when a recovery stalled (2026-11-14 afternoon and evening).
    removal-release-progress.txt: device references and live objects every
    0.25–2 s during those recoveries.
    depthcopy/: the standalone stress program (source, shaders, run script).
    Breadcrumb dumps of the 7 hangs are summarized above; the raw logs were
    rotated away. The passthrough source is at (link to the patches).

  3. chaoyen1980 commented on Nov 15, 2026

    @chaoyen1980
    Author

    Possibly related: #42261 reports a memcpy in libd3d12core.so (same 2023-10-20
    build) overrunning a 128 KiB shared allocation under a real 3D workload, sometimes
    surfacing as a lost device, and not reproducible with a synthetic harness. Our
    #42280 is also heap corruption inside libd3d12core. If a similar overrun hit
    data shared with the GPU instead of a guard page, it could explain hangs like
    these that only a real game triggers. This is a guess; we have no direct
    evidence of an overrun in our case.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions