Agent chat window: an off-screen chat window no longer stalls the main window (#9118) (#9119)

* claim: #9118

* Agent chat window: an off-screen chat window no longer stalls the main window (#9118)

The chat popped out into its own window is an immediate viewport: eframe draws
and presents it inside the main window's frame. Both surfaces waited for vsync,
and a Wayland compositor sends no frame callbacks to a window it isn't showing,
so the main window stalled behind the chat window's present.

A surface other than the main window's is now configured with Mailbox, else
Immediate, when the configured mode waits for vsync and the surface offers one
(viewport_present_mode in the vendored egui-wgpu). The main window keeps vsync
and still paces both.
This commit is contained in:
SixSolid
2026-10-30 13:24:30 +00:00
committed by GitHub
parent 1754318df7
commit 9af2ae9a0f
4 changed files with 73 additions and 3 deletions
+25 -2
View File
@@ -1,8 +1,10 @@
//! The window surface is clamped to the device's largest texture (#869): a window moved between
//! monitors of different scale asked for 11512 x 6808 against a maximum of 8192 and the
//! surface configure was a validation panic. The clamp lives in the vendored egui-wgpu.
//! surface configure was a validation panic. The clamp lives in the vendored egui-wgpu, and so
//! does the present mode of a window other than the main one (the agent chat popped out).
use egui_wgpu::clamp_surface_extent;
use egui_wgpu::wgpu::PresentMode;
use egui_wgpu::{clamp_surface_extent, viewport_present_mode};
#[test]
fn a_window_that_fits_is_unchanged() {
@@ -39,3 +41,24 @@ fn extreme_sizes_never_give_zero_or_exceed_the_limit() {
assert!(s > 0.0 && s <= 1.0);
}
}
#[test]
fn a_popped_out_window_presents_without_waiting_for_the_display() {
// Mesa's Vulkan on Wayland: the chat's window took the main window down to a crawl while
// both waited for vsync in one frame.
let wayland = [PresentMode::Fifo, PresentMode::Mailbox, PresentMode::Immediate];
assert_eq!(viewport_present_mode(false, PresentMode::AutoVsync, &wayland), PresentMode::Mailbox);
assert_eq!(viewport_present_mode(false, PresentMode::Fifo, &[PresentMode::Fifo, PresentMode::Immediate]), PresentMode::Immediate);
// The main window keeps its vsync: it paces both.
assert_eq!(viewport_present_mode(true, PresentMode::AutoVsync, &wayland), PresentMode::AutoVsync);
}
#[test]
fn a_surface_that_only_waits_and_a_chosen_mode_without_vsync_are_kept() {
assert_eq!(viewport_present_mode(false, PresentMode::AutoVsync, &[PresentMode::Fifo]), PresentMode::AutoVsync);
assert_eq!(viewport_present_mode(false, PresentMode::Fifo, &[]), PresentMode::Fifo);
for configured in [PresentMode::AutoNoVsync, PresentMode::Immediate, PresentMode::Mailbox] {
assert_eq!(viewport_present_mode(false, configured, &[PresentMode::Fifo, PresentMode::Mailbox]), configured);
assert_eq!(viewport_present_mode(true, configured, &[PresentMode::Fifo]), configured);
}
}
+5
View File
@@ -313,6 +313,11 @@ SolidCraft changes:
and the frame is skipped with a repaint asked for when it is due. The streak and the deadline
survive a recreate; a configure the device takes ends the streak, a recreate it asked for
included. A recreate for a lost surface goes ahead as before.
- `src/lib.rs`, `src/winit.rs`: a window other than the main one (an immediate viewport, drawn
and presented inside the main window's frame) is configured with Mailbox, else Immediate,
when the configured mode waits for vsync and the surface offers one (`viewport_present_mode`).
Waiting on both surfaces in one frame halved the frame rate, and on Wayland a window the
compositor wasn't showing stalled the main window behind its present.
- `src/lib.rs`, `src/winit.rs`: the device's lost callback marks `RenderState::device_lost`
(`DeviceLost`), and the painter draws nothing on a lost device. Before a frame's uploads it
submits nothing (`DeviceLost::probe`), so a device the driver lost is found by that
+28
View File
@@ -1058,6 +1058,34 @@ pub enum Attempt<E> {
Refused(RetryPlan, E),
}
/// SolidCraft: the present mode a viewport's surface is configured with. The main window keeps
/// `configured`. Another window (the agent chat popped out into its own window) is an immediate
/// viewport: eframe draws and presents it inside the main window's frame, one surface after the
/// other. With vsync on both, every frame waited for the display twice, and on a compositor that
/// sends no frame callbacks to a window it isn't showing (Wayland: on another workspace, scrolled
/// away, covered) the main window stalled behind the other one's present. So another window
/// presents without waiting where its surface can (Mailbox, which doesn't tear, before
/// Immediate); the main window's vsync still paces both. A `configured` mode without vsync is
/// kept everywhere.
pub fn viewport_present_mode(
root: bool,
configured: wgpu::PresentMode,
supported: &[wgpu::PresentMode],
) -> wgpu::PresentMode {
use wgpu::PresentMode;
let waits = matches!(
configured,
PresentMode::AutoVsync | PresentMode::Fifo | PresentMode::FifoRelaxed
);
if root || !waits {
return configured;
}
[PresentMode::Mailbox, PresentMode::Immediate]
.into_iter()
.find(|mode| supported.contains(mode))
.unwrap_or(configured)
}
/// SolidCraft: the surface size for a window of `width` x `height` pixels on a device whose
/// textures are at most `max_side` on a side, and the scale it is drawn at. A window that fits is
/// unchanged (scale 1); a larger one shrinks uniformly, keeping its aspect ratio, so the image
+15 -1
View File
@@ -111,6 +111,7 @@ impl Painter {
/// device, or a surface the adapter doesn't support, neither of which configured it (#7007).
/// Called only through [`Self::try_configure`], which says what follows.
fn configure_surface(
viewport_id: ViewportId,
surface_state: &SurfaceState,
render_state: &RenderState,
config: &SurfaceConfig,
@@ -141,6 +142,17 @@ impl Painter {
else {
return Err(crate::ConfigureError::Unsupported);
};
// SolidCraft: a window other than the main one presents without waiting for the display
// where its surface can (`crate::viewport_present_mode`).
let supported = surface_state
.surface
.get_capabilities(&render_state.adapter)
.present_modes;
let present_mode = crate::viewport_present_mode(
viewport_id == ViewportId::ROOT,
present_mode,
&supported,
);
let mut surf_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: render_state.target_format,
@@ -177,7 +189,9 @@ impl Painter {
) -> bool {
// A copy, as the configure borrows the whole surface state.
let mut retry = state.retry;
let attempt = retry.attempt(now, || Self::configure_surface(state, render_state, config));
let attempt = retry.attempt(now, || {
Self::configure_surface(viewport_id, state, render_state, config)
});
state.retry = retry;
match attempt {
crate::Attempt::Took(refusals) => {