#[derive(Clone, Debug, Eq, PartialEq)]
pub enum AdapterPolicy {
Auto,
Performance,
Index(usize),
Name(String),
}
impl AdapterPolicy {
#[must_use]
pub const fn platform_default() -> Self {
if cfg!(target_os = "windows") {
Self::Performance
} else {
Self::Auto
}
}
#[must_use]
pub fn parse(value: Option<&str>) -> Self {
Self::parse_set(value).unwrap_or_else(Self::platform_default)
}
#[must_use]
pub fn parse_set(value: Option<&str>) -> Option<Self> {
match value.map(str::trim) {
None | Some("") => None,
Some("auto") => Some(Self::Auto),
Some("performance") => Some(Self::Performance),
Some(text) => Some(
text.parse::<usize>()
.map_or_else(|_| Self::Name(text.to_owned()), Self::Index),
),
}
}
#[must_use]
pub fn from_env() -> Self {
Self::parse(std::env::var("ALPHA2_GPU_ADAPTER").ok().as_deref())
}
#[must_use]
pub fn set_in_env() -> Option<Self> {
Self::parse_set(std::env::var("ALPHA2_GPU_ADAPTER").ok().as_deref())
}
#[must_use]
pub const fn is_pin(&self) -> bool {
matches!(self, Self::Index(_) | Self::Name(_))
}
#[must_use]
pub fn names(&self, index: usize, name: &str) -> bool {
match self {
Self::Index(pinned) => index == *pinned,
Self::Name(pinned) => name.to_lowercase().contains(&pinned.to_lowercase()),
Self::Auto | Self::Performance => false,
}
}
#[must_use]
pub fn setting(&self) -> String {
match self {
Self::Auto => "ALPHA2_GPU_ADAPTER=auto".to_owned(),
Self::Performance => "ALPHA2_GPU_ADAPTER=performance".to_owned(),
Self::Index(index) => format!("ALPHA2_GPU_ADAPTER={index}"),
Self::Name(name) => format!("ALPHA2_GPU_ADAPTER={name}"),
}
}
#[must_use]
pub fn report_name(&self) -> String {
match self {
Self::Auto => "auto".to_owned(),
Self::Performance => "performance".to_owned(),
Self::Index(index) => format!("index:{index}"),
Self::Name(name) => format!("name:{name}"),
}
}
}
#[must_use]
pub fn high_performance_rank(device_type: wgpu::DeviceType) -> u8 {
match device_type {
wgpu::DeviceType::DiscreteGpu => 1,
wgpu::DeviceType::IntegratedGpu => 2,
wgpu::DeviceType::Other => 3,
wgpu::DeviceType::VirtualGpu => 4,
wgpu::DeviceType::Cpu => 5,
}
}
#[must_use]
pub fn product_order(kinds: &[(usize, wgpu::DeviceType)]) -> Vec<usize> {
let mut ordered = kinds.to_vec();
ordered.sort_by_key(|(_, kind)| high_performance_rank(*kind));
ordered.into_iter().map(|(index, _)| index).collect()
}
pub fn gl_version_supported(version: &str) -> Result<(), String> {
let (es, rest) = match version.trim().strip_prefix("OpenGL ES") {
Some(rest) => (true, rest.trim_start()),
None => (false, version.trim()),
};
let mut numbers = rest
.split(|c: char| !c.is_ascii_digit())
.filter(|part| !part.is_empty())
.map(str::parse::<u32>);
let (Some(Ok(major)), Some(Ok(minor))) = (numbers.next(), numbers.next()) else {
return Ok(());
};
let (needed, name) = if es {
((3, 1), "OpenGL ES")
} else {
((4, 3), "OpenGL")
};
if (major, minor) >= needed {
Ok(())
} else {
Err(format!(
"{name} {major}.{minor} cannot run alpha2's shaders (needs desktop GL 4.3 or GLES 3.1)"
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use wgpu::DeviceType::{Cpu, DiscreteGpu, IntegratedGpu, Other, VirtualGpu};
#[test]
fn a_policy_is_auto_performance_an_index_or_a_name_and_unset_is_the_platform_default() {
assert_eq!(
AdapterPolicy::platform_default(),
if cfg!(target_os = "windows") {
AdapterPolicy::Performance
} else {
AdapterPolicy::Auto
}
);
for (value, policy) in [
(None, AdapterPolicy::platform_default()),
(Some(""), AdapterPolicy::platform_default()),
(Some(" auto "), AdapterPolicy::Auto),
(Some("performance"), AdapterPolicy::Performance),
(Some("1"), AdapterPolicy::Index(1)),
(Some("NVIDIA"), AdapterPolicy::Name("NVIDIA".to_owned())),
(Some("encoder"), AdapterPolicy::Name("encoder".to_owned())),
(Some("default"), AdapterPolicy::Name("default".to_owned())),
] {
assert_eq!(AdapterPolicy::parse(value), policy, "{value:?}");
}
assert!(!AdapterPolicy::Performance.is_pin());
assert!(AdapterPolicy::Index(0).is_pin());
assert!(AdapterPolicy::Name("intel".to_owned()).names(3, "Intel(R) Iris(R) Xe Graphics"));
assert!(!AdapterPolicy::Name("intel".to_owned()).names(0, "NVIDIA GeForce MX450"));
assert!(AdapterPolicy::Index(2).names(2, "any"));
assert_eq!(AdapterPolicy::Index(2).setting(), "ALPHA2_GPU_ADAPTER=2");
assert_eq!(AdapterPolicy::Index(2).report_name(), "index:2");
assert_eq!(
AdapterPolicy::Name("Iris".to_owned()).report_name(),
"name:Iris"
);
}
#[test]
fn gl_versions_below_desktop_4_3_and_gles_3_1_are_refused() {
let refused = gl_version_supported("3.3.0 NVIDIA 580.105.08").expect_err("GL 3.3");
assert!(refused.contains("OpenGL 3.3"), "{refused}");
assert!(
refused.contains("needs desktop GL 4.3 or GLES 3.1"),
"{refused}"
);
assert_eq!(
gl_version_supported("4.5 (Core Profile) Mesa 23.2.1"),
Ok(())
);
assert_eq!(gl_version_supported("OpenGL ES 3.2 Mesa"), Ok(()));
let es = gl_version_supported("OpenGL ES 3.0 ANGLE").expect_err("GLES 3.0");
assert!(es.contains("OpenGL ES 3.0"), "{es}");
assert_eq!(gl_version_supported("unknown"), Ok(()));
}
#[test]
fn product_order_follows_the_high_performance_rank_and_keeps_enumeration_order_within_a_rank() {
assert_eq!(
product_order(&[
(0, Cpu),
(1, IntegratedGpu),
(2, DiscreteGpu),
(3, VirtualGpu),
(4, IntegratedGpu),
(5, Other),
(6, DiscreteGpu),
]),
[2, 6, 1, 4, 5, 3, 0]
);
}
}