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}