From a207f13f73a0b323d875fdb977e9b08f5e9b3939 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Mon, 2 Nov 2026 15:47:11 +0100 Subject: [PATCH] Keep the graphics backend when the window closes before its first frame Fixes #2116 Co-Authored-By: Claude Opus 5.5 --- apps/designcraft/src/gpu.rs | 7 ++++++- apps/designcraft/src/gpu/backend.rs | 22 ++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/apps/designcraft/src/gpu.rs b/apps/designcraft/src/gpu.rs index 907117b0c..93586e585 100644 --- a/apps/designcraft/src/gpu.rs +++ b/apps/designcraft/src/gpu.rs @@ -678,7 +678,12 @@ fn restart_without<'a>(graphics_failed: bool, frames: u64, picked: Option<&'a st /// ended it. pub fn finish(outcome: Result, startup: &Startup) -> Result<(), String> { let (why, graphics_failed) = match outcome { - Ok(Ok(())) => return Ok(()), + // The window closed normally, perhaps before its first frame or before the app was + // created (so `on_exit` never ran): no driver fault took this start down (#2116). + Ok(Ok(())) => { + startup.presented(); + return Ok(()); + } Ok(Err(e @ eframe::Error::Wgpu(_))) => (e.to_string(), true), Ok(Err(e)) => (e.to_string(), false), Err(panic) => (panic, GRAPHICS_PANIC.load(Ordering::Relaxed)), diff --git a/apps/designcraft/src/gpu/backend.rs b/apps/designcraft/src/gpu/backend.rs index 6aa250dd4..06c477064 100644 --- a/apps/designcraft/src/gpu/backend.rs +++ b/apps/designcraft/src/gpu/backend.rs @@ -14,8 +14,9 @@ //! fallback off. //! //! The fallback: before the window is created, `gpu.json` in the settings directory records the -//! backend being tried; once a frame has been presented, or the graphics failed with an error the -//! app caught (see `gpu::finish`), that entry is cleared. An entry still there at the next start +//! backend being tried; once a frame has been presented, the window closed normally (even before +//! its first frame), or the graphics failed with an error the app caught (see `gpu::finish`), that +//! entry is cleared. An entry still there at the next start //! means the previous start never got that far — the driver crashed, or the app was killed //! meanwhile — so that backend joins `failed`, the next candidate is tried and the status bar says //! so. Backends a restart left out (`--gpu-skip=`, see `gpu`) aren't chosen for that start but @@ -538,6 +539,23 @@ mod tests { let _ = std::fs::remove_dir_all(path.parent().expect("dir")); } + /// A window closed before its first frame (#2116): the app was created slowly or not at all, + /// so neither a frame nor `on_exit` cleared the record, but eframe ended cleanly. No driver + /// fault took that start down, so the next start keeps the backend. + #[test] + fn a_start_that_ends_cleanly_before_its_first_frame_does_not_fail_its_backend() { + let path = temp_path("clean-end"); + let fallback = Fallback::begin_unforced(Some(path.clone()), V, &[]).expect("candidates"); + let first = fallback.backend; + assert_eq!(read(&path).trying, Some(first)); + let startup = super::super::Startup::new(Some(fallback), Vec::new()); + assert_eq!(super::super::finish(Ok(Ok(())), &startup), Ok(())); + assert_eq!(read(&path).trying, None, "a clean end leaves no backend being tried"); + let c = choose(candidates(), &read(&path), V, &[]).expect("candidates"); + assert_eq!((c.backend, c.record.failed, c.switched_from), (first, Vec::new(), None)); + let _ = std::fs::remove_dir_all(path.parent().expect("dir")); + } + #[test] fn without_a_path_nothing_is_written_and_a_bad_path_is_not_fatal() { let mut s = Fallback::begin_unforced(None, V, &[]).expect("candidates");