From 188cc06d2b9a6af539d1c7bbd0ada25e3d0c6828 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 22:13:19 +0000 Subject: [PATCH] App: the window's GPU device asks for what the adapter has (#198) eframe's default device request asks for wgpu's default limits (8 storage buffers per stage and more). An older GPU such as Intel HD Graphics 5500 can't meet them, so the device request failed before EffectCraft ran, and the release build on Windows quit without a word. The device now asks for the adapter's own limits (textures capped at 16384 px like the headless renderer). The compositor already checks what it needs and composites on the CPU when the device can't run it. If the window still can't open, a message dialog says why instead of the app vanishing. Test: device_limits_fit_the_adapter. Not checked on that GPU. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018gnTnH3Jmk5zr4tMq1Mc9f --- apps/effectcraft/src/main.rs | 61 ++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/apps/effectcraft/src/main.rs b/apps/effectcraft/src/main.rs index aa55a6de..353d6f22 100644 --- a/apps/effectcraft/src/main.rs +++ b/apps/effectcraft/src/main.rs @@ -89,9 +89,10 @@ fn main() -> eframe::Result { // Wayland matches the window to `ai.storyteller.effectcraft.desktop` by this ID. .with_app_id(appimage::APP_ID), event_loop_builder: agent_event_loop(control_port.is_some()), + wgpu_options: wgpu_options(), ..Default::default() }; - eframe::run_native( + let result = eframe::run_native( "EffectCraft", options, Box::new(move |cc| { @@ -188,7 +189,50 @@ fn main() -> eframe::Result { app, })) }), - ) + ); + if let Err(e) = &result { + startup_failed(e); + } + result +} + +/// The window's wgpu device asks for the adapter's own limits (textures up to 16384 px) +/// instead of eframe's fixed set: an older GPU that can't meet those (Intel HD Graphics 5500, +/// #198) still opens the app, and the compositor then checks what it needs and falls back to +/// the CPU when the device can't run it. +fn wgpu_options() -> eframe::WgpuConfiguration { + use eframe::egui_wgpu::WgpuSetup; + let mut config = eframe::WgpuConfiguration::default(); + if let WgpuSetup::CreateNew(setup) = &mut config.wgpu_setup { + setup.device_descriptor = std::sync::Arc::new(|adapter| eframe::wgpu::DeviceDescriptor { + label: Some("EffectCraft device"), + required_limits: device_limits(adapter.limits()), + ..Default::default() + }); + } + config +} + +/// The limits to request from an adapter that offers `adapter`: all of them, with textures +/// capped at 16384 px like the headless renderer's device. +fn device_limits(adapter: eframe::wgpu::Limits) -> eframe::wgpu::Limits { + eframe::wgpu::Limits { max_texture_dimension_2d: adapter.max_texture_dimension_2d.min(16384), ..adapter } +} + +/// The window couldn't open (no usable graphics adapter or device): say so instead of quitting +/// silently, since release builds on Windows have no console. +fn startup_failed(e: &eframe::Error) { + let message = format!( + "EffectCraft couldn't start its window: {e}\n\nUpdating the graphics driver often helps. If it keeps happening, please report it at https://github.com/storytold/effectcraft/issues with this message." + ); + log::error!("{message}"); + eprintln!("effectcraft: {message}"); + let _ = rfd::MessageDialog::new() + .set_level(rfd::MessageLevel::Error) + .set_title("EffectCraft") + .set_description(message) + .set_buttons(rfd::MessageButtons::Ok) + .show(); } /// The desktop app: the egui app plus, on macOS, the system menu bar. @@ -254,6 +298,19 @@ mod tests { parse_args(args.iter().map(|a| a.to_string()), None) } + /// The window's device never asks for more than the adapter offers (#198). + #[test] + fn device_limits_fit_the_adapter() { + use eframe::wgpu::Limits; + for adapter in [Limits::downlevel_defaults(), Limits::downlevel_webgl2_defaults(), Limits::default()] { + let l = super::device_limits(adapter.clone()); + assert!(l.check_limits(&adapter), "{l:?}"); + assert_eq!(l.max_storage_buffers_per_shader_stage, adapter.max_storage_buffers_per_shader_stage); + } + let big = Limits { max_texture_dimension_2d: 32768, ..Limits::default() }; + assert_eq!(super::device_limits(big).max_texture_dimension_2d, 16384); + } + /// Launching without a project opens an empty project, not the demo (#204). #[test] fn no_arguments_start_an_empty_project() {