Skip to main content

oxideav_scene/
render.rs

1//! Renderer traits + a stub implementation.
2//!
3//! The real renderer will land as a separate crate (probably
4//! `oxideav-scene-render`) and depend on the rasteriser + text
5//! shaper. This crate defines the trait surface so downstream code
6//! can write against it today.
7
8use oxideav_core::{Error, Result};
9
10use crate::duration::TimeStamp;
11use crate::ops::ExportOp;
12use crate::scene::Scene;
13
14/// One output tick.
15#[derive(Clone, Debug, Default)]
16pub struct RenderedFrame {
17    /// Optional reconstructed video frame. `None` for audio-only
18    /// intervals or for exporters that don't emit raster.
19    pub video: Option<oxideav_core::VideoFrame>,
20    /// Audio samples for the interval since the previous
21    /// `sample_at` call, at the scene's `sample_rate`. Interleaved
22    /// f32. Always valid (silent if no cue fired).
23    pub audio: Vec<f32>,
24    /// Structured export ops (PDF runs, filter-format operators, …).
25    pub operations: Vec<ExportOp>,
26}
27
28/// Driver trait — the outer render loop.
29pub trait SceneRenderer {
30    /// Prepare internal state for a fresh render. Called before the
31    /// first `render_at` on a new scene or after `seek`.
32    fn prepare(&mut self, scene: &Scene) -> Result<()>;
33
34    /// Render the scene at timestamp `t`. `t` must be monotonically
35    /// non-decreasing between consecutive calls unless `seek` is
36    /// called in between.
37    fn render_at(&mut self, scene: &Scene, t: TimeStamp) -> Result<RenderedFrame>;
38
39    /// Jump to `t` — invalidates any per-object state the renderer
40    /// cached from the previous position.
41    fn seek(&mut self, t: TimeStamp) -> Result<()>;
42}
43
44/// Per-object sampler — one instance per `SceneObject`. Implementations
45/// hold the object's decoder / glyph cache / bitmap handle and emit
46/// the concrete pixel contribution for a given timestamp. Plug into a
47/// [`SceneRenderer`] by registering a factory keyed on the object's
48/// `ObjectKind` discriminant.
49pub trait SceneSampler {
50    /// Sample at time `t`. Returns `None` if the object has nothing
51    /// to emit at this timestamp (e.g. a `Video` object whose next
52    /// keyframe is still in the future).
53    fn sample(&mut self, t: TimeStamp) -> Result<Option<SampleOutput>>;
54
55    /// Natural size of the object before `Transform` is applied. Used
56    /// for aspect-ratio-preserving layout in groups.
57    fn natural_size(&self) -> (f32, f32);
58}
59
60/// What a [`SceneSampler`] hands back. Video frames go into the
61/// compositor; text / raw ops flow through to the export pipeline.
62#[non_exhaustive]
63#[derive(Clone, Debug)]
64pub enum SampleOutput {
65    Frame(oxideav_core::VideoFrame),
66    Audio(Vec<f32>),
67    /// Carry-through for vector / structured exports (PDF).
68    StructuredOp(ExportOp),
69}
70
71/// Placeholder implementation. Every call returns
72/// `Error::Unsupported` — included so downstream code can compile
73/// against the trait today.
74#[derive(Default)]
75pub struct StubRenderer;
76
77impl SceneRenderer for StubRenderer {
78    fn prepare(&mut self, _scene: &Scene) -> Result<()> {
79        Err(Error::unsupported(
80            "oxideav-scene: renderer is a scaffold — real renderer lands as a separate crate",
81        ))
82    }
83
84    fn render_at(&mut self, _scene: &Scene, _t: TimeStamp) -> Result<RenderedFrame> {
85        Err(Error::unsupported(
86            "oxideav-scene: renderer is a scaffold — real renderer lands as a separate crate",
87        ))
88    }
89
90    fn seek(&mut self, _t: TimeStamp) -> Result<()> {
91        Err(Error::unsupported(
92            "oxideav-scene: renderer is a scaffold — real renderer lands as a separate crate",
93        ))
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn stub_renderer_returns_unsupported() {
103        let scene = Scene::default();
104        let mut r = StubRenderer;
105        let err = r.prepare(&scene).unwrap_err();
106        assert!(matches!(err, Error::Unsupported(_)));
107        let err = r.render_at(&scene, 0).unwrap_err();
108        assert!(matches!(err, Error::Unsupported(_)));
109    }
110}