Help: the System Compatibility Report says why there is no GPU and names the window's adapter (#596)

With no GPU compositor the report only said "GPU: none", though the host had recorded the
reason (`Session::accel_note`: the compositor's setup error, a retired GPU preview, a
headless start) and the window's wgpu adapter was known. Users with a capable GPU (#596, an
RTX 4070) couldn't tell why it wasn't used.

The report now carries `gpuNote` (the reason, when there is no compositor) and
`windowGraphics` (the adapter, backend, device type and driver the window draws with, which
the desktop app records when it sets up the GPU), and the text shows
"GPU: none (CPU compositing): <reason>" and "Window graphics: …"; the issue line names the
reason too.

Test: the compatibility report test sets a reason and an adapter and checks the JSON and text.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
This commit is contained in:
Claude committed 2026-10-10 19:54:17 +00:00
1 parent 3effa9a2ea
commit a220b63bfb
4 files changed
+36 -2

No files matched your search

+14 -2
View File
@@ -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",
+4
View File
@@ -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<String>,
/// 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<String>,
/// Expression syntax checker (set by the host that links the expression engine).
pub expr_check: Option<fn(&str) -> std::result::Result<(), String>>,
pub importer: Option<Arc<dyn Importer>>,
@@ -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,
+9
View File
@@ -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")));
+9
View File
@@ -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())));