diff --git a/crates/engine/src/sysinfo.rs b/crates/engine/src/sysinfo.rs index 0debec95..de39974d 100644 --- a/crates/engine/src/sysinfo.rs +++ b/crates/engine/src/sysinfo.rs @@ -190,6 +190,25 @@ pub fn cpu_cores() -> usize { std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1) } +/// The graphics lines of Settings ▸ GPU Information and Help ▸ System Report: whether the GPU +/// compositor runs (and on which adapter), else why compositing is on the CPU, and the adapter +/// the window itself got. Without this a card the compositor couldn't use looked undetected (#596). +pub fn gpu_summary(s: &crate::Session) -> String { + let mut text = match s.accel.as_ref().map(|a| a.name()) { + Some(name) => format!("Compositing: GPU, {name}.\n"), + None => { + let threads = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1); + let why = s.accel_note.as_deref().unwrap_or("no reason was recorded"); + format!("Compositing: CPU, {threads} threads (pure Rust, rayon).\nGPU compositing is off: {why}\n") + } + }; + match &s.window_adapter { + Some(adapter) => text.push_str(&format!("Window graphics: {adapter}\n")), + None => text.push_str("Window graphics: not known yet (the window has not drawn its first frame).\n"), + } + text +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/engine/src/tests_settings.rs b/crates/engine/src/tests_settings.rs index 1a445205..84bf2fc4 100644 --- a/crates/engine/src/tests_settings.rs +++ b/crates/engine/src/tests_settings.rs @@ -650,3 +650,44 @@ fn render_backend_says_why_it_renders_on_the_cpu() { assert_eq!(why(&mut s), json!("headless: renders on the CPU unless started with --gpu")); assert_eq!(s.execute("render.backend", json!({"backend": "cpu"})).unwrap()["why"], json!("the project's renderer is Mercury Software Only")); } + +/// Settings ▸ GPU Information names what the GPU compositor runs on, or why it is off, and the +/// adapter the window got, so a card the compositor couldn't use doesn't look undetected (#596). +#[test] +fn gpu_summary_names_the_adapter_or_says_why_there_is_none() { + use crate::sysinfo::gpu_summary; + use effectcraft_render::{Accelerator, FxStep, Renderer}; + + struct Named; + impl Accelerator for Named { + fn name(&self) -> String { + "NVIDIA GeForce RTX 4070 (Vulkan)".into() + } + fn comp_frame(&self, _: &Renderer, _: crate::project::ItemId, _: effectcraft_time::Tick) -> Option { + None + } + fn supports_effect(&self, _: &str) -> bool { + false + } + fn effects(&self, _: &[FxStep], _: &effectcraft_effects::Buf, _: Option) -> Option { + None + } + } + + let mut s = Session::default(); + let off = gpu_summary(&s); + assert!(off.contains("Compositing: CPU"), "{off}"); + assert!(off.contains("GPU compositing is off: no reason was recorded"), "{off}"); + assert!(off.contains("Window graphics: not known yet"), "{off}"); + + s.accel_note = Some("GPU compositor unavailable: no compute shaders".into()); + s.window_adapter = Some("NVIDIA GeForce RTX 4070 (Dx12, DiscreteGpu)".into()); + let declined = gpu_summary(&s); + assert!(declined.contains("GPU compositing is off: GPU compositor unavailable: no compute shaders"), "{declined}"); + assert!(declined.contains("Window graphics: NVIDIA GeForce RTX 4070 (Dx12, DiscreteGpu)"), "the card the window got is listed: {declined}"); + + s.accel = Some(std::sync::Arc::new(Named)); + let on = gpu_summary(&s); + assert!(on.starts_with("Compositing: GPU, NVIDIA GeForce RTX 4070 (Vulkan)."), "{on}"); + assert!(!on.contains("compositing is off"), "{on}"); +} diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 1386da49..1470b258 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -775,12 +775,12 @@ pub fn frontend(app: &mut EffectcraftApp, ctx: &egui::Context, id: &str, p: Valu Value::Null } "app.gpuInfo" => { - let threads = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1); crate::panels::dialogs::info( app, "GPU Information", &format!( - "Compositing: CPU, {threads} threads (pure Rust, rayon).\nDisplay: egui on wgpu.\nLayer cache: {} MB • Preview cache: {} MB.", + "{}Layer cache: {} MB • Preview cache: {} MB.", + effectcraft_engine::sysinfo::gpu_summary(&app.session), app.session.layer_cache.budget() >> 20, app.frames.budget() >> 20 ), diff --git a/crates/ui-egui/tests/ui_settings_info.rs b/crates/ui-egui/tests/ui_settings_info.rs index 64f088fe..fe9b3733 100644 --- a/crates/ui-egui/tests/ui_settings_info.rs +++ b/crates/ui-egui/tests/ui_settings_info.rs @@ -111,3 +111,22 @@ fn gpu_information_ok_returns_to_settings_and_cancel_restores_preferences() { fn gpu_information_escape_returns_to_settings_and_ok_saves_preferences() { check_info_transaction(true); } + +/// GPU Information names the adapter the window got and says why the compositor is off, so a +/// card that the compositor could not use does not look undetected (#596). +#[test] +fn gpu_information_names_the_adapter_and_why_the_gpu_is_off() { + let session = Session { + accel_note: Some("GPU compositor unavailable: no compute shaders".into()), + window_adapter: Some("NVIDIA GeForce RTX 4070 (Dx12, DiscreteGpu)".into()), + ..Default::default() + }; + let mut h = Harness::builder().with_size(vec2(1600.0, 1000.0)).build_eframe(|_| EffectcraftApp::new(session)); + h.run_steps(4); + let ctx = h.ctx.clone(); + menus::invoke(h.state_mut(), &ctx, "app.gpuInfo", json!({})).unwrap(); + h.run_steps(3); + assert_eq!(h.state().dialog, Some(Dialog::Info)); + assert!(h.query_by_label_contains("Window graphics: NVIDIA GeForce RTX 4070").is_some(), "the window's adapter is shown"); + assert!(h.query_by_label_contains("GPU compositing is off: GPU compositor unavailable").is_some(), "the reason is shown"); +}