ravenrs_platform_native/
diagnostics.rs1use std::sync::Arc;
5
6use ravenrs_core::{DiagnosticReport, SurfaceReport, SystemReport};
7use ravenrs_render::{GpuContext, gpu_report, preferred_surface_format, surface_report, wgpu};
8use winit::application::ApplicationHandler;
9use winit::event::WindowEvent;
10use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
11use winit::window::{Window, WindowId};
12
13const OVERRIDES: [&str; 9] = [
15 "WGPU_BACKEND",
16 "WGPU_ADAPTER_NAME",
17 "WGPU_POWER_PREF",
18 "WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER",
19 "VK_ICD_FILENAMES",
20 "VK_DRIVER_FILES",
21 "LIBGL_ALWAYS_SOFTWARE",
22 "MESA_LOADER_DRIVER_OVERRIDE",
23 "GALLIUM_DRIVER",
24];
25
26const PATH_OVERRIDES: [&str; 2] = ["VK_ICD_FILENAMES", "VK_DRIVER_FILES"];
28
29pub(crate) fn new_instance(display: Option<&OwnedDisplayHandle>, permissive: bool) -> wgpu::Instance {
32 let mut descriptor = match display {
33 Some(display) => {
34 wgpu::InstanceDescriptor::new_with_display_handle_from_env(Box::new(display.clone()))
35 }
36 None => wgpu::InstanceDescriptor::new_without_display_handle_from_env(),
37 };
38 if permissive {
39 descriptor.flags |= wgpu::InstanceFlags::ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER;
40 }
41 wgpu::Instance::new(descriptor)
42}
43
44pub(crate) fn gather(
46 display: Option<&OwnedDisplayHandle>,
47 adapter: &wgpu::Adapter,
48 surface: Option<SurfaceReport>,
49) -> DiagnosticReport {
50 let (instance, permissive) = (new_instance(display, false), new_instance(display, true));
51 let gpu = pollster::block_on(gpu_report(adapter, &instance, &permissive, surface));
52 DiagnosticReport { gpu: Ok(gpu), system: system_report() }
53}
54
55fn system_report() -> SystemReport {
56 SystemReport {
57 version: env!("CARGO_PKG_VERSION").into(),
58 os: os_name(),
59 session: std::env::var("XDG_SESSION_TYPE").ok(),
60 desktop: std::env::var("XDG_CURRENT_DESKTOP").ok(),
61 vulkan_drivers: vulkan_drivers(),
62 overrides: OVERRIDES
63 .iter()
64 .filter_map(|name| Some(override_entry(name, &std::env::var(name).ok()?)))
65 .collect(),
66 optimized: !cfg!(debug_assertions),
67 }
68}
69
70fn override_entry(name: &str, value: &str) -> String {
73 if !PATH_OVERRIDES.contains(&name) {
74 return format!("{name}={value}");
75 }
76 let files: Vec<_> = std::env::split_paths(value)
77 .filter_map(|path| Some(path.file_name()?.to_string_lossy().into_owned()))
78 .collect();
79 format!("{name}={}", files.join(","))
80}
81
82fn os_name() -> String {
84 let release = std::fs::read_to_string("/etc/os-release").unwrap_or_default();
85 pretty_name(&release).unwrap_or_else(|| std::env::consts::OS.to_owned())
86}
87
88fn pretty_name(os_release: &str) -> Option<String> {
89 let line = os_release.lines().find_map(|line| line.strip_prefix("PRETTY_NAME="))?;
90 Some(line.trim_matches('"').to_owned())
91}
92
93fn vulkan_drivers() -> Vec<String> {
95 let mut drivers: Vec<String> = ["/usr/share/vulkan/icd.d", "/etc/vulkan/icd.d"]
96 .iter()
97 .filter_map(|dir| std::fs::read_dir(dir).ok())
98 .flatten()
99 .filter_map(|entry| Some(entry.ok()?.file_name().to_string_lossy().into_owned()))
100 .collect();
101 drivers.sort();
102 drivers
103}
104
105fn probe(display: Option<&OwnedDisplayHandle>, window: Option<Arc<Window>>) -> DiagnosticReport {
107 let instance = new_instance(display, false);
108 let surface = window.and_then(|window| instance.create_surface(window).ok());
109 let failed = |e: String| DiagnosticReport { gpu: Err(e), system: system_report() };
110 let gpu = match pollster::block_on(GpuContext::new(&instance, surface.as_ref())) {
111 Ok(gpu) => gpu,
112 Err(e) => return failed(e.to_string()),
113 };
114 let described = surface.as_ref().and_then(|surface| {
115 let capabilities = surface.get_capabilities(&gpu.adapter);
116 let format = preferred_surface_format(&capabilities.formats)?;
117 Some(surface_report(&capabilities, format, wgpu::PresentMode::AutoVsync))
118 });
119 gather(display, &gpu.adapter, described)
120}
121
122struct Probe {
123 display: OwnedDisplayHandle,
124 report: Option<DiagnosticReport>,
125}
126
127impl ApplicationHandler for Probe {
128 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
129 if self.report.is_some() {
130 return;
131 }
132 let attributes = Window::default_attributes().with_visible(false);
134 let window = event_loop.create_window(attributes).ok().map(Arc::new);
135 self.report = Some(probe(Some(&self.display), window));
136 event_loop.exit();
137 }
138
139 fn window_event(&mut self, _event_loop: &ActiveEventLoop, _id: WindowId, _event: WindowEvent) {}
140}
141
142pub fn diagnose() -> DiagnosticReport {
149 let Ok(event_loop) = EventLoop::new() else {
150 return probe(None, None);
152 };
153 let mut probing = Probe { display: event_loop.owned_display_handle(), report: None };
154 match event_loop.run_app(&mut probing) {
155 Ok(()) => probing.report.unwrap_or_else(|| probe(None, None)),
156 Err(e) => {
157 tracing::warn!(error = %e, "could not run the event loop");
158 probe(None, None)
159 }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[cfg(unix)]
168 #[test]
169 fn overrides_with_paths_keep_only_the_file_names() {
170 assert_eq!(
171 override_entry("VK_DRIVER_FILES", "/home/ana/icd/radeon.json:/opt/lvp.json"),
172 "VK_DRIVER_FILES=radeon.json,lvp.json"
173 );
174 assert_eq!(override_entry("WGPU_BACKEND", "gl"), "WGPU_BACKEND=gl");
175 }
176
177 #[test]
178 fn the_distribution_name_comes_from_os_release() {
179 let release =
180 "NAME=\"Fedora Linux\"\nPRETTY_NAME=\"Fedora Linux 44 (Workstation Edition)\"\nID=fedora\n";
181 assert_eq!(pretty_name(release).as_deref(), Some("Fedora Linux 44 (Workstation Edition)"));
182 assert_eq!(pretty_name("ID=fedora\n"), None);
183 }
184}