corundum_framework_gui/
gpu.rs1use std::sync::Arc;
19
20use eframe::{egui_wgpu, wgpu};
21
22pub fn wgpu_options() -> egui_wgpu::WgpuConfiguration {
24 let mut options = egui_wgpu::WgpuConfiguration::default();
25 if let egui_wgpu::WgpuSetup::CreateNew(setup) = &mut options.wgpu_setup {
26 setup.instance_descriptor.backends = backends();
27 setup.instance_descriptor.flags = instance_flags(setup.instance_descriptor.flags);
28 let egui_default = Arc::clone(&setup.device_descriptor);
29 setup.device_descriptor = Arc::new(move |adapter| {
30 let mut descriptor = egui_default(adapter);
31 descriptor.required_limits = descriptor
32 .required_limits
33 .or_worse_values_from(&adapter.limits());
34 descriptor
35 });
36 }
37 options
38}
39
40fn instance_flags(flags: wgpu::InstanceFlags) -> wgpu::InstanceFlags {
44 if cfg!(target_os = "android") {
45 flags - wgpu::InstanceFlags::DEBUG
46 } else {
47 flags
48 }
49}
50
51fn backends() -> wgpu::Backends {
53 wgpu::Backends::from_env().unwrap_or_else(default_backends)
54}
55
56fn default_backends() -> wgpu::Backends {
60 if cfg!(windows) {
61 wgpu::Backends::VULKAN
62 } else if cfg!(target_os = "android") {
63 wgpu::Backends::VULKAN | wgpu::Backends::GL
66 } else {
67 wgpu::Backends::PRIMARY
68 }
69}
70
71pub fn on_render_state(render_state: Option<&egui_wgpu::RenderState>) {
75 let Some(render_state) = render_state else {
76 tracing::warn!("no wgpu render state; skipping GPU diagnostics");
77 return;
78 };
79
80 let info = render_state.adapter.get_info();
81 tracing::info!(
82 backend = ?info.backend,
83 adapter = %info.name,
84 device_type = ?info.device_type,
85 driver = %info.driver_info,
86 "wgpu renderer ready"
87 );
88
89 render_state
92 .device
93 .set_device_lost_callback(|reason, message| {
94 tracing::error!(?reason, %message, "GPU device lost");
95 });
96}
97
98pub fn install_panic_hook() {
103 let previous = std::panic::take_hook();
104 std::panic::set_hook(Box::new(move |info| {
105 if let Some(explanation) = panic_message(info).and_then(device_lost_explanation) {
106 tracing::error!("{explanation}");
107 }
108 previous(info);
109 }));
110}
111
112fn panic_message<'a>(info: &'a std::panic::PanicHookInfo<'_>) -> Option<&'a str> {
113 let payload = info.payload();
114 payload
115 .downcast_ref::<&str>()
116 .copied()
117 .or_else(|| payload.downcast_ref::<String>().map(String::as_str))
118}
119
120const DEVICE_LOST_EXPLANATION: &str = concat!(
121 "The GPU device was lost. egui-wgpu reports this as a staging buffer failure, ",
122 "but the buffer size is not the problem: wgpu swallows a lost device and returns ",
123 "None, and egui-wgpu panics on that None. On Windows this usually means a Remote ",
124 "Desktop reconnect or a display change removed the graphics device. Restart the app.",
125);
126
127fn device_lost_explanation(panic_msg: &str) -> Option<&'static str> {
133 panic_msg
134 .contains("Failed to create staging buffer for ")
135 .then_some(DEVICE_LOST_EXPLANATION)
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 const INDEX_PANIC: &str = "Failed to create staging buffer for index data. \
144 Index count: 16113. Required index buffer size: 64452. \
145 Actual size 99168 and capacity: 99168 (bytes)";
146
147 const VERTEX_PANIC: &str = "Failed to create staging buffer for vertex data. \
149 Vertex count: 4028. Required vertex buffer size: 128896. \
150 Actual size 198336 and capacity: 198336 (bytes)";
151
152 #[test]
153 fn explains_the_index_buffer_panic() {
154 assert!(device_lost_explanation(INDEX_PANIC).is_some());
155 }
156
157 #[test]
158 fn explains_the_vertex_buffer_panic() {
159 assert!(device_lost_explanation(VERTEX_PANIC).is_some());
160 }
161
162 #[test]
163 fn explanation_names_the_real_cause() {
164 let msg = device_lost_explanation(INDEX_PANIC).unwrap();
165 assert!(
166 msg.contains("device"),
167 "should say the device was lost: {msg}"
168 );
169 assert!(
170 msg.contains("Remote Desktop"),
171 "should name the usual Windows trigger: {msg}"
172 );
173 }
174
175 #[test]
176 fn ignores_unrelated_panics() {
177 assert_eq!(
178 device_lost_explanation("index out of bounds: len is 3"),
179 None
180 );
181 assert_eq!(device_lost_explanation(""), None);
182 }
183
184 #[cfg(windows)]
185 #[test]
186 fn defaults_to_vulkan_on_windows_without_an_env_override() {
187 assert_eq!(backends(), wgpu::Backends::VULKAN);
189 }
190
191 #[cfg(target_os = "android")]
192 #[test]
193 fn defaults_to_vulkan_and_gl_on_android_without_an_env_override() {
194 assert_eq!(backends(), wgpu::Backends::VULKAN | wgpu::Backends::GL);
196 }
197
198 #[cfg(not(any(windows, target_os = "android")))]
199 #[test]
200 fn defaults_to_the_primary_backends_elsewhere_without_an_env_override() {
201 assert_eq!(backends(), wgpu::Backends::PRIMARY);
203 }
204
205 #[test]
206 fn android_drops_the_debug_labels_and_keeps_validation() {
207 let flags = instance_flags(wgpu::InstanceFlags::debugging());
208 assert_eq!(
209 flags.contains(wgpu::InstanceFlags::DEBUG),
210 !cfg!(target_os = "android")
211 );
212 assert!(flags.contains(wgpu::InstanceFlags::VALIDATION));
213 }
214
215 #[test]
216 fn the_default_is_vulkan_on_windows_and_adds_gl_on_android() {
217 assert_eq!(
218 default_backends(),
219 if cfg!(windows) {
220 wgpu::Backends::VULKAN
221 } else if cfg!(target_os = "android") {
222 wgpu::Backends::VULKAN | wgpu::Backends::GL
223 } else {
224 wgpu::Backends::PRIMARY
225 }
226 );
227 }
228}