Skip to main content

oxideav_core/
engine.rs

1//! Per-codec hardware engine probing.
2//!
3//! Each HW-accel sibling crate (oxideav-nvidia, oxideav-vaapi,
4//! oxideav-vdpau, oxideav-vulkan-video, oxideav-videotoolbox) attaches
5//! an [`EngineProbeFn`] to every [`crate::CodecInfo`] it registers via
6//! [`crate::CodecInfo::with_engine_probe`]. The CLI's `info` command
7//! (and any other consumer) calls the probe on demand to enumerate
8//! the physical / logical engines that backend can dispatch to —
9//! GPU name, driver version, per-codec capability matrix, etc.
10//!
11//! Probes are called on demand, not at registration time, so the cost
12//! of opening device handles + querying capabilities is only paid
13//! when someone asks. Probes should be idempotent and side-effect
14//! free; consumers may call them more than once per process.
15//!
16//! There is no distributed slice and no collection macro: engine info
17//! travels with each [`crate::CodecInfo`], matching the explicit-calls
18//! pattern already used by `oxideav-meta`'s `register_all`. Consumers
19//! that want to enumerate engines walk the codec registry, group
20//! entries by [`crate::CodecInfo::engine_id`], and call each backend's
21//! [`EngineProbeFn`] at most once per group.
22
23/// A single hardware engine the backend can dispatch to. For NVIDIA /
24/// Vulkan / VA-API DRM, this is one entry per physical GPU. For
25/// VDPAU on a single-X11-display system, this is one entry per X
26/// screen. For VideoToolbox on Apple Silicon, this is one entry per
27/// SoC.
28#[derive(Clone, Debug)]
29pub struct HwDeviceInfo {
30    /// Human-readable device name. e.g. "NVIDIA GeForce RTX 5080",
31    /// "Intel(R) UHD Graphics 770", "Apple M3 Max Media Engine".
32    pub name: String,
33    /// Driver / runtime version, if reportable. e.g. "580.95.05",
34    /// "Mesa 24.2", "VideoToolbox (system)".
35    pub driver_version: Option<String>,
36    /// API version the backend speaks. e.g. "CUDA 12.6",
37    /// "VDPAU API 1", "Vulkan 1.4", "VA-API 1.22".
38    pub api_version: Option<String>,
39    /// On-card memory in bytes if known. Discrete GPUs report a real
40    /// figure; integrated and shared-memory engines usually report
41    /// `None`.
42    pub total_memory_bytes: Option<u64>,
43    /// Backend-specific extras keyed by string. e.g. for NVIDIA:
44    /// `("compute_capability", "12.0")`. CLI prints these as
45    /// `key = value` in a sub-block. Order is preserved.
46    pub extra: Vec<(String, String)>,
47    /// Per-codec capabilities for codecs this engine can decode and/or
48    /// encode.
49    pub codecs: Vec<HwCodecCaps>,
50}
51
52/// Capabilities of a single codec on a single device.
53#[derive(Clone, Debug)]
54pub struct HwCodecCaps {
55    /// Codec id matching `oxideav_core::CodecId`. e.g. "h264", "hevc",
56    /// "av1", "vp9". Should be the same string the SW codec uses.
57    pub codec: String,
58    /// Whether this device can decode this codec.
59    pub decode: bool,
60    /// Whether this device can encode this codec.
61    pub encode: bool,
62    /// Max coded width supported, if reportable.
63    pub max_width: Option<u32>,
64    /// Max coded height supported, if reportable.
65    pub max_height: Option<u32>,
66    /// Max bit-depth supported, if reportable. Typically 8, 10, or 12.
67    pub max_bit_depth: Option<u32>,
68    /// Profile names (backend-specific). e.g. for H.264 on NVDEC:
69    /// `["Baseline", "Main", "High"]`.
70    pub profiles: Vec<String>,
71    /// Backend-specific extras (e.g. `("max_dpb_slots", "17")`).
72    pub extra: Vec<(String, String)>,
73}
74
75/// Function signature for a backend's engine probe. Returns one entry
76/// per device the backend currently sees. Call cheaply; consumers may
77/// call multiple times per process.
78pub type EngineProbeFn = fn() -> Vec<HwDeviceInfo>;
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn hw_device_info_clone_and_extras_round_trip() {
86        let info = HwDeviceInfo {
87            name: "Test GPU".into(),
88            driver_version: Some("1.0".into()),
89            api_version: Some("API 1".into()),
90            total_memory_bytes: Some(16 * 1024 * 1024 * 1024),
91            extra: vec![("compute_capability".into(), "12.0".into())],
92            codecs: vec![HwCodecCaps {
93                codec: "h264".into(),
94                decode: true,
95                encode: true,
96                max_width: Some(8192),
97                max_height: Some(8192),
98                max_bit_depth: Some(8),
99                profiles: vec!["Baseline".into(), "Main".into(), "High".into()],
100                extra: vec![],
101            }],
102        };
103        let clone = info.clone();
104        assert_eq!(clone.name, "Test GPU");
105        assert_eq!(clone.driver_version.as_deref(), Some("1.0"));
106        assert_eq!(clone.api_version.as_deref(), Some("API 1"));
107        assert_eq!(clone.total_memory_bytes, Some(16 * 1024 * 1024 * 1024));
108        assert_eq!(clone.extra.len(), 1);
109        assert_eq!(clone.extra[0].0, "compute_capability");
110        assert_eq!(clone.extra[0].1, "12.0");
111        assert_eq!(clone.codecs.len(), 1);
112        assert_eq!(clone.codecs[0].codec, "h264");
113        assert!(clone.codecs[0].decode);
114        assert!(clone.codecs[0].encode);
115        assert_eq!(clone.codecs[0].profiles.len(), 3);
116    }
117
118    #[test]
119    fn engine_probe_fn_is_callable() {
120        fn empty_probe() -> Vec<HwDeviceInfo> {
121            vec![]
122        }
123        let probe: EngineProbeFn = empty_probe;
124        assert!(probe().is_empty());
125    }
126}