HwRender: WPF crashes on ARM64 when GPU is suspended during frame presentation (DXGI_ERROR_DEVICE_REMOVED)
- Dominant language
- C#
- Stars
- 7.7k
- Forks
- 1.3k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 61
Description
# [HwRender] WPF crashes on ARM64 when GPU is suspended during frame presentation
## Summary
On ARM64 devices, WPF crashes when the GPU is suspended (e.g. during power management transitions) while the compositor thread is in the middle of calling `IDirect3DSwapChain9::Present`. The process terminates with a fatal exception rather than recovering gracefully.
## Observed behavior
- WPF application crashes on ARM64 hardware when the GPU is suspended or power-cycled during rendering.
- Fault bucket signature: `XAML_887a0005_wpfgfx_cor3.dll!VS_FatalError`
- `IDirect3DSwapChain9::Present` returns `0x887A0005` (`DXGI_ERROR_DEVICE_REMOVED`).
- The crash is ARM64-specific; x64 is not affected.
## Repro notes
1. Run a WPF application on ARM64 hardware (e.g. Snapdragon-based Windows device) with continuous rendering activity (animations, video, or rapid UI updates).
2. Trigger a GPU power-management event: sleep/resume the device, lock the screen for an extended period, or physically remove/disable the display adapter.
3. Observe the WPF application crash rather than recovering and repainting after the GPU comes back.
## Impact
- Affects any WPF application running on ARM64 hardware (Snapdragon X Elite/Plus, other Windows-on-ARM devices).
- Crash is deterministic when a GPU suspension event occurs during an active Present call.
- Hit count indicates this affects a significant number of ARM64 users in production.
- Issue originally observed in Visual Studio integration scenario.
## Expected behavior
When `IDirect3DSwapChain9::Present` returns an error indicating the GPU was lost or removed, WPF should:
1. Recognize the error as a device-lost condition.
2. Release GPU resources via the existing `MarkUnusable()` path.
3. Skip the current frame and notify the UI thread (`RENDERING_STATUS_DEVICE_LOST`).
4. Automatically recreate GPU resources and repaint the window when the GPU resumes.
No crash, no zombie window.
## Actual behavior
`PresentWithD3D` only checks for `S_OK`, `S_PRESENT_MODE_CHANGED`, and `S_PRESENT_OCCLUDED`. Any other HRESULT falls through to `MIL_THR`, which treats `0x887A0005` as a fatal error, triggering an unrecoverable exception and terminating the process.
## Suspected root cause
On ARM64, the D3D9-over-DXGI compatibility layer surfaces `DXGI_ERROR_DEVICE_REMOVED` (`0x887A0005`) directly when the GPU is suspended or removed, instead of translating it to the legacy D3D9 equivalent `D3DERR_DEVICELOST`. WPF's `PresentWithD3D` does not recognize this DXGI code and passes it unchanged to `MIL_THR`, which classifies it as fatal. `HandlePresentFailure` — which contains the correct device-lost recovery logic — is never reached.
This is an ARM64-specific behavior difference in the D3D9-over-DXGI translation layer.
## Proposed fix direction
- Define `DXGI_ERROR_DEVICE_REMOVED` locally in `wgx_error.h` (as a stable `#define`) to avoid pulling in `dxgi.h`.
- In `PresentWithD3D` (`d3ddevice.cpp`): add an `else if (hr == DXGI_ERROR_DEVICE_REMOVED)` branch — placed **before** the `MIL_THR` call — that converts the error to `D3DERR_DEVICELOST`. This feeds the existing device-lost recovery path.
- In `HandlePresentFailure` (`d3ddevice.cpp`): add `DXGI_ERROR_DEVICE_REMOVED` to the device-lost condition block as defense-in-depth for other callers (e.g. `PresentWithGDI`).
Contributor guide
Assessment
This issue has not been assessed yet.