1use alpha2_eval::FrameNeed;
2use alpha2_media::SourceId;
3use alpha2_render::{CacheEligibility, RenderCacheKey, RenderSize, RenderedTexture};
4use alpha2_time::MediaTime;
5
6use crate::schedule::frame_times;
7use crate::{
8 EngineError, EngineProject, FrameProvider, FrameRenderer, MissingFramePolicy, QualityProfile,
9};
10
11const MAX_PREFETCH_FRAMES: u32 = 120;
12
13#[derive(Debug)]
16pub struct PlaybackFrame<'a, I = RenderedTexture> {
17 pub requested_time: MediaTime,
19 pub presented_time: Option<MediaTime>,
21 pub texture: Option<&'a I>,
23 pub missing_sources: Vec<SourceId>,
25 pub repeated: bool,
27 pub unchanged: bool,
31}
32
33type ShownKey = (RenderSize, RenderCacheKey, u64);
36
37#[derive(Debug)]
45pub struct Player<I = RenderedTexture> {
46 quality: QualityProfile,
47 prefetch_frames: u32,
48 missing_frame_policy: MissingFramePolicy,
49 last_frame: Option<(MediaTime, I)>,
50 shown: Option<ShownKey>,
52}
53
54impl<I> Default for Player<I> {
55 fn default() -> Self {
56 Self {
57 quality: QualityProfile::preview(),
58 prefetch_frames: 2,
59 missing_frame_policy: MissingFramePolicy::HoldPrevious,
60 last_frame: None,
61 shown: None,
62 }
63 }
64}
65
66impl<I> Player<I> {
67 pub fn render_size(
73 &self,
74 project: &EngineProject,
75 ) -> Result<alpha2_render::RenderSize, EngineError> {
76 self.quality.render_size(&project.project().settings)
77 }
78
79 pub fn new(quality: QualityProfile) -> Result<Self, EngineError> {
84 quality.validate()?;
85 Ok(Self {
86 quality,
87 ..Self::default()
88 })
89 }
90
91 #[must_use]
94 pub const fn with_prefetch_frames(mut self, frames: u32) -> Self {
95 self.prefetch_frames = if frames > MAX_PREFETCH_FRAMES {
96 MAX_PREFETCH_FRAMES
97 } else {
98 frames
99 };
100 self
101 }
102
103 #[must_use]
105 pub const fn prefetch_frames(&self) -> u32 {
106 self.prefetch_frames
107 }
108
109 #[must_use]
111 pub const fn quality(&self) -> QualityProfile {
112 self.quality
113 }
114
115 #[must_use]
123 pub const fn with_missing_frame_policy(mut self, policy: MissingFramePolicy) -> Self {
124 self.missing_frame_policy = policy;
125 self
126 }
127
128 #[must_use]
130 pub const fn missing_frame_policy(&self) -> MissingFramePolicy {
131 self.missing_frame_policy
132 }
133
134 pub fn prefetch_needs(
139 &self,
140 project: &EngineProject,
141 time: MediaTime,
142 ) -> Result<Vec<FrameNeed>, EngineError> {
143 let rate = project.project().settings.frame_rate;
144 let first = rate
145 .frame_at_or_before(time)
146 .ok_or(EngineError::OutOfRange)?;
147 let mut needs = Vec::new();
148 for (_, frame_time) in
149 frame_times(rate, first, self.prefetch_frames.saturating_add(1), None)?
150 {
151 for need in project.frame_needs(frame_time)? {
152 if !needs.iter().any(|existing: &FrameNeed| {
153 existing.source_id == need.source_id && existing.media_time == need.media_time
154 }) {
155 needs.push(need);
156 }
157 }
158 }
159 Ok(needs)
160 }
161
162 pub fn render_at<'a, R: FrameRenderer<Image = I> + ?Sized>(
181 &'a mut self,
182 project: &EngineProject,
183 renderer: &R,
184 frames: &dyn FrameProvider,
185 time: MediaTime,
186 ) -> Result<PlaybackFrame<'a, I>, EngineError> {
187 let frame = project.evaluate(time)?;
188 crate::frame::check_frame_effects(renderer, &frame)?;
189 let size = self.quality.render_size(&project.project().settings)?;
190 renderer.set_texture_budget_bytes(self.quality.texture_budget_bytes);
191 renderer.set_quality_controls(
192 self.quality.max_filter_samples,
193 self.quality.motion_blur_subframes,
194 );
195 let built = renderer.build_frame_graph(size, &frame, frames)?;
196 if let Some(graph) = built.graph {
197 let key = match graph.output().map(|output| graph.cache_eligibility(output)) {
198 Some(Ok(CacheEligibility::Eligible)) => Some(
199 renderer
200 .graph_cache_key(&graph)
201 .map_err(alpha2_render::GraphError::into_render_error)?,
202 ),
203 _ => None,
204 };
205 let unchanged = self.last_frame.is_some()
206 && key.is_some_and(|key| self.shown == Some((size, key, renderer.content_epoch())));
207 if !unchanged {
208 let texture = renderer.render_frame_graph(&graph, &built.pending)?;
209 self.last_frame = Some((time, texture));
210 self.shown = key.map(|key| (size, key, renderer.content_epoch()));
212 }
213 let (presented_time, texture) =
214 self.last_frame.as_mut().ok_or(EngineError::OutOfRange)?;
215 *presented_time = time;
217 renderer.record_frame_outcome(true, false);
218 return Ok(PlaybackFrame {
219 requested_time: time,
220 presented_time: Some(*presented_time),
221 texture: Some(texture),
222 missing_sources: Vec::new(),
223 repeated: false,
224 unchanged,
225 });
226 }
227 self.shown = None;
229
230 let (presented_time, texture) = match self.missing_frame_policy {
231 MissingFramePolicy::HoldPrevious => self
232 .last_frame
233 .as_ref()
234 .map_or((None, None), |(time, texture)| (Some(*time), Some(texture))),
235 MissingFramePolicy::Wait => (None, None),
238 };
239 renderer.record_frame_outcome(texture.is_some(), texture.is_some());
240 Ok(PlaybackFrame {
241 requested_time: time,
242 presented_time,
243 texture,
244 missing_sources: built.report.missing_sources,
245 repeated: texture.is_some(),
246 unchanged: false,
247 })
248 }
249
250 pub fn reset(&mut self) {
252 self.last_frame = None;
253 self.shown = None;
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn prefetch_configuration_is_bounded() {
263 let player = Player::<RenderedTexture>::default().with_prefetch_frames(u32::MAX);
264 assert_eq!(player.prefetch_frames, MAX_PREFETCH_FRAMES);
265 }
266
267 #[test]
268 fn hold_previous_is_the_default_missing_frame_policy() {
269 assert_eq!(
270 Player::<RenderedTexture>::default().missing_frame_policy(),
271 MissingFramePolicy::HoldPrevious
272 );
273 }
274
275 #[test]
276 fn the_missing_frame_policy_is_configurable() {
277 let player = Player::<RenderedTexture>::default()
278 .with_missing_frame_policy(MissingFramePolicy::Wait);
279 assert_eq!(player.missing_frame_policy(), MissingFramePolicy::Wait);
280 assert_eq!(
281 player
282 .with_missing_frame_policy(MissingFramePolicy::HoldPrevious)
283 .missing_frame_policy(),
284 MissingFramePolicy::HoldPrevious
285 );
286 }
287
288 #[test]
289 fn configuring_the_policy_preserves_quality_and_prefetch() {
290 let player = Player::<RenderedTexture>::new(QualityProfile::export())
291 .expect("valid quality")
292 .with_prefetch_frames(7)
293 .with_missing_frame_policy(MissingFramePolicy::Wait);
294 assert_eq!(player.quality(), QualityProfile::export());
295 assert_eq!(player.prefetch_frames, 7);
296 assert_eq!(player.missing_frame_policy(), MissingFramePolicy::Wait);
297 }
298}