diff --git a/crates/engine/src/commands/app_more.rs b/crates/engine/src/commands/app_more.rs index 34780274..b6101c5a 100644 --- a/crates/engine/src/commands/app_more.rs +++ b/crates/engine/src/commands/app_more.rs @@ -328,7 +328,10 @@ pub fn system_report(s: &Session) -> Value { let budgets = s.prefs.cache_budgets(mem.or(s.sys_memory)); let mut issues = vec![]; if gpu.is_none() { - issues.push("No GPU compositor: rendering uses the CPU (all features still work).".to_string()); + issues.push(match &s.accel_note { + Some(why) => format!("No GPU compositor ({why}): rendering uses the CPU (all features still work)."), + None => "No GPU compositor: rendering uses the CPU (all features still work).".to_string(), + }); } if mem.is_some_and(|m| m.total < 8 << 30) { issues.push("Less than 8 GB of memory: use lower preview resolutions for long or large comps.".to_string()); @@ -344,6 +347,8 @@ pub fn system_report(s: &Session) -> Value { "cores": crate::sysinfo::cpu_cores(), "memory": mem.map(|m| json!({"total": gb(m.total), "available": gb(m.available)})), "gpu": gpu, + "gpuNote": if gpu.is_none() { s.accel_note.clone() } else { None }, + "windowGraphics": s.window_adapter, "caches": {"layer": gb(budgets.layer as u64), "footage": gb(budgets.media as u64), "preview": gb(budgets.preview as u64)}, "import": IMPORT_FORMATS, "export": export, @@ -369,7 +374,14 @@ pub fn report_text(r: &Value) -> String { Some(m) => t.push_str(&format!("Memory: {} ({} available)\n", m["total"].as_str().unwrap_or("?"), m["available"].as_str().unwrap_or("?"))), None => t.push_str("Memory: unknown\n"), } - t.push_str(&format!("GPU: {}\n", r.get("gpu").and_then(Value::as_str).unwrap_or("none (CPU compositing)"))); + match (r.get("gpu").and_then(Value::as_str), r.get("gpuNote").and_then(Value::as_str)) { + (Some(gpu), _) => t.push_str(&format!("GPU: {gpu}\n")), + (None, Some(why)) => t.push_str(&format!("GPU: none (CPU compositing): {why}\n")), + (None, None) => t.push_str("GPU: none (CPU compositing)\n"), + } + if let Some(w) = r.get("windowGraphics").and_then(Value::as_str) { + t.push_str(&format!("Window graphics: {w}\n")); + } let c = &r["caches"]; t.push_str(&format!( "Cache budgets: layer {}, footage {}, preview {}\n", diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index a9ee6b89..ac805f4c 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -368,6 +368,9 @@ pub struct Session { /// Why there is no [`Session::accel`] (`render.backend`'s `why`): the host's reason, e.g. a /// headless start without `--gpu` or the compositor's setup error. pub accel_note: Option, + /// The graphics adapter and backend the window draws with (`NVIDIA GeForce RTX 4070 + /// (Vulkan)`), as the host reports it: the System Compatibility Report shows it. + pub window_adapter: Option, /// Expression syntax checker (set by the host that links the expression engine). pub expr_check: Option std::result::Result<(), String>>, pub importer: Option>, @@ -511,6 +514,7 @@ impl Default for Session { expr: None, accel: None, accel_note: None, + window_adapter: None, expr_check: None, importer: None, exporter: None, diff --git a/crates/engine/src/tests_m145.rs b/crates/engine/src/tests_m145.rs index b55442eb..2431c17c 100644 --- a/crates/engine/src/tests_m145.rs +++ b/crates/engine/src/tests_m145.rs @@ -455,6 +455,15 @@ fn compatibility_report_and_logging() { } let text = crate::commands::report_text_for_tests(&r); assert!(text.contains("Operating system") && text.contains("Import:")); + // #596: without a GPU compositor the report says why, and names the window's adapter. + s.accel_note = Some("GPU compositor unavailable: no storage textures".into()); + s.window_adapter = Some("NVIDIA GeForce RTX 4070 (Gl, DiscreteGpu)".into()); + let r = s.execute("help.systemReport", json!({"quiet": true})).unwrap(); + assert_eq!(r["gpuNote"], "GPU compositor unavailable: no storage textures"); + let text = crate::commands::report_text_for_tests(&r); + assert!(text.contains("GPU: none (CPU compositing): GPU compositor unavailable: no storage textures"), "{text}"); + assert!(text.contains("Window graphics: NVIDIA GeForce RTX 4070 (Gl, DiscreteGpu)"), "{text}"); + assert!(r["issues"][0].as_str().is_some_and(|i| i.contains("no storage textures")), "{r}"); s.drain_events(); s.execute("help.systemReport", json!({})).unwrap(); assert!(s.drain_events().iter().any(|e| matches!(e, crate::Event::Frontend { command, .. } if command == "app.showReport"))); diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index dd0efc0c..f9175b66 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -612,6 +612,15 @@ impl EffectcraftApp { return; } let Some(rs) = frame.wgpu_render_state() else { return }; + // Help ▸ System Compatibility Report names the adapter and backend the window got. + let info = rs.adapter.get_info(); + self.session.window_adapter = Some(format!( + "{} ({:?}, {:?}{})", + info.name, + info.backend, + info.device_type, + if info.driver.is_empty() { String::new() } else { format!(", {} {}", info.driver, info.driver_info) } + )); // Backend validation can panic on shader compilation even when device creation // succeeded. Keep the document and the egui renderer, and use CPU compositing. let gpu = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| effectcraft_gpu::Gpu::new(&rs.adapter, rs.device.clone(), rs.queue.clone())));