Skip to main content

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}