use super::pass_clock::PASS_CLOCK_FEATURES;
use crate::render::adapter::{
classify_unavailable, env_override, env_prefers_low_power, record_gpu_unavailable,
record_selection, AdapterFacts, AdapterKind, AdapterSelection, GpuUnavailable,
};
#[must_use]
pub fn kind_of(device_type: wgpu::DeviceType) -> AdapterKind {
match device_type {
wgpu::DeviceType::DiscreteGpu => AdapterKind::DiscreteGpu,
wgpu::DeviceType::IntegratedGpu => AdapterKind::IntegratedGpu,
wgpu::DeviceType::VirtualGpu => AdapterKind::VirtualGpu,
wgpu::DeviceType::Cpu => AdapterKind::Cpu,
wgpu::DeviceType::Other => AdapterKind::Other,
}
}
#[must_use]
pub fn facts_of(info: &wgpu::AdapterInfo) -> AdapterFacts {
AdapterFacts {
name: info.name.clone(),
vendor: info.vendor,
device: info.device,
kind: kind_of(info.device_type),
backend: format!("{:?}", info.backend),
driver: info.driver.clone(),
}
}
#[must_use]
pub fn facts_of_all(adapters: &[wgpu::Adapter]) -> Vec<AdapterFacts> {
adapters.iter().map(|a| facts_of(&a.get_info())).collect()
}
#[must_use]
pub fn decide_for(adapters: &[wgpu::Adapter]) -> Option<AdapterSelection> {
let facts = facts_of_all(adapters);
AdapterSelection::decide(&facts, env_override().as_deref())
}
pub fn select_adapter(adapters: &[wgpu::Adapter]) -> Option<(wgpu::Adapter, AdapterSelection)> {
let sel = decide_for(adapters)?;
let chosen = adapters[sel.index].clone();
record_selection(sel.clone());
Some((chosen, sel))
}
pub fn request_best_adapter(
instance: &wgpu::Instance,
backends: wgpu::Backends,
) -> Option<wgpu::Adapter> {
let adapters = pollster::block_on(instance.enumerate_adapters(backends));
if !adapters.is_empty() {
if let Some((a, _)) = select_adapter(&adapters) {
return Some(a);
}
}
let power_preference = if env_prefers_low_power() {
wgpu::PowerPreference::LowPower
} else {
wgpu::PowerPreference::HighPerformance
};
pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference,
force_fallback_adapter: false,
compatible_surface: None,
}))
.ok()
}
#[must_use]
pub fn preferred_backends() -> wgpu::Backends {
#[cfg(target_arch = "wasm32")]
{
wgpu::Backends::BROWSER_WEBGPU
}
#[cfg(not(target_arch = "wasm32"))]
{
wgpu::Backends::PRIMARY
}
}
#[must_use]
pub fn facett_wgpu_options() -> egui_wgpu::WgpuConfiguration {
use egui_wgpu::{WgpuConfiguration, WgpuSetup, WgpuSetupCreateNew};
let mut setup = WgpuSetupCreateNew::without_display_handle();
setup.instance_descriptor.backends = preferred_backends();
setup.power_preference = if env_prefers_low_power() {
wgpu::PowerPreference::LowPower
} else {
wgpu::PowerPreference::HighPerformance
};
setup.device_descriptor = std::sync::Arc::new(|adapter: &wgpu::Adapter| {
let base_limits = if adapter.get_info().backend == wgpu::Backend::Gl {
wgpu::Limits::downlevel_webgl2_defaults()
} else {
wgpu::Limits::default()
};
wgpu::DeviceDescriptor {
label: Some("facett wgpu device"),
required_features: adapter.features() & PASS_CLOCK_FEATURES,
required_limits: wgpu::Limits { max_texture_dimension_2d: 8192, ..base_limits },
..Default::default()
}
});
setup.native_adapter_selector = Some(std::sync::Arc::new(
|adapters: &[wgpu::Adapter], surface: Option<&wgpu::Surface<'_>>| match select_adapter(
adapters,
) {
Some((a, _)) => {
if let Some(s) = surface {
record_surface_present_modes(s.get_capabilities(&a).present_modes);
}
Ok(a)
}
None => Err(gpu_unavailable_for(adapters).to_string()),
},
));
WgpuConfiguration {
wgpu_setup: WgpuSetup::CreateNew(setup),
surface: egui_wgpu::SurfaceConfig::LOW_LATENCY,
..Default::default()
}
}
static PRESENT_MODES: std::sync::OnceLock<std::sync::Mutex<Option<Vec<wgpu::PresentMode>>>> =
std::sync::OnceLock::new();
fn present_modes_slot() -> &'static std::sync::Mutex<Option<Vec<wgpu::PresentMode>>> {
PRESENT_MODES.get_or_init(|| std::sync::Mutex::new(None))
}
pub fn record_surface_present_modes(modes: Vec<wgpu::PresentMode>) {
if let Ok(mut g) = present_modes_slot().lock() {
*g = Some(modes);
}
}
#[must_use]
pub fn surface_present_modes() -> Option<Vec<wgpu::PresentMode>> {
present_modes_slot().lock().ok().and_then(|g| g.clone())
}
#[must_use]
pub fn present_mode_for(supported: Option<&[wgpu::PresentMode]>, preferred: wgpu::PresentMode) -> wgpu::PresentMode {
match preferred {
wgpu::PresentMode::AutoVsync | wgpu::PresentMode::AutoNoVsync => preferred,
_ => match supported {
Some(modes) if modes.contains(&preferred) => preferred,
_ => wgpu::PresentMode::AutoVsync,
},
}
}
#[must_use]
pub fn gpu_unavailable_for(adapters: &[wgpu::Adapter]) -> GpuUnavailable {
let err = classify_unavailable(format!("{:?}", preferred_backends()), facts_of_all(adapters));
record_gpu_unavailable(err.clone());
err
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn device_type_mapping_is_exact() {
assert_eq!(kind_of(wgpu::DeviceType::DiscreteGpu), AdapterKind::DiscreteGpu);
assert_eq!(kind_of(wgpu::DeviceType::IntegratedGpu), AdapterKind::IntegratedGpu);
assert_eq!(kind_of(wgpu::DeviceType::VirtualGpu), AdapterKind::VirtualGpu);
assert_eq!(kind_of(wgpu::DeviceType::Cpu), AdapterKind::Cpu);
assert_eq!(kind_of(wgpu::DeviceType::Other), AdapterKind::Other);
}
fn info(name: &str, dt: wgpu::DeviceType, vendor: u32) -> wgpu::AdapterInfo {
wgpu::AdapterInfo {
name: name.to_owned(),
vendor,
device: 0,
device_type: dt,
device_pci_bus_id: String::new(),
driver: String::new(),
driver_info: String::new(),
backend: wgpu::Backend::Vulkan,
subgroup_min_size: 0,
subgroup_max_size: 0,
transient_saves_memory: false,
}
}
#[test]
fn oden_enumeration_resolves_to_the_discrete_gpu() {
use crate::render::adapter::{choose_adapter_index, VENDOR_ASPEED, VENDOR_NVIDIA};
let infos = [
info("ASPEED Graphics Family", wgpu::DeviceType::Other, VENDOR_ASPEED),
info("llvmpipe (LLVM 21.1.8, 256 bits)", wgpu::DeviceType::Cpu, 0x1_0005),
info("NVIDIA GeForce RTX 4090", wgpu::DeviceType::DiscreteGpu, VENDOR_NVIDIA),
];
let facts: Vec<_> = infos.iter().map(facts_of).collect();
assert_eq!(facts[2].backend, "Vulkan");
let pick = choose_adapter_index(&facts).expect("must pick");
assert_eq!(facts[pick].name, "NVIDIA GeForce RTX 4090");
let sel = AdapterSelection::decide(&facts, None).expect("decides");
assert!(!sel.software);
assert_eq!(sel.index, 2);
}
#[test]
fn wgpu_options_install_the_policy() {
use egui_wgpu::WgpuSetup;
let cfg = facett_wgpu_options();
match cfg.wgpu_setup {
WgpuSetup::CreateNew(setup) => {
assert!(
setup.native_adapter_selector.is_some(),
"the adapter-selection policy must be installed"
);
assert_eq!(setup.power_preference, wgpu::PowerPreference::HighPerformance);
assert!(
setup.instance_descriptor.backends.contains(wgpu::Backends::VULKAN),
"Vulkan must be among the requested backends"
);
}
_ => panic!("expected CreateNew"),
}
}
#[test]
fn wgpu_options_keep_eframes_low_latency_queue() {
let cfg = facett_wgpu_options();
assert_eq!(
cfg.surface.desired_maximum_frame_latency,
Some(1),
"one queued frame, as eframe's NativeOptions::default() has — not wgpu's 2"
);
assert_eq!(cfg.surface.present_mode, wgpu::PresentMode::AutoVsync, "the safe boot mode");
}
#[test]
fn present_mode_for_falls_back_to_vsync_when_mailbox_is_unsupported() {
use wgpu::PresentMode as P;
assert_eq!(present_mode_for(Some(&[P::Fifo, P::Mailbox, P::Immediate]), P::Mailbox), P::Mailbox);
assert_eq!(present_mode_for(Some(&[P::Fifo, P::Immediate]), P::Mailbox), P::AutoVsync);
assert_eq!(present_mode_for(Some(&[P::Fifo]), P::Mailbox), P::AutoVsync);
assert_eq!(present_mode_for(None, P::Mailbox), P::AutoVsync, "no surface seen = no claim");
assert_eq!(present_mode_for(Some(&[]), P::Mailbox), P::AutoVsync);
assert_eq!(present_mode_for(Some(&[P::Fifo]), P::AutoVsync), P::AutoVsync);
assert_eq!(present_mode_for(None, P::AutoNoVsync), P::AutoNoVsync);
assert_eq!(present_mode_for(Some(&[P::Fifo, P::Immediate]), P::Immediate), P::Immediate);
}
}