g2g_core/pad_template.rs
1//! Pad templates: declarative, pre-instantiation metadata describing the
2//! caps an element *type* can accept or produce.
3//!
4//! `caps_constraint_as_*` are runtime methods on a *constructed* element,
5//! so they can reflect instance state (a sink already narrowed to one
6//! format, a decoder's derived output). A pad template is the *static*
7//! superset of what the element type can ever do, and it is queryable
8//! without constructing the element. This is the analog of GStreamer's
9//! static pad templates from `gst_element_factory_get_static_pad_templates`.
10//!
11//! Two uses:
12//! - **Introspection.** A tool lists an element type's pads and the caps
13//! each supports without building a graph.
14//! - **Pre-instantiation solver queries.** [`pad_link`] / [`types_can_link`]
15//! run the same [`solve_linear`] used at negotiation against two types'
16//! templates, answering "can A's output feed B's input?" before either
17//! element exists.
18
19use alloc::vec::Vec;
20
21use crate::caps::{Caps, CapsSet};
22use crate::format_element::CapsConstraint;
23use crate::runtime::solver::{solve_linear, NegotiationFailure};
24
25/// Which side of an element a pad sits on.
26// Closed set: intentionally exhaustive (not #[non_exhaustive]); see STABILITY.md.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum PadDirection {
29 /// Input pad: the element consumes caps here.
30 Sink,
31 /// Output pad: the element produces caps here.
32 Source,
33}
34
35/// The static caps capability of a pad.
36#[derive(Debug, Clone, PartialEq)]
37pub enum PadCaps {
38 /// A concrete, ordered set of supported caps (highest preference first).
39 Fixed(CapsSet),
40 /// Wildcard: a sink pad that accepts any caps (e.g. `FakeSink`). On a
41 /// source pad this is degenerate, a producer must name concrete caps,
42 /// and is treated as the empty producible set.
43 Any,
44}
45
46/// Static, pre-instantiation declaration of one pad's caps capability. The
47/// runtime `caps_constraint_as_*` of a constructed instance is always a
48/// subset of its pad template (it may narrow to instance configuration).
49#[derive(Debug, Clone, PartialEq)]
50pub struct PadTemplate {
51 pub direction: PadDirection,
52 pub caps: PadCaps,
53}
54
55impl PadTemplate {
56 /// A sink pad accepting the given concrete caps set.
57 pub fn sink(caps: CapsSet) -> Self {
58 Self {
59 direction: PadDirection::Sink,
60 caps: PadCaps::Fixed(caps),
61 }
62 }
63
64 /// A source pad producing the given concrete caps set.
65 pub fn source(caps: CapsSet) -> Self {
66 Self {
67 direction: PadDirection::Source,
68 caps: PadCaps::Fixed(caps),
69 }
70 }
71
72 /// A sink pad accepting any caps (wildcard).
73 pub fn sink_any() -> Self {
74 Self {
75 direction: PadDirection::Sink,
76 caps: PadCaps::Any,
77 }
78 }
79
80 /// The solver constraint this pad contributes at its end of a link: a
81 /// source pad `Produces`, a sink pad `Accepts` (or `AcceptsAny`). The
82 /// result owns its caps, so it borrows nothing from `self`.
83 pub fn as_constraint(&self) -> CapsConstraint<'static> {
84 match (self.direction, &self.caps) {
85 (PadDirection::Source, PadCaps::Fixed(s)) => CapsConstraint::Produces(s.clone()),
86 (PadDirection::Sink, PadCaps::Fixed(s)) => CapsConstraint::Accepts(s.clone()),
87 (PadDirection::Sink, PadCaps::Any) => CapsConstraint::AcceptsAny,
88 (PadDirection::Source, PadCaps::Any) => {
89 CapsConstraint::Produces(CapsSet::from_alternatives(Vec::new()))
90 }
91 }
92 }
93}
94
95/// Element types that publish static pad templates. The query is an
96/// associated function (no `&self`), so a tool inspects a *type* without
97/// constructing it: `<FakeSink as PadTemplates>::pad_templates()`.
98pub trait PadTemplates {
99 /// Every pad this element type exposes, in declaration order.
100 fn pad_templates() -> Vec<PadTemplate>;
101
102 /// The first pad template in the given direction, if any.
103 fn pad_template(direction: PadDirection) -> Option<PadTemplate> {
104 Self::pad_templates()
105 .into_iter()
106 .find(|t| t.direction == direction)
107 }
108}
109
110/// Pre-instantiation solver query: the caps an element whose source pad is
111/// `producer` would fixate to feeding an element whose sink pad is
112/// `consumer`, without constructing either. Returns the fixated caps, or a
113/// structured [`NegotiationFailure`]:
114/// - `EmptyLink` — the pads' caps don't intersect (genuinely incompatible).
115/// - `Unfixable` — the shapes *are* compatible, but a field is still open
116/// on both sides (common for static templates that leave geometry or
117/// framerate `Any`); a concrete value is chosen at instance time. Use
118/// [`types_can_link`] when "are they compatible?" is the question.
119/// - `EndpointShapeMismatch` — the directions were swapped.
120pub fn pad_link(
121 producer: &PadTemplate,
122 consumer: &PadTemplate,
123) -> Result<Caps, NegotiationFailure> {
124 let producer_c = producer.as_constraint();
125 let consumer_c = consumer.as_constraint();
126 solve_linear(&[&producer_c, &consumer_c])?
127 .into_iter()
128 .last()
129 .ok_or(NegotiationFailure::Degenerate)
130}
131
132/// Convenience tooling query: can a `A`-typed element's source pad feed a
133/// `B`-typed element's sink pad? `false` if either lacks the needed pad or
134/// the caps don't intersect. An `Unfixable` result counts as compatible:
135/// static templates routinely leave geometry / framerate open, and that
136/// only resolves at instance time, not at type-compatibility time. Use
137/// [`pad_link`] when you need the fixated caps or the precise failure.
138pub fn types_can_link<A, B>() -> bool
139where
140 A: PadTemplates,
141 B: PadTemplates,
142{
143 match (
144 A::pad_template(PadDirection::Source),
145 B::pad_template(PadDirection::Sink),
146 ) {
147 (Some(producer), Some(consumer)) => matches!(
148 pad_link(&producer, &consumer),
149 Ok(_) | Err(NegotiationFailure::Unfixable { .. })
150 ),
151 _ => false,
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use crate::caps::{Dim, Rate, RawVideoFormat};
159
160 /// Fully concrete caps (a real producer names every field).
161 fn fixed(format: RawVideoFormat, w: u32, h: u32) -> Caps {
162 Caps::RawVideo {
163 format,
164 width: Dim::Fixed(w),
165 height: Dim::Fixed(h),
166 framerate: Rate::Fixed(30 << 16),
167 interlace: crate::Interlace::Any,
168 }
169 }
170
171 /// A format at any geometry / framerate (a broad static template).
172 fn any_geom(format: RawVideoFormat) -> Caps {
173 Caps::RawVideo {
174 format,
175 width: Dim::Any,
176 height: Dim::Any,
177 framerate: Rate::Any,
178 interlace: crate::Interlace::Any,
179 }
180 }
181
182 /// Produces RGBA at any geometry (static superset, like `VideoTestSrc`).
183 struct RgbaSource;
184 impl PadTemplates for RgbaSource {
185 fn pad_templates() -> Vec<PadTemplate> {
186 alloc::vec![PadTemplate::source(CapsSet::one(any_geom(
187 RawVideoFormat::Rgba8
188 )))]
189 }
190 }
191
192 /// Accepts only NV12.
193 struct Nv12Sink;
194 impl PadTemplates for Nv12Sink {
195 fn pad_templates() -> Vec<PadTemplate> {
196 alloc::vec![PadTemplate::sink(CapsSet::one(any_geom(
197 RawVideoFormat::Nv12
198 )))]
199 }
200 }
201
202 /// Accepts anything.
203 struct AnySink;
204 impl PadTemplates for AnySink {
205 fn pad_templates() -> Vec<PadTemplate> {
206 alloc::vec![PadTemplate::sink_any()]
207 }
208 }
209
210 #[test]
211 fn compatible_pads_link_to_fixated_caps() {
212 // A concrete producer feeding a broad sink fixates to the producer's caps.
213 let producer = PadTemplate::source(CapsSet::one(fixed(RawVideoFormat::Rgba8, 1280, 720)));
214 let consumer = PadTemplate::sink(CapsSet::one(any_geom(RawVideoFormat::Rgba8)));
215 let caps = pad_link(&producer, &consumer).expect("pads overlap");
216 assert_eq!(caps, fixed(RawVideoFormat::Rgba8, 1280, 720));
217 }
218
219 #[test]
220 fn disjoint_pads_report_empty_link() {
221 let producer = PadTemplate::source(CapsSet::one(any_geom(RawVideoFormat::Rgba8)));
222 let consumer = PadTemplate::sink(CapsSet::one(any_geom(RawVideoFormat::Nv12)));
223 assert!(
224 matches!(
225 pad_link(&producer, &consumer),
226 Err(NegotiationFailure::EmptyLink { .. })
227 ),
228 "RGBA producer cannot feed an NV12-only sink"
229 );
230 }
231
232 #[test]
233 fn wildcard_sink_accepts_any_producer() {
234 let producer = PadTemplate::source(CapsSet::one(fixed(RawVideoFormat::Rgba8, 640, 480)));
235 let caps = pad_link(&producer, &PadTemplate::sink_any()).expect("any sink accepts");
236 assert_eq!(caps, fixed(RawVideoFormat::Rgba8, 640, 480));
237 }
238
239 #[test]
240 fn types_can_link_uses_each_type_template() {
241 assert!(
242 types_can_link::<RgbaSource, AnySink>(),
243 "RGBA source -> any sink"
244 );
245 assert!(
246 !types_can_link::<RgbaSource, Nv12Sink>(),
247 "RGBA source -/-> NV12 sink"
248 );
249 // A source has no sink pad, so nothing can feed into it.
250 assert!(
251 !types_can_link::<RgbaSource, RgbaSource>(),
252 "source has no sink pad"
253 );
254 }
255}