#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
use super::{Accelerators, PROBE_TIMEOUT, probe};
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
const SYSTEM_PROFILER_PATH: &str = "/usr/sbin/system_profiler";
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
const SYSTEM_PROFILER_ARGS: &[&str] = &[DISPLAYS_DATA_TYPE, "-json", "-detailLevel", "mini"];
const DISPLAYS_DATA_TYPE: &str = "SPDisplaysDataType";
const METAL_SUPPORT_KEYS: [&str; 2] = ["spdisplays_mtlgpufamilysupport", "spdisplays_metal"];
const METAL_SUPPORTED: &str = "spdisplays_supported";
const METAL_VERSION_PREFIX: &str = "spdisplays_metal";
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
pub(super) fn detect() -> Accelerators {
let output = probe::output(SYSTEM_PROFILER_PATH, SYSTEM_PROFILER_ARGS, PROBE_TIMEOUT).ok();
Accelerators {
gpu: output.as_deref().is_some_and(metal_available),
cuda: false,
dedicated_memory: None,
}
}
fn metal_available(output: &[u8]) -> bool {
let Ok(report) = serde_json::from_slice::<serde_json::Value>(output) else {
return false;
};
let Some(devices) = report
.get(DISPLAYS_DATA_TYPE)
.and_then(serde_json::Value::as_array)
else {
return false;
};
devices.iter().any(|device| {
METAL_SUPPORT_KEYS
.iter()
.filter_map(|key| device.get(key).and_then(serde_json::Value::as_str))
.any(reports_metal_support)
})
}
fn reports_metal_support(value: &str) -> bool {
value == METAL_SUPPORTED
|| value
.strip_prefix(METAL_VERSION_PREFIX)
.is_some_and(|version| {
!version.is_empty() && version.bytes().all(|byte| byte.is_ascii_digit())
})
}
#[cfg(test)]
mod tests {
use super::metal_available;
#[test]
fn detects_apple_metal_and_legacy_supported_reports() {
for report in [
serde_json::json!({
"SPDisplaysDataType": [{
"sppci_model": "Apple M4 Pro",
"spdisplays_mtlgpufamilysupport": "spdisplays_metal3"
}]
}),
serde_json::json!({
"SPDisplaysDataType": [
{ "sppci_model": "Unsupported adapter" },
{ "spdisplays_metal": "spdisplays_supported" }
]
}),
serde_json::json!({
"SPDisplaysDataType": [{
"spdisplays_mtlgpufamilysupport": "spdisplays_supported"
}]
}),
serde_json::json!({
"SPDisplaysDataType": [{
"spdisplays_mtlgpufamilysupport": "spdisplays_unsupported",
"spdisplays_metal": "spdisplays_metal12"
}]
}),
] {
assert!(metal_available(&serde_json::to_vec(&report).unwrap()));
}
}
#[test]
fn malformed_or_unsupported_display_reports_do_not_enable_graphics() {
for report in [
b"not JSON".as_slice(),
b"{}",
b"[]",
br#"{"SPDisplaysDataType": []}"#,
br#"{"SPDisplaysDataType": {}}"#,
br#"{"SPDisplaysDataType": [null, 1, "spdisplays_supported"]}"#,
br#"{"SPDisplaysDataType": [{"sppci_model": "Apple M4 Pro"}]}"#,
br#"{"SPDisplaysDataType": [{"spdisplays_metal": "spdisplays_unsupported"}]}"#,
br#"{"SPDisplaysDataType": [{"spdisplays_metal": true}]}"#,
br#"{"SPDisplaysDataType": [{"spdisplays_metal": ""}]}"#,
br#"{"SPDisplaysDataType": [{"spdisplays_metal": "spdisplays_metal3.1"}]}"#,
br#"{"SPDisplaysDataType": [{"spdisplays_metal": "spdisplays_metal3extra"}]}"#,
br#"{"SPDisplaysDataType": [{"spdisplays_mtlgpufamilysupport": "spdisplays_metal"}]}"#,
] {
assert!(!metal_available(report));
}
}
}