ff_preview/scene/compositor.rs
1//! Pluggable layer compositor for the preview runner.
2//!
3//! The runner composites each frame with the built-in CPU compositor
4//! ([`RealtimeComposer`](ff_filter::RealtimeComposer)). A caller that has a GPU
5//! compositor (which `ff-preview` cannot reach directly, since `ff-render` depends
6//! on `ff-preview`, not the reverse) can inject one through this seam: the runner
7//! tries the injected compositor first and falls back to the CPU path whenever it
8//! returns `None` (no GPU, an unsupported layer, or a GPU error). This is how the
9//! `avio` engine wires `ff-render` into the preview without a dependency cycle.
10
11use std::time::Duration;
12
13use ff_filter::{RealtimeLayer, XfadeTransition};
14use ff_format::VideoFrame;
15
16/// An external compositor the preview runner can use in place of its built-in CPU
17/// compositor. Implemented by `avio` over `ff-render`; see the module docs.
18pub trait PreviewCompositor: Send {
19 /// Composite `layers` (bottom to top, paired with each layer's decoded `rgba`
20 /// frame) into a single `rgba` frame at timeline time `t`, targeting the
21 /// `canvas` output size.
22 ///
23 /// Returns `Some((rgba, width, height))` on success, or `None` to fall back to
24 /// the runner's CPU compositor (an unsupported layer, no adapter, or a GPU
25 /// error). Returning `None` must never leave the runner in a bad state.
26 fn composite(
27 &mut self,
28 layers: &[(&RealtimeLayer, &VideoFrame)],
29 canvas: (u32, u32),
30 t: Duration,
31 ) -> Option<(Vec<u8>, u32, u32)>;
32
33 /// Blend the outgoing frame `a` into the incoming frame `b` at `progress`
34 /// (`0` = all `a`, `1` = all `b`) for the `xfade` `kind`, both packed RGBA of
35 /// `w * h * 4` bytes.
36 ///
37 /// Returns `Some(rgba)` on success, or `None` to leave the frame to the runner's
38 /// CPU `apply_xfade` — which is the answer for a kind the implementor does not
39 /// render, a missing adapter, a GPU error, and a kind it renders correctly but
40 /// slower. Declining must never leave the runner in a bad state.
41 ///
42 /// Defaults to `None`, so an implementor that only composites is unaffected.
43 ///
44 /// This sits beside `composite` rather than in a trait of its own because both
45 /// exist for the same reason — reaching `ff-render`, which depends on this crate —
46 /// and one injected object means one GPU context rather than two.
47 fn blend(
48 &mut self,
49 kind: XfadeTransition,
50 a: &[u8],
51 b: &[u8],
52 progress: f32,
53 w: u32,
54 h: u32,
55 ) -> Option<Vec<u8>> {
56 let _ = (kind, a, b, progress, w, h);
57 None
58 }
59
60 /// Drops whatever the implementor carries from one clip into the next.
61 ///
62 /// The runner calls this when playback crosses a clip boundary. It exists for a
63 /// **stateful** effect: motion blur accumulates an exposure trail across the
64 /// frames of one clip, and without a reset at the cut the outgoing clip's trail
65 /// bleeds into the incoming clip's first frame. The export path has always done
66 /// this; playback did not, which is what #1705 fixes.
67 ///
68 /// Defaults to a no-op, so an implementor that carries nothing across frames is
69 /// unaffected.
70 fn reset_effects(&mut self) {}
71}