Skip to main content

ff_render/
lib.rs

1//! # ff-render
2//!
3//! GPU compositing and effects pipeline for video, built on [wgpu].
4//!
5//! `ff-render` sits above `ff-preview` in the crate stack and implements
6//! [`ff_preview::FrameSink`], so a [`RenderGraph`] can be attached to a
7//! [`ff_preview::PlayerRunner`] as an opt-in [`GpuFrameSink`]. It is not the
8//! default preview compositor: avio composites preview on the CPU today, and
9//! making GPU compositing the default across avio is tracked separately in
10//! issue #1365.
11//!
12//! ## Feature flags
13//!
14//! | Feature | Description | Default |
15//! |---------|-------------|---------|
16//! | `wgpu`  | GPU processing via wgpu (Metal / Vulkan / DX12 / WebGPU) | no |
17//!
18//! Without `wgpu` only the CPU fallback path is available via
19//! [`RenderGraph::process_cpu`].
20//!
21//! ## Usage — wiring to `PlayerRunner`
22//!
23//! ```ignore
24//! use std::sync::Arc;
25//!
26//! use ff_preview::{PreviewPlayer, RgbaSink};
27//! use ff_render::context::RenderContext;
28//! use ff_render::graph::RenderGraph;
29//! use ff_render::nodes::ColorGradeNode;
30//! use ff_render::sink::GpuFrameSink;
31//!
32//! # #[tokio::main]
33//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
34//! // 1. Initialise the GPU device (headless — no window required).
35//! let ctx = Arc::new(RenderContext::init().await?);
36//!
37//! // 2. Build a render graph: apply a gentle brightness boost.
38//! let graph = RenderGraph::new(Arc::clone(&ctx)).push(ColorGradeNode {
39//!     brightness: 0.1,
40//!     ..Default::default()
41//! });
42//!
43//! // 3. Open the player, attach the GPU sink, and run on a dedicated thread.
44//! let downstream = RgbaSink::new();
45//! let handle = downstream.frame_handle();
46//! let (mut runner, _player_handle) = PreviewPlayer::open("clip.mp4")?.split();
47//! runner.set_sink(Box::new(GpuFrameSink::new(graph, Box::new(downstream))));
48//!
49//! std::thread::spawn(move || runner.run());
50//!
51//! // 4. Retrieve the latest processed frame from any thread.
52//! if let Some(frame) = handle.lock().unwrap().as_ref() {
53//!     println!("frame: {}×{} pts={:?}", frame.width, frame.height, frame.pts);
54//! }
55//! # Ok(())
56//! # }
57//! ```
58
59#![warn(clippy::all)]
60#![warn(clippy::pedantic)]
61
62pub mod error;
63pub mod graph;
64pub mod nodes;
65pub mod sink;
66
67#[cfg(feature = "wgpu")]
68pub mod compositor;
69#[cfg(feature = "wgpu")]
70pub mod context;
71
72// ── Top-level re-exports ─────────────────────────────────────────────────────
73
74#[cfg(feature = "wgpu")]
75pub use compositor::{Compositor, FrameLayer, LayerTransform};
76pub use error::RenderError;
77pub use ff_format::{ErrorSeverity, MediaError};
78pub use graph::RenderGraph;
79pub use nodes::{
80    AlphaMatteNode, BlendMode, BlendModeNode, ChromaKeyNode, ColorGradeNode, CrossfadeNode,
81    LumaMaskNode, OverlayNode, RenderNodeCpu, ScaleAlgorithm, ScaleNode, ShapeMaskNode,
82    TransformNode, YuvFormat, YuvUploadNode,
83};
84pub use sink::GpuFrameSink;
85
86#[cfg(feature = "wgpu")]
87pub use context::RenderContext;
88#[cfg(feature = "wgpu")]
89pub use nodes::RenderNode;
90#[cfg(feature = "wgpu")]
91pub use sink::TextureHandle;