A window that can't start says why instead of the app crashing (#786)

When the graphics can't start the window, the app shows why in the start-failure dialog instead of crashing: a panic before the first frames were shown is reported like any other start failure.
This commit is contained in:
smittie2000
2026-11-04 06:33:32 +02:00
committed by GitHub
parent 282a4bca5c
commit 08df7caa1a
+55 -17
View File
@@ -82,23 +82,40 @@ fn main() -> eframe::Result {
setup.instance_descriptor.backend_options.dx12.shader_compiler = dx12_compiler(eframe::wgpu::Dx12Compiler::from_env());
setup.native_adapter_selector = Some(Arc::new(pick_adapter));
}
let run = eframe::run_native(
"Craft Anything",
options,
Box::new(move |cc| {
log::info!("window open");
CraftApp::setup_context(&cc.egui_ctx);
let mut app = CraftApp::new(engine, hub);
// The window is built hidden and its first frame is drawn afterwards, where a graphics
// driver may still crash: the mark stays until frames have been shown (#74).
app.after_first_frames(move || start_guard::opened(&config));
app.load_prefs(cc.storage);
if let Some(p) = open {
app.open_path(p);
}
Ok(Box::new(app))
}),
);
// Set once the first frames have been shown. Before that, eframe 0.36's wgpu panics when it
// can't configure the window's surface (no usable GPU, or no desktop: winget-pkgs validation
// starts the app that way and rejected the panic, exit 101), instead of returning an error.
let shown = Arc::new(std::sync::atomic::AtomicBool::new(false));
let shown_by_app = Arc::clone(&shown);
let run = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
eframe::run_native(
"Craft Anything",
options,
Box::new(move |cc| {
log::info!("window open");
CraftApp::setup_context(&cc.egui_ctx);
let mut app = CraftApp::new(engine, hub);
// The window is built hidden and its first frame is drawn afterwards, where a graphics
// driver may still crash: the mark stays until frames have been shown (#74).
app.after_first_frames(move || {
start_guard::opened(&config);
shown_by_app.store(true, std::sync::atomic::Ordering::Relaxed);
});
app.load_prefs(cc.storage);
if let Some(p) = open {
app.open_path(p);
}
Ok(Box::new(app))
}),
)
}));
let run = match run {
Ok(run) => run,
// A window that never showed: report it like any other start failure, below.
Err(payload) if !shown.load(std::sync::atomic::Ordering::Relaxed) => Err(eframe::Error::AppCreation(panic_text(payload.as_ref()).into())),
// A panic after the window was up is a bug, not a start failure: it ends the app as before.
Err(payload) => std::panic::resume_unwind(payload),
};
match &run {
Ok(()) => log::info!("closed"),
Err(e) => {
@@ -135,6 +152,15 @@ fn wants_software(setting: Option<&str>) -> bool {
setting.is_some_and(|s| s.trim().eq_ignore_ascii_case("software"))
}
/// The message a panic carried (`panic!` with a literal or a formatted string), or a generic one.
fn panic_text(payload: &(dyn std::any::Any + Send)) -> String {
match (payload.downcast_ref::<&str>(), payload.downcast_ref::<String>()) {
(Some(text), _) => (*text).to_string(),
(None, Some(text)) => text.clone(),
(None, None) => "the graphics start failed".to_string(),
}
}
/// What to try when the window can't start. A lost graphics device on Windows gets the way to the
/// software renderer; on macOS that setting does nothing (only Windows picks its adapter), so it
/// gets the update advice and the report path. Anything else gets the usual driver advice.
@@ -206,6 +232,18 @@ mod tests {
use super::*;
use eframe::wgpu::{Backend as B, DeviceType as T};
/// A panic before the window showed becomes the error the start-failure dialog shows: wgpu's
/// own message when there is one (`panic!` with a literal or a formatted string).
#[test]
fn a_start_panic_keeps_its_message() {
let literal: Box<dyn std::any::Any + Send> = Box::new("wgpu error: Validation Error");
let formatted: Box<dyn std::any::Any + Send> = Box::new(String::from("In Surface::configure: Invalid surface"));
let other: Box<dyn std::any::Any + Send> = Box::new(7_u8);
assert_eq!(panic_text(literal.as_ref()), "wgpu error: Validation Error");
assert_eq!(panic_text(formatted.as_ref()), "In Surface::configure: Invalid surface");
assert_eq!(panic_text(other.as_ref()), "the graphics start failed");
}
/// #27: a VMware VM offers its virtual GPU over GL and WARP over DX12; GL drew a black window.
/// #33: a start that shows nothing is looked at through `desktop.log`; between same-class
/// devices DX12 comes before Vulkan, and GL stays the last resort.