Skip to main content

alpha2_eval/
frame.rs

1use crate::{EffectParamValue, sample_effect_param};
2use alpha2_media::SourceId;
3use alpha2_model::{
4    BlendSpace, ClipBlendMode, ClipEffect, ClipId, EffectResourceId, Generator, Matte, Rgb,
5    ShapeMask, TextStyle, TrackId, Vec2,
6};
7use alpha2_time::MediaTime;
8
9/// A timestamp-specific composition description with no GPU handles.
10///
11/// Identities and authored effects are borrowed from the [`crate::ActiveIndex`] (`'a`), so
12/// evaluating a frame copies none of them.
13#[derive(Clone, Debug, PartialEq)]
14pub struct FrameDesc<'a> {
15    /// Timestamp evaluated on the project timeline.
16    pub time: MediaTime,
17    /// Opaque color under every visual layer, or `None` for transparent black (alpha output).
18    pub background: Option<Rgb>,
19    /// The space in which blend modes are evaluated.
20    pub blend_space: BlendSpace,
21    /// Active visual clips in composition order.
22    pub visual: Vec<ActiveClip<'a>>,
23    /// Active audio clips in mix order.
24    pub audio: Vec<ActiveClip<'a>>,
25    /// Active two-input transitions in track order.
26    pub transitions: Vec<ActiveTransition<'a>>,
27    /// The cue each caption track shows, in track order.
28    pub captions: Vec<ActiveCaption<'a>>,
29}
30
31impl FrameDesc<'_> {
32    /// Whether the frame draws `clip`'s picture. A matte ([`ActiveClip::is_matte`]) is drawn
33    /// only while an active clip names it.
34    #[must_use]
35    pub fn draws(&self, clip: &ActiveClip<'_>) -> bool {
36        !clip.is_matte
37            || self.visual.iter().any(|other| {
38                other
39                    .matte
40                    .is_some_and(|matte| &matte.source == clip.clip_id)
41            })
42    }
43}
44
45/// The cue one caption track shows at a timestamp.
46#[derive(Clone, Copy, Debug, PartialEq)]
47pub struct ActiveCaption<'a> {
48    /// The caption track.
49    pub track_id: &'a TrackId,
50    /// The cue's text.
51    pub text: &'a alpha2_model::TextContent,
52    /// Time since the cue started, which keys its style's animators.
53    pub local_time: alpha2_time::MediaTime,
54    /// The track style with the cue's (or else its speaker's) named style patch applied.
55    pub style: &'a TextStyle,
56    /// How many [`FrameDesc::visual`] clips lie under the caption by track order, counting clips
57    /// the frame does not draw ([`FrameDesc::draws`]) so an undrawn clip never shifts a caption.
58    pub layers_below: usize,
59}
60
61/// A visible or audible clip mapped to its source timestamp.
62#[derive(Clone, Copy, Debug, PartialEq)]
63pub struct ActiveClip<'a> {
64    /// Active clip identity.
65    pub clip_id: &'a ClipId,
66    /// Media source needed for this clip.
67    pub source_id: &'a SourceId,
68    /// Track containing the clip.
69    pub track_id: &'a TrackId,
70    /// Source timestamp selected by the clip's time map.
71    pub source_time: MediaTime,
72    /// Evaluated visual presentation, present only for visual tracks.
73    pub visual: Option<EvaluatedVisual>,
74    /// The composition that builds the clip's picture; `None` shows the source as is.
75    pub composition: Option<crate::ActiveComposition<'a>>,
76    /// Artistic blend operation used by visual composition.
77    pub blend_mode: ClipBlendMode,
78    /// Whether a runtime may keep and reuse the clip's rendered picture.
79    pub cache: alpha2_model::CacheIntent,
80    /// Shape masks in order. Evaluate outlines with [`crate::evaluate_geometry`] and feathers with
81    /// [`crate::sample_scalar`] at the frame's time.
82    pub masks: &'a [ShapeMask],
83    /// The clip whose picture sets where this one is visible.
84    pub matte: Option<&'a Matte>,
85    /// Whether this clip is another clip's matte, drawn only into that matte.
86    pub is_matte: bool,
87    /// Generated source content, drawn at `source_time` with no host frame.
88    pub generator: Option<&'a Generator>,
89    /// Planned shapes or layout generator tree, to evaluate at `source_time`.
90    pub plan: Option<&'a crate::GeneratorPlan>,
91    /// For a composition source, its composition at the clip's source time (no host frame).
92    pub source_composition: Option<crate::ActiveComposition<'a>>,
93}
94
95impl ActiveClip<'_> {
96    /// Whether the clip draws its own host-supplied source frame.
97    #[must_use]
98    pub fn needs_source(&self) -> bool {
99        self.generator.is_none()
100            && self.source_composition.is_none()
101            && self
102                .composition
103                .is_none_or(|composition| composition.plan().uses_clip())
104    }
105}
106
107/// Visual properties evaluated at one timeline timestamp.
108#[derive(Copy, Clone, Debug, PartialEq)]
109pub struct EvaluatedVisual {
110    /// Normalized output translation.
111    pub position: Vec2,
112    /// Normalized source anchor.
113    pub anchor: Vec2,
114    /// Per-axis scale.
115    pub scale: Vec2,
116    /// Clockwise rotation in degrees.
117    pub rotation_degrees: f32,
118    /// Opacity from zero to one.
119    pub opacity: f32,
120}
121
122/// A source-frame request emitted for host prefetch and decode scheduling.
123#[derive(Clone, Debug, PartialEq, Eq)]
124pub struct FrameNeed {
125    /// Clip consumer that owns the sequential decoder cursor.
126    ///
127    /// Multiple clips may read the same source at unrelated timestamps. Hosts
128    /// must not make those clips share one seek position.
129    pub clip_id: ClipId,
130    /// The composition step reading the source, if not the clip itself: its
131    /// [`crate::Step::node`] path, prefixed with `/` for steps of a composition source. Each
132    /// (clip, reader) pair keeps its own decoder cursor.
133    pub reader: Option<alpha2_model::NodeId>,
134    /// Source to decode or retrieve.
135    pub source_id: SourceId,
136    /// Timestamp requested on the source timeline.
137    pub media_time: MediaTime,
138}
139
140/// A transition evaluated at one timeline timestamp.
141#[derive(Clone, Copy, Debug, PartialEq)]
142pub struct ActiveTransition<'a> {
143    /// Clip sampled at progress zero.
144    pub from_clip: &'a ClipId,
145    /// Clip sampled at progress one.
146    pub to_clip: &'a ClipId,
147    /// Versioned shader invocation, with parameters sampled at the frame's time.
148    pub effect: ActiveEffect<'a>,
149    /// Eased progress in the inclusive unit interval.
150    pub progress: f32,
151}
152
153/// One effect invocation at one timestamp (timeline time for transitions, composition time for
154/// composition steps).
155#[derive(Clone, Copy, Debug, PartialEq)]
156pub struct ActiveEffect<'a> {
157    effect: &'a ClipEffect,
158    time: MediaTime,
159}
160
161impl<'a> ActiveEffect<'a> {
162    /// The invocation `effect` sampled at `time`.
163    #[must_use]
164    pub const fn new(effect: &'a ClipEffect, time: MediaTime) -> Self {
165        Self { effect, time }
166    }
167
168    /// Stable effect definition identifier.
169    #[must_use]
170    pub fn id(&self) -> &'a str {
171        &self.effect.id
172    }
173
174    /// Exact effect definition version.
175    #[must_use]
176    pub const fn version(&self) -> u32 {
177        self.effect.version
178    }
179
180    /// Every authored parameter sampled at this timestamp. Omitted ones use definition defaults.
181    pub fn params(&self) -> impl Iterator<Item = (&'a str, EffectParamValue)> + 'a {
182        let time = self.time;
183        self.effect
184            .params
185            .iter()
186            .map(move |(name, animation)| (name.as_str(), sample_effect_param(animation, time)))
187    }
188
189    /// One parameter sampled at this timestamp, if the invocation sets it.
190    #[must_use]
191    pub fn param(&self, name: &str) -> Option<EffectParamValue> {
192        self.effect
193            .params
194            .get(name)
195            .map(|animation| sample_effect_param(animation, self.time))
196    }
197
198    /// Named external resources matched against the effect definition.
199    pub fn resources(&self) -> impl Iterator<Item = (&'a str, &'a EffectResourceId)> + 'a {
200        self.effect
201            .resources
202            .iter()
203            .map(|(name, id)| (name.as_str(), id))
204    }
205
206    /// The authored invocation, with its parameters unevaluated.
207    #[must_use]
208    pub const fn authored(&self) -> &'a ClipEffect {
209        self.effect
210    }
211}