gpui_linux: Fix Wayland flickering under CPU load by skipping redundant surface commit (#54214)
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 is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #32792 Closes #38266 Closes #54133 Release Notes: - Fixed graphical corruption that could occur when using Wayland ---- **What**: Fixes flickering on Wayland (Sway/wlroots) under CPU load (e.g. rust-analyzer running). The bug only reproduces in release builds - debug builds are too slow to hit the race window. Environment where this was reproduced: Intel GPU (both Xe KMD and i915), Mesa 26.0.4, Sway 1.11. **Why**: When wgpu presents a frame on Wayland+Vulkan, it calls `vkQueuePresentKHR,` which - as required by the Vulkan spec - synchronously issues `wl_surface.attach`, `wl_surface.damage`, and `wl_surface.commit` to the compositor before returning. The commit also picks up any pending frame callbacks. Zed's `completed_frame()` independently calls `state.surface.commit()`. This is a redundant second commit on the same surface. Under load, the Wayland socket dispatch can be delayed enough that both commits are in flight in close succession, and the ordering becomes timing-dependent. When Zed's commit reaches the compositor before Mesa's attach+commit sequence has been fully flushed, the compositor sees a commit with no buffer attached, fires `wl_callback::Done` immediately, and Zed starts the next frame too early - Mesa's real buffer arrives late, causing the visible flicker. Under no load, Mesa's synchronous commit consistently reaches the compositor first, so the bug doesn't appear. **Fix**: Track whether `renderer.draw()` actually called `frame.present()`. When it did, Mesa owns the `wl_surface.commit()` for that frame - skip Zed's commit in `completed_frame()`. Only commit ourselves when wgpu didn't present (surface not configured, lost, occluded, etc.) - in those cases Mesa won't commit, and we need to keep the frame callback alive. --------- Co-authored-by: Benjamin Laib <b5l@users.noreply.github.com> Co-authored-by: John Tur <john-tur@outlook.com>
This commit is contained in:
1 parent
5fb7238037
commit
2ae08181de
2 files changed
+21
-12
No files matched your search
@@ -117,6 +117,7 @@ pub struct WaylandWindowState {
|
||||
active: bool,
|
||||
hovered: bool,
|
||||
pub(crate) force_render_after_recovery: bool,
|
||||
renderer_presented: bool,
|
||||
in_progress_configure: Option<InProgressConfigure>,
|
||||
resize_throttle: bool,
|
||||
in_progress_window_controls: Option<WindowControls>,
|
||||
@@ -392,6 +393,7 @@ impl WaylandWindowState {
|
||||
active: false,
|
||||
hovered: false,
|
||||
force_render_after_recovery: false,
|
||||
renderer_presented: false,
|
||||
in_progress_window_controls: None,
|
||||
window_controls: WindowControls::default(),
|
||||
client_inset: None,
|
||||
@@ -1398,7 +1400,7 @@ impl PlatformWindow for WaylandWindow {
|
||||
return;
|
||||
}
|
||||
|
||||
state.renderer.draw(scene);
|
||||
state.renderer_presented = state.renderer.draw(scene);
|
||||
|
||||
if state.renderer.needs_redraw() {
|
||||
state.force_render_after_recovery = true;
|
||||
@@ -1406,8 +1408,15 @@ impl PlatformWindow for WaylandWindow {
|
||||
}
|
||||
|
||||
fn completed_frame(&self) {
|
||||
let state = self.borrow();
|
||||
state.surface.commit();
|
||||
let mut state = self.borrow_mut();
|
||||
|
||||
// Work around a bug in old versions of wlroots where committing without a buffer attached
|
||||
// can cause invalid synchronization that leads to graphical corruption.
|
||||
if !state.renderer_presented {
|
||||
state.surface.commit();
|
||||
}
|
||||
|
||||
state.renderer_presented = false;
|
||||
}
|
||||
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
|
||||
|
||||
@@ -1079,13 +1079,13 @@ impl WgpuRenderer {
|
||||
self.max_texture_size
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, scene: &Scene) {
|
||||
pub fn draw(&mut self, scene: &Scene) -> bool {
|
||||
// Bail out early if the surface has been unconfigured (e.g. during
|
||||
// Android background/rotation transitions). Attempting to acquire
|
||||
// a texture from an unconfigured surface can block indefinitely on
|
||||
// some drivers (Adreno).
|
||||
if !self.surface_configured {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let last_error = self.last_error.lock().unwrap().take();
|
||||
@@ -1106,7 +1106,7 @@ impl WgpuRenderer {
|
||||
self.atlas.clear();
|
||||
self.needs_redraw = true;
|
||||
self.failed_frame_count = 0;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
self.failed_frame_count = 0;
|
||||
@@ -1124,7 +1124,7 @@ impl WgpuRenderer {
|
||||
resources
|
||||
.surface
|
||||
.configure(&resources.device, &surface_config);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
|
||||
let surface_config = self.surface_config.clone();
|
||||
@@ -1132,15 +1132,15 @@ impl WgpuRenderer {
|
||||
resources
|
||||
.surface
|
||||
.configure(&resources.device, &surface_config);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
wgpu::CurrentSurfaceTexture::Validation => {
|
||||
*self.last_error.lock().unwrap() =
|
||||
Some("Surface texture validation error".to_string());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1321,7 +1321,7 @@ impl WgpuRenderer {
|
||||
self.instance_buffer_capacity
|
||||
);
|
||||
frame.present();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
self.grow_instance_buffer();
|
||||
continue;
|
||||
@@ -1331,7 +1331,7 @@ impl WgpuRenderer {
|
||||
.queue
|
||||
.submit(std::iter::once(encoder.finish()));
|
||||
frame.present();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user