use super::capabilities::{HwAccelCapabilities, HwAccelDevice, HwKind};
use std::path::{Path, PathBuf};
pub(crate) fn probe_linux() -> HwAccelCapabilities {
match try_probe_linux() {
Ok(caps) => caps,
Err(e) => {
tracing::debug!("Linux HW accel probe failed (benign): {e}");
HwAccelCapabilities::none()
}
}
}
const VENDOR_INTEL: u32 = 0x8086;
const VENDOR_AMD: u32 = 0x1002;
const VENDOR_NVIDIA: u32 = 0x10de;
#[derive(Debug, Clone, Copy)]
enum GpuVendor {
Intel,
Amd,
Nvidia,
}
fn parse_hex_id(raw: &str) -> Option<u32> {
let trimmed = raw.trim().trim_start_matches("0x").trim_start_matches("0X");
u32::from_str_radix(trimmed, 16).ok()
}
fn read_vendor_id(card_dir: &Path) -> Option<u32> {
let vendor_path = card_dir.join("device/vendor");
let raw = std::fs::read_to_string(&vendor_path).ok()?;
parse_hex_id(&raw)
}
fn read_driver_name(card_dir: &Path) -> Option<String> {
let driver_link = card_dir.join("device/driver");
let target = std::fs::read_link(&driver_link).ok()?;
target
.file_name()
.and_then(|n| n.to_str())
.map(str::to_string)
}
fn find_render_node(_card_dir: &Path, card_index: u32) -> Option<PathBuf> {
let render_minor = 128u32.saturating_add(card_index);
let candidate = PathBuf::from(format!("/dev/dri/renderD{render_minor}"));
if std::fs::metadata(&candidate).is_ok() {
return Some(candidate);
}
for i in 128u32..=159 {
let path = PathBuf::from(format!("/dev/dri/renderD{i}"));
if std::fs::metadata(&path).is_ok() {
return Some(path);
}
}
None
}
fn classify_vendor(id: u32) -> Option<GpuVendor> {
match id {
VENDOR_INTEL => Some(GpuVendor::Intel),
VENDOR_AMD => Some(GpuVendor::Amd),
VENDOR_NVIDIA => Some(GpuVendor::Nvidia),
_ => None,
}
}
fn codecs_for_vendor(vendor: GpuVendor) -> Vec<String> {
match vendor {
GpuVendor::Intel => {
vec![
"h264".to_string(),
"hevc".to_string(),
"av1".to_string(),
"vp9".to_string(),
]
}
GpuVendor::Amd => {
vec![
"h264".to_string(),
"hevc".to_string(),
"av1".to_string(),
"vp9".to_string(),
"vp8".to_string(),
]
}
GpuVendor::Nvidia => {
vec!["h264".to_string(), "hevc".to_string()]
}
}
}
fn supports_hdr(vendor: GpuVendor) -> bool {
matches!(vendor, GpuVendor::Intel | GpuVendor::Amd)
}
fn try_probe_linux() -> Result<HwAccelCapabilities, String> {
let drm_class = Path::new("/sys/class/drm");
if !drm_class.exists() {
return Ok(HwAccelCapabilities::none());
}
let entries =
std::fs::read_dir(drm_class).map_err(|e| format!("read_dir /sys/class/drm: {e}"))?;
let mut devices: Vec<HwAccelDevice> = Vec::new();
let mut card_index: u32 = 0;
for entry_res in entries {
let entry = entry_res.map_err(|e| format!("readdir error: {e}"))?;
let name = entry.file_name();
let name_str = name.to_string_lossy();
if !name_str.starts_with("card") {
continue;
}
if name_str.chars().filter(|c| c == &'-').count() > 0 {
continue;
}
let card_dir = entry.path();
let vendor_id = match read_vendor_id(&card_dir) {
Some(v) => v,
None => {
card_index = card_index.saturating_add(1);
continue;
}
};
let vendor = match classify_vendor(vendor_id) {
Some(v) => v,
None => {
tracing::debug!(
"Unknown GPU vendor {:#06x} at {:?}; skipping",
vendor_id,
card_dir
);
card_index = card_index.saturating_add(1);
continue;
}
};
let render_node = find_render_node(&card_dir, card_index);
if render_node.is_none() {
tracing::debug!("No render node for {:?}; skipping", card_dir);
card_index = card_index.saturating_add(1);
continue;
}
let driver = read_driver_name(&card_dir);
let codecs = codecs_for_vendor(vendor);
let hdr = supports_hdr(vendor);
devices.push(HwAccelDevice {
kind: HwKind::Vaapi,
driver,
render_node,
supported_codecs: codecs,
max_width: 8192,
max_height: 4320,
supports_hdr: hdr,
});
card_index = card_index.saturating_add(1);
}
Ok(HwAccelCapabilities { devices })
}