From 9d801eec7acf10d80a2fb9543253e6d0877c9fa0 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Tue, 22 Sep 2026 00:11:23 +0000 Subject: [PATCH] gpui_linux: Force a full render on X11 Expose after GPU recovery (#64570) # Objective Zed on X11 could crash with `index out of bounds: the len is 0 but the index is 0` in `WgpuAtlas::get_texture_info` right after recovering from a lost GPU device (Sentry ZED-BVQ, Linear FR-488). ## Solution When the device is lost, `X11Window::draw` recreates the renderer, which clears the sprite atlas, and sets `force_render_after_recovery` so the next frame rebuilds the scene instead of replaying the cached one. Only the periodic refresh timer consumed that flag though. An `Expose` event handled in the same event loop turn (a compositor repainting after the same GPU reset, for example) requested a presentation-only frame with `force_render: false`, so GPUI presented the previously rendered scene, whose sprites still pointed at atlas textures that no longer exist. We now consume the flag in `X11WindowStatePtr::refresh`, so every frame request, Expose or timer, forces a full render after recovery. The field is private now so a caller can't bypass it again. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Fixed a crash on Linux (X11) that could occur when a window was redrawn immediately after recovering from a lost GPU device. - [GPUI] Fixed a crash on Linux (X11) when an `Expose` event presented a cached frame immediately after GPU device recovery. --- crates/gpui_linux/src/linux/x11/client.rs | 5 +---- crates/gpui_linux/src/linux/x11/window.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/gpui_linux/src/linux/x11/client.rs b/crates/gpui_linux/src/linux/x11/client.rs index 3ab9a6e6e4..23a9a91a97 100644 --- a/crates/gpui_linux/src/linux/x11/client.rs +++ b/crates/gpui_linux/src/linux/x11/client.rs @@ -2007,14 +2007,11 @@ impl X11ClientState { let mut state = client.0.borrow_mut(); let xcb_connection = state.xcb_connection.clone(); if let Some(window) = state.windows.get_mut(&x_window) { - let force_render = std::mem::take( - &mut window.window.state.borrow_mut().force_render_after_recovery, - ); let window = window.window.clone(); drop(state); window.refresh(RequestFrameOptions { require_presentation: false, - force_render, + force_render: false, }); } xcb_connection diff --git a/crates/gpui_linux/src/linux/x11/window.rs b/crates/gpui_linux/src/linux/x11/window.rs index 56b3d8a906..3d33ab0c11 100644 --- a/crates/gpui_linux/src/linux/x11/window.rs +++ b/crates/gpui_linux/src/linux/x11/window.rs @@ -283,7 +283,7 @@ pub struct X11WindowState { /// `VisibilityNotify`; this is the last value it reported. visibility: WindowVisibility, hovered: bool, - pub(crate) force_render_after_recovery: bool, + force_render_after_recovery: bool, fullscreen: bool, client_side_decorations_supported: bool, decorations: WindowDecorations, @@ -1181,9 +1181,13 @@ impl X11WindowStatePtr { } } - pub fn refresh(&self, request_frame_options: RequestFrameOptions) { + pub fn refresh(&self, mut request_frame_options: RequestFrameOptions) { let callback = self.callbacks.borrow_mut().request_frame.take(); if let Some(mut fun) = callback { + // Expose events can present a frame before the refresh timer runs, + // so every frame request must rebuild stale atlas references after recovery. + request_frame_options.force_render |= + std::mem::take(&mut self.state.borrow_mut().force_render_after_recovery); fun(request_frame_options); self.callbacks.borrow_mut().request_frame = Some(fun); }