use std::sync::Arc;
use eframe::{egui_wgpu, wgpu};
pub fn wgpu_options() -> egui_wgpu::WgpuConfiguration {
let mut options = egui_wgpu::WgpuConfiguration::default();
if let egui_wgpu::WgpuSetup::CreateNew(setup) = &mut options.wgpu_setup {
setup.instance_descriptor.backends = backends();
setup.instance_descriptor.flags = instance_flags(setup.instance_descriptor.flags);
let egui_default = Arc::clone(&setup.device_descriptor);
setup.device_descriptor = Arc::new(move |adapter| {
let mut descriptor = egui_default(adapter);
descriptor.required_limits = descriptor
.required_limits
.or_worse_values_from(&adapter.limits());
descriptor
});
}
options
}
fn instance_flags(flags: wgpu::InstanceFlags) -> wgpu::InstanceFlags {
if cfg!(target_os = "android") {
flags - wgpu::InstanceFlags::DEBUG
} else {
flags
}
}
fn backends() -> wgpu::Backends {
wgpu::Backends::from_env().unwrap_or_else(default_backends)
}
fn default_backends() -> wgpu::Backends {
if cfg!(windows) {
wgpu::Backends::VULKAN
} else if cfg!(target_os = "android") {
wgpu::Backends::VULKAN | wgpu::Backends::GL
} else {
wgpu::Backends::PRIMARY
}
}
pub fn on_render_state(render_state: Option<&egui_wgpu::RenderState>) {
let Some(render_state) = render_state else {
tracing::warn!("no wgpu render state; skipping GPU diagnostics");
return;
};
let info = render_state.adapter.get_info();
tracing::info!(
backend = ?info.backend,
adapter = %info.name,
device_type = ?info.device_type,
driver = %info.driver_info,
"wgpu renderer ready"
);
render_state
.device
.set_device_lost_callback(|reason, message| {
tracing::error!(?reason, %message, "GPU device lost");
});
}
pub fn install_panic_hook() {
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
if let Some(explanation) = panic_message(info).and_then(device_lost_explanation) {
tracing::error!("{explanation}");
}
previous(info);
}));
}
fn panic_message<'a>(info: &'a std::panic::PanicHookInfo<'_>) -> Option<&'a str> {
let payload = info.payload();
payload
.downcast_ref::<&str>()
.copied()
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
}
const DEVICE_LOST_EXPLANATION: &str = concat!(
"The GPU device was lost. egui-wgpu reports this as a staging buffer failure, ",
"but the buffer size is not the problem: wgpu swallows a lost device and returns ",
"None, and egui-wgpu panics on that None. On Windows this usually means a Remote ",
"Desktop reconnect or a display change removed the graphics device. Restart the app.",
);
fn device_lost_explanation(panic_msg: &str) -> Option<&'static str> {
panic_msg
.contains("Failed to create staging buffer for ")
.then_some(DEVICE_LOST_EXPLANATION)
}
#[cfg(test)]
mod tests {
use super::*;
const INDEX_PANIC: &str = "Failed to create staging buffer for index data. \
Index count: 16113. Required index buffer size: 64452. \
Actual size 99168 and capacity: 99168 (bytes)";
const VERTEX_PANIC: &str = "Failed to create staging buffer for vertex data. \
Vertex count: 4028. Required vertex buffer size: 128896. \
Actual size 198336 and capacity: 198336 (bytes)";
#[test]
fn explains_the_index_buffer_panic() {
assert!(device_lost_explanation(INDEX_PANIC).is_some());
}
#[test]
fn explains_the_vertex_buffer_panic() {
assert!(device_lost_explanation(VERTEX_PANIC).is_some());
}
#[test]
fn explanation_names_the_real_cause() {
let msg = device_lost_explanation(INDEX_PANIC).unwrap();
assert!(
msg.contains("device"),
"should say the device was lost: {msg}"
);
assert!(
msg.contains("Remote Desktop"),
"should name the usual Windows trigger: {msg}"
);
}
#[test]
fn ignores_unrelated_panics() {
assert_eq!(
device_lost_explanation("index out of bounds: len is 3"),
None
);
assert_eq!(device_lost_explanation(""), None);
}
#[cfg(windows)]
#[test]
fn defaults_to_vulkan_on_windows_without_an_env_override() {
assert_eq!(backends(), wgpu::Backends::VULKAN);
}
#[cfg(target_os = "android")]
#[test]
fn defaults_to_vulkan_and_gl_on_android_without_an_env_override() {
assert_eq!(backends(), wgpu::Backends::VULKAN | wgpu::Backends::GL);
}
#[cfg(not(any(windows, target_os = "android")))]
#[test]
fn defaults_to_the_primary_backends_elsewhere_without_an_env_override() {
assert_eq!(backends(), wgpu::Backends::PRIMARY);
}
#[test]
fn android_drops_the_debug_labels_and_keeps_validation() {
let flags = instance_flags(wgpu::InstanceFlags::debugging());
assert_eq!(
flags.contains(wgpu::InstanceFlags::DEBUG),
!cfg!(target_os = "android")
);
assert!(flags.contains(wgpu::InstanceFlags::VALIDATION));
}
#[test]
fn the_default_is_vulkan_on_windows_and_adds_gl_on_android() {
assert_eq!(
default_backends(),
if cfg!(windows) {
wgpu::Backends::VULKAN
} else if cfg!(target_os = "android") {
wgpu::Backends::VULKAN | wgpu::Backends::GL
} else {
wgpu::Backends::PRIMARY
}
);
}
}