Skip to main content

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}