diff --git a/apps/vectorcraft/src/gpu.rs b/apps/vectorcraft/src/gpu.rs index 00f92405e..d7d917ed4 100644 --- a/apps/vectorcraft/src/gpu.rs +++ b/apps/vectorcraft/src/gpu.rs @@ -753,13 +753,22 @@ enum FirstFrame { Shown, /// Overdue. Hung, + /// Overdue, but the UI is still building the first frame: the app's own work (on a slow + /// machine, or with antivirus reading every font file), not the adapter, which has been given + /// nothing to show yet. + Busy, } -/// The window's start after `frames` frames run, `since` its first frame was due. -fn first_frame(frames: u64, since: Duration, minimized: bool) -> FirstFrame { +/// The window's start after `frames` frames run, `since` its first frame was due and +/// `since_built` the first frame's UI pass ended (`None` while it runs). A frame counts once its +/// UI pass ends, before it is painted, so the adapter's time starts there: with no frame run, the +/// UI thread is in the app's code, and starting again without the adapter wouldn't help (#5160). +fn first_frame(frames: u64, since: Duration, since_built: Option, minimized: bool) -> FirstFrame { if frames >= 2 || minimized { FirstFrame::Shown - } else if since >= FIRST_FRAME_TIMEOUT { + } else if frames == 0 { + if since >= FIRST_FRAME_TIMEOUT { FirstFrame::Busy } else { FirstFrame::Wait } + } else if since_built.unwrap_or(since) >= FIRST_FRAME_TIMEOUT { FirstFrame::Hung } else { FirstFrame::Wait @@ -767,18 +776,34 @@ fn first_frame(frames: u64, since: Duration, minimized: bool) -> FirstFrame { } /// Watch the window's first frame from another thread: once it reaches the screen, clear the start -/// from [`START_FILE`]; when it hasn't reached the screen [`FIRST_FRAME_TIMEOUT`] after the app was -/// created ([`Startup::ready`]), start the app again without the adapter, as [`finish`] does for -/// one that failed, and end this process, whose UI thread is stuck in the graphics driver. +/// from [`START_FILE`]; when it hasn't reached the screen [`FIRST_FRAME_TIMEOUT`] after its UI pass +/// ended, start the app again without the adapter, as [`finish`] does for one that failed, and end +/// this process, whose UI thread is stuck in the graphics driver. A first frame still being built +/// that long is waited for, with a note in the log. pub fn watch_first_frame(startup: Arc) { + let mut built: Option = None; + let mut noted = false; let watch = move || loop { std::thread::sleep(Duration::from_millis(250)); let Some(due) = startup.ready.get() else { continue }; let ui = startup.ui.get(); let frames = ui.map_or(0, egui::Context::cumulative_frame_nr); + if frames >= 1 && built.is_none() { + built = Some(Instant::now()); + } let minimized = ui.and_then(|c| c.input(|i| i.viewport().minimized)).unwrap_or(false); - match first_frame(frames, due.elapsed(), minimized) { + match first_frame(frames, due.elapsed(), built.map(|t| t.elapsed()), minimized) { FirstFrame::Wait => continue, + FirstFrame::Busy => { + if !noted { + log::warn!( + "the window's first frame is still being built after {}s: waiting for it (the app's work, not the graphics adapter)", + FIRST_FRAME_TIMEOUT.as_secs() + ); + noted = true; + } + continue; + } FirstFrame::Shown => { startup.presented(); return; diff --git a/apps/vectorcraft/src/gpu/tests.rs b/apps/vectorcraft/src/gpu/tests.rs index b1511a232..40d84934f 100644 --- a/apps/vectorcraft/src/gpu/tests.rs +++ b/apps/vectorcraft/src/gpu/tests.rs @@ -366,14 +366,19 @@ fn gpus_without_pci_ids_are_told_apart_by_name() { fn a_first_frame_that_never_reaches_the_screen_hangs_its_adapter() { let s = Duration::from_secs; // The second frame began: the first was presented. - assert_eq!(first_frame(2, s(60), false), FirstFrame::Shown); - // Still drawing (or presenting) the first frame: waited for, then given up on (#964). - for frames in [0, 1] { - assert_eq!(first_frame(frames, s(3), false), FirstFrame::Wait); - assert_eq!(first_frame(frames, FIRST_FRAME_TIMEOUT, false), FirstFrame::Hung); - } + assert_eq!(first_frame(2, s(60), Some(s(1)), false), FirstFrame::Shown); + // The first frame built and not yet presented: waited for, then given up on (#964), counting + // from when its UI pass ended. + assert_eq!(first_frame(1, s(3), Some(s(1)), false), FirstFrame::Wait); + assert_eq!(first_frame(1, FIRST_FRAME_TIMEOUT, Some(FIRST_FRAME_TIMEOUT), false), FirstFrame::Hung); + assert_eq!(first_frame(1, s(45), Some(s(5)), false), FirstFrame::Wait, "a first frame built slowly gets its full time to show"); + assert_eq!(first_frame(1, FIRST_FRAME_TIMEOUT, None, false), FirstFrame::Hung); + // The first frame still being built: the app's work, not the adapter's (#5160). + assert_eq!(first_frame(0, s(3), None, false), FirstFrame::Wait); + assert_eq!(first_frame(0, FIRST_FRAME_TIMEOUT, None, false), FirstFrame::Busy); + assert_eq!(first_frame(0, s(600), None, false), FirstFrame::Busy, "never a restart without an adapter"); // A window minimized at the start may not draw again until restored. - assert_eq!(first_frame(1, s(60), true), FirstFrame::Shown); + assert_eq!(first_frame(1, s(60), Some(s(60)), true), FirstFrame::Shown); // A hung adapter is left out once, like one that failed. let skip = vec!["Dx12:10de:28a0".to_string()]; assert_eq!(restart_without(true, 1, Some("Dx12:10de:28a0"), &skip), None); diff --git a/docs/gaps.md b/docs/gaps.md index 2f3c5ea61..d754fe774 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Gaps -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-04 · **Change:** minor (the menu bar no longer waits for the installed fonts' scan, so a large font library doesn't keep the window from opening; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-04 · **Change:** minor (a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -191,6 +191,7 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall - **Unconfirmed (2026-10-14):** Tool cursors are painted into the window on Wayland (#1652; not run on Linux, #1645 stays open until confirmed). On Windows the custom title bar's window stays truly borderless, so the pointer lines up with what is drawn on systems where the hidden caption offset it (#1791, #1758; awaiting the reporter's confirmation). - **Done:** RPM required dependencies use 64-bit library capabilities and declare glibc 2.35, libm and libgcc_s (#1275); generated metadata and isolated synthetic-provider checks cover x86_64/aarch64. Real-distribution install/start-up QA is still missing. +- **Done (2026-11-04):** a first frame the app is slow to build no longer costs the window its graphics adapter (#5160). The first-frame watchdog gave the frame 20 s from the app's start and then started again without the adapter, so a slow machine (antivirus reading every file, a large font library) ended with no adapter left and no window. A frame counts once its UI pass ends, before it is painted, so the 20 s now run from there; a frame still being built is waited for, with a note in the log (`a_first_frame_that_never_reaches_the_screen_hangs_its_adapter`; checked on Linux with the first frame held 35 s: the window opened with no restart). Estimate unchanged. - **Done (2026-11-04):** a large font library no longer keeps the window from opening (#5483). The menu bar's Type › Font, drawn in the first frame, waited for the scan of every installed font; when that took longer than the 20 s first-frame watchdog (thousands of fonts on a slow disk, or with each file checked by antivirus) the app took the graphics adapter for hung and started again without it until none was left. The menu now lists the fonts already loaded until the scan ends, then all of them (`the_menu_family_list_now_does_not_wait_for_the_scan`; checked on Linux with 3,923 extra font files, each open delayed 8 ms: the window opened in 1.5 s, where it never did before). Estimate unchanged. - **Done (2026-10-29):** the Linux Flatpak on GNOME no longer reconnects to the compositor and logs a warning every 250 ms when arboard has neither data-control nor X11 (267k warnings in 18 h filled the 16 MB log in 3 h): a clipboard that can't be opened is retried with a wait growing to a minute and logged once. Copy and Paste of text with other apps work there through the core Wayland protocol. - **Missing:** pictures (`image/png`) and SVG as `image/svg+xml` on the clipboard of the Flatpak on GNOME, where only text travels (smithay-clipboard 0.7 carries text only); the app not starting or staying in the background on some Windows 11 machines (#858, #964 fixed