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, _)) => Ok(a),
None => Err(gpu_unavailable_for(adapters).to_string()),
},
));
WgpuConfiguration { wgpu_setup: WgpuSetup::CreateNew(setup), ..Default::default() }
}
#[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"),
}
}
}