1#[derive(Clone, Debug, Eq, PartialEq)]
5pub enum AdapterPolicy {
6 Auto,
10 Performance,
13 Index(usize),
15 Name(String),
17}
18
19impl AdapterPolicy {
20 #[must_use]
24 pub const fn platform_default() -> Self {
25 if cfg!(target_os = "windows") {
26 Self::Performance
27 } else {
28 Self::Auto
29 }
30 }
31
32 #[must_use]
35 pub fn parse(value: Option<&str>) -> Self {
36 Self::parse_set(value).unwrap_or_else(Self::platform_default)
37 }
38
39 #[must_use]
41 pub fn parse_set(value: Option<&str>) -> Option<Self> {
42 match value.map(str::trim) {
43 None | Some("") => None,
44 Some("auto") => Some(Self::Auto),
45 Some("performance") => Some(Self::Performance),
46 Some(text) => Some(
47 text.parse::<usize>()
48 .map_or_else(|_| Self::Name(text.to_owned()), Self::Index),
49 ),
50 }
51 }
52
53 #[must_use]
55 pub fn from_env() -> Self {
56 Self::parse(std::env::var("ALPHA2_GPU_ADAPTER").ok().as_deref())
57 }
58
59 #[must_use]
61 pub fn set_in_env() -> Option<Self> {
62 Self::parse_set(std::env::var("ALPHA2_GPU_ADAPTER").ok().as_deref())
63 }
64
65 #[must_use]
67 pub const fn is_pin(&self) -> bool {
68 matches!(self, Self::Index(_) | Self::Name(_))
69 }
70
71 #[must_use]
73 pub fn names(&self, index: usize, name: &str) -> bool {
74 match self {
75 Self::Index(pinned) => index == *pinned,
76 Self::Name(pinned) => name.to_lowercase().contains(&pinned.to_lowercase()),
77 Self::Auto | Self::Performance => false,
78 }
79 }
80
81 #[must_use]
83 pub fn setting(&self) -> String {
84 match self {
85 Self::Auto => "ALPHA2_GPU_ADAPTER=auto".to_owned(),
86 Self::Performance => "ALPHA2_GPU_ADAPTER=performance".to_owned(),
87 Self::Index(index) => format!("ALPHA2_GPU_ADAPTER={index}"),
88 Self::Name(name) => format!("ALPHA2_GPU_ADAPTER={name}"),
89 }
90 }
91
92 #[must_use]
95 pub fn report_name(&self) -> String {
96 match self {
97 Self::Auto => "auto".to_owned(),
98 Self::Performance => "performance".to_owned(),
99 Self::Index(index) => format!("index:{index}"),
100 Self::Name(name) => format!("name:{name}"),
101 }
102 }
103}
104
105#[must_use]
107pub fn high_performance_rank(device_type: wgpu::DeviceType) -> u8 {
108 match device_type {
109 wgpu::DeviceType::DiscreteGpu => 1,
110 wgpu::DeviceType::IntegratedGpu => 2,
111 wgpu::DeviceType::Other => 3,
112 wgpu::DeviceType::VirtualGpu => 4,
113 wgpu::DeviceType::Cpu => 5,
114 }
115}
116
117#[must_use]
119pub fn product_order(kinds: &[(usize, wgpu::DeviceType)]) -> Vec<usize> {
120 let mut ordered = kinds.to_vec();
121 ordered.sort_by_key(|(_, kind)| high_performance_rank(*kind));
123 ordered.into_iter().map(|(index, _)| index).collect()
124}
125
126pub fn gl_version_supported(version: &str) -> Result<(), String> {
132 let (es, rest) = match version.trim().strip_prefix("OpenGL ES") {
133 Some(rest) => (true, rest.trim_start()),
134 None => (false, version.trim()),
135 };
136 let mut numbers = rest
137 .split(|c: char| !c.is_ascii_digit())
138 .filter(|part| !part.is_empty())
139 .map(str::parse::<u32>);
140 let (Some(Ok(major)), Some(Ok(minor))) = (numbers.next(), numbers.next()) else {
141 return Ok(());
142 };
143 let (needed, name) = if es {
144 ((3, 1), "OpenGL ES")
145 } else {
146 ((4, 3), "OpenGL")
147 };
148 if (major, minor) >= needed {
149 Ok(())
150 } else {
151 Err(format!(
152 "{name} {major}.{minor} cannot run alpha2's shaders (needs desktop GL 4.3 or GLES 3.1)"
153 ))
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use wgpu::DeviceType::{Cpu, DiscreteGpu, IntegratedGpu, Other, VirtualGpu};
161
162 #[test]
163 fn a_policy_is_auto_performance_an_index_or_a_name_and_unset_is_the_platform_default() {
164 assert_eq!(
165 AdapterPolicy::platform_default(),
166 if cfg!(target_os = "windows") {
167 AdapterPolicy::Performance
168 } else {
169 AdapterPolicy::Auto
170 }
171 );
172 for (value, policy) in [
173 (None, AdapterPolicy::platform_default()),
174 (Some(""), AdapterPolicy::platform_default()),
175 (Some(" auto "), AdapterPolicy::Auto),
176 (Some("performance"), AdapterPolicy::Performance),
177 (Some("1"), AdapterPolicy::Index(1)),
178 (Some("NVIDIA"), AdapterPolicy::Name("NVIDIA".to_owned())),
179 (Some("encoder"), AdapterPolicy::Name("encoder".to_owned())),
181 (Some("default"), AdapterPolicy::Name("default".to_owned())),
182 ] {
183 assert_eq!(AdapterPolicy::parse(value), policy, "{value:?}");
184 }
185 assert!(!AdapterPolicy::Performance.is_pin());
186 assert!(AdapterPolicy::Index(0).is_pin());
187 assert!(AdapterPolicy::Name("intel".to_owned()).names(3, "Intel(R) Iris(R) Xe Graphics"));
188 assert!(!AdapterPolicy::Name("intel".to_owned()).names(0, "NVIDIA GeForce MX450"));
189 assert!(AdapterPolicy::Index(2).names(2, "any"));
190 assert_eq!(AdapterPolicy::Index(2).setting(), "ALPHA2_GPU_ADAPTER=2");
191 assert_eq!(AdapterPolicy::Index(2).report_name(), "index:2");
192 assert_eq!(
193 AdapterPolicy::Name("Iris".to_owned()).report_name(),
194 "name:Iris"
195 );
196 }
197
198 #[test]
199 fn gl_versions_below_desktop_4_3_and_gles_3_1_are_refused() {
200 let refused = gl_version_supported("3.3.0 NVIDIA 580.105.08").expect_err("GL 3.3");
201 assert!(refused.contains("OpenGL 3.3"), "{refused}");
202 assert!(
203 refused.contains("needs desktop GL 4.3 or GLES 3.1"),
204 "{refused}"
205 );
206 assert_eq!(
207 gl_version_supported("4.5 (Core Profile) Mesa 23.2.1"),
208 Ok(())
209 );
210 assert_eq!(gl_version_supported("OpenGL ES 3.2 Mesa"), Ok(()));
211 let es = gl_version_supported("OpenGL ES 3.0 ANGLE").expect_err("GLES 3.0");
212 assert!(es.contains("OpenGL ES 3.0"), "{es}");
213 assert_eq!(gl_version_supported("unknown"), Ok(()));
214 }
215
216 #[test]
217 fn product_order_follows_the_high_performance_rank_and_keeps_enumeration_order_within_a_rank() {
218 assert_eq!(
219 product_order(&[
220 (0, Cpu),
221 (1, IntegratedGpu),
222 (2, DiscreteGpu),
223 (3, VirtualGpu),
224 (4, IntegratedGpu),
225 (5, Other),
226 (6, DiscreteGpu),
227 ]),
228 [2, 6, 1, 4, 5, 3, 0]
229 );
230 }
231}