1use alpha2_eval::FrameNeed;
2use alpha2_media::SourceId;
3use alpha2_render::{CacheEligibility, RenderCacheKey, RenderSize, RenderedTexture, Renderer};
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)]
15pub struct PlaybackFrame<'a> {
16 pub requested_time: MediaTime,
18 pub presented_time: Option<MediaTime>,
20 pub texture: Option<&'a RenderedTexture>,
22 pub missing_sources: Vec<SourceId>,
24 pub repeated: bool,
26 pub unchanged: bool,
30}
31
32type ShownKey = (RenderSize, RenderCacheKey, u64);
35
36#[derive(Debug)]
42pub struct Player {
43 quality: QualityProfile,
44 prefetch_frames: u32,
45 missing_frame_policy: MissingFramePolicy,
46 last_frame: Option<(MediaTime, RenderedTexture)>,
47 shown: Option<ShownKey>,
49}
50
51impl Default for Player {
52 fn default() -> Self {
53 Self {
54 quality: QualityProfile::preview(),
55 prefetch_frames: 2,
56 missing_frame_policy: MissingFramePolicy::HoldPrevious,
57 last_frame: None,
58 shown: None,
59 }
60 }
61}
62
63impl Player {
64 pub fn render_size(
70 &self,
71 project: &EngineProject,
72 ) -> Result<alpha2_render::RenderSize, EngineError> {
73 self.quality.render_size(&project.project().settings)
74 }
75
76 pub fn new(quality: QualityProfile) -> Result<Self, EngineError> {
81 quality.validate()?;
82 Ok(Self {
83 quality,
84 ..Self::default()
85 })
86 }
87
88 #[must_use]
91 pub const fn with_prefetch_frames(mut self, frames: u32) -> Self {
92 self.prefetch_frames = if frames > MAX_PREFETCH_FRAMES {
93 MAX_PREFETCH_FRAMES
94 } else {
95 frames
96 };
97 self
98 }
99
100 #[must_use]
102 pub const fn prefetch_frames(&self) -> u32 {
103 self.prefetch_frames
104 }
105
106 #[must_use]
108 pub const fn quality(&self) -> QualityProfile {
109 self.quality
110 }
111
112 #[must_use]
120 pub const fn with_missing_frame_policy(mut self, policy: MissingFramePolicy) -> Self {
121 self.missing_frame_policy = policy;
122 self
123 }
124
125 #[must_use]
127 pub const fn missing_frame_policy(&self) -> MissingFramePolicy {
128 self.missing_frame_policy
129 }
130
131 pub fn prefetch_needs(
136 &self,
137 project: &EngineProject,
138 time: MediaTime,
139 ) -> Result<Vec<FrameNeed>, EngineError> {
140 let rate = project.project().settings.frame_rate;
141 let first = rate
142 .frame_at_or_before(time)
143 .ok_or(EngineError::OutOfRange)?;
144 let mut needs = Vec::new();
145 for (_, frame_time) in
146 frame_times(rate, first, self.prefetch_frames.saturating_add(1), None)?
147 {
148 for need in project.frame_needs(frame_time)? {
149 if !needs.iter().any(|existing: &FrameNeed| {
150 existing.source_id == need.source_id && existing.media_time == need.media_time
151 }) {
152 needs.push(need);
153 }
154 }
155 }
156 Ok(needs)
157 }
158
159 pub fn render_at<'a>(
178 &'a mut self,
179 project: &EngineProject,
180 renderer: &Renderer,
181 frames: &dyn FrameProvider,
182 time: MediaTime,
183 ) -> Result<PlaybackFrame<'a>, EngineError> {
184 let frame = project.evaluate(time)?;
185 crate::frame::check_frame_effects(renderer, &frame)?;
186 let size = self.quality.render_size(&project.project().settings)?;
187 renderer.set_texture_budget_bytes(self.quality.texture_budget_bytes);
188 renderer.set_quality_controls(
189 self.quality.max_filter_samples,
190 self.quality.motion_blur_subframes,
191 );
192 let built = renderer.build_frame_graph(size, &frame, frames)?;
193 if let Some(graph) = built.graph {
194 let key = match graph.output().map(|output| graph.cache_eligibility(output)) {
195 Some(Ok(CacheEligibility::Eligible)) => Some(
196 renderer
197 .graph_cache_key(&graph)
198 .map_err(alpha2_render::GraphError::into_render_error)?,
199 ),
200 _ => None,
201 };
202 let unchanged = self.last_frame.is_some()
203 && key.is_some_and(|key| self.shown == Some((size, key, renderer.content_epoch())));
204 if !unchanged {
205 let texture = renderer.render_graph(&graph, &built.pending)?;
206 self.last_frame = Some((time, texture));
207 self.shown = key.map(|key| (size, key, renderer.content_epoch()));
209 }
210 let (presented_time, texture) =
211 self.last_frame.as_mut().ok_or(EngineError::OutOfRange)?;
212 *presented_time = time;
214 renderer.record_frame_outcome(true, false);
215 return Ok(PlaybackFrame {
216 requested_time: time,
217 presented_time: Some(*presented_time),
218 texture: Some(texture),
219 missing_sources: Vec::new(),
220 repeated: false,
221 unchanged,
222 });
223 }
224 self.shown = None;
226
227 let (presented_time, texture) = match self.missing_frame_policy {
228 MissingFramePolicy::HoldPrevious => self
229 .last_frame
230 .as_ref()
231 .map_or((None, None), |(time, texture)| (Some(*time), Some(texture))),
232 MissingFramePolicy::Wait => (None, None),
235 };
236 renderer.record_frame_outcome(texture.is_some(), texture.is_some());
237 Ok(PlaybackFrame {
238 requested_time: time,
239 presented_time,
240 texture,
241 missing_sources: built.report.missing_sources,
242 repeated: texture.is_some(),
243 unchanged: false,
244 })
245 }
246
247 pub fn reset(&mut self) {
249 self.last_frame = None;
250 self.shown = None;
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn prefetch_configuration_is_bounded() {
260 let player = Player::default().with_prefetch_frames(u32::MAX);
261 assert_eq!(player.prefetch_frames, MAX_PREFETCH_FRAMES);
262 }
263
264 #[test]
265 fn hold_previous_is_the_default_missing_frame_policy() {
266 assert_eq!(
267 Player::default().missing_frame_policy(),
268 MissingFramePolicy::HoldPrevious
269 );
270 }
271
272 #[test]
273 fn the_missing_frame_policy_is_configurable() {
274 let player = Player::default().with_missing_frame_policy(MissingFramePolicy::Wait);
275 assert_eq!(player.missing_frame_policy(), MissingFramePolicy::Wait);
276 assert_eq!(
277 player
278 .with_missing_frame_policy(MissingFramePolicy::HoldPrevious)
279 .missing_frame_policy(),
280 MissingFramePolicy::HoldPrevious
281 );
282 }
283
284 #[test]
285 fn configuring_the_policy_preserves_quality_and_prefetch() {
286 let player = Player::new(QualityProfile::export())
287 .expect("valid quality")
288 .with_prefetch_frames(7)
289 .with_missing_frame_policy(MissingFramePolicy::Wait);
290 assert_eq!(player.quality(), QualityProfile::export());
291 assert_eq!(player.prefetch_frames, 7);
292 assert_eq!(player.missing_frame_policy(), MissingFramePolicy::Wait);
293 }
294}