oxframe_render/lib.rs
1//! Renderers turn a [`SceneGraph`] into an RGBA image.
2//!
3//! > **Status:** oxframe is under very active development. The API is not stable yet and
4//! > may change from version to version. Pin the exact version in your Cargo.toml and check
5//! > the [changelog](https://github.com/oxygenframe/oxframe-sdk/blob/main/CHANGELOG.md)
6//! > before upgrading.
7//!
8//! [`CpuRenderer`] (tiny-skia) is the reference backend and the `--cpu`
9//! mode for machines without a GPU; `GpuRenderer` (vello, feature `gpu`) is
10//! the fast path. Both consume the same [`oxframe_2d::DrawItem`] list and pass
11//! the same golden tests within a ΔE tolerance.
12
13#![warn(missing_docs)]
14
15use kurbo::Affine;
16use oxframe_2d::{DrawItem, Primitive, SceneGraph};
17use oxframe_core::Color;
18pub use oxframe_core::Image;
19
20#[cfg(feature = "cpu")]
21mod blur;
22#[cfg(feature = "cpu")]
23mod cpu;
24#[cfg(feature = "cpu")]
25pub use cpu::CpuRenderer;
26
27#[cfg(feature = "gpu")]
28mod gpu;
29#[cfg(feature = "gpu")]
30pub use gpu::{GpuError, GpuRenderer};
31
32/// Size and background of one render call.
33#[derive(Debug, Clone, Copy)]
34pub struct RenderOptions {
35 /// Output size; the scene is scaled uniformly to fit and centered.
36 pub width: u32,
37 /// Output height in pixels.
38 pub height: u32,
39 /// Color drawn under the scene.
40 pub background: Color,
41}
42
43/// Draws a scene graph into an image.
44pub trait Renderer {
45 /// Draws the current state of `graph` and returns the picture.
46 ///
47 /// A renderer that presents straight to a surface may return an empty
48 /// image after showing the frame. Transitions need the pixels, so they
49 /// call [`render_pixels`](Self::render_pixels) and then
50 /// [`present_image`](Self::present_image).
51 fn render(&mut self, graph: &SceneGraph, options: &RenderOptions) -> Image;
52
53 /// Draws `items`, a part of `graph`'s draw list, and returns the pixels
54 /// without presenting. `graph` gives the scene size.
55 ///
56 /// The engine calls this for a frame where a shader reads drawn pixels
57 /// (`lens_drops`, an effect): it draws the list in parts and runs the
58 /// shader between them.
59 fn render_list(
60 &mut self,
61 graph: &SceneGraph,
62 items: &[DrawItem],
63 options: &RenderOptions,
64 ) -> Image;
65
66 /// Draws `graph` and returns the pixels, without presenting.
67 ///
68 /// The default is [`render`](Self::render), which already returns pixels.
69 fn render_pixels(&mut self, graph: &SceneGraph, options: &RenderOptions) -> Image {
70 self.render(graph, options)
71 }
72
73 /// Shows a frame that was blended from pixels. The default does nothing,
74 /// because [`render`](Self::render) already showed the frame.
75 fn present_image(&mut self, _image: &Image) {}
76}
77
78/// Ids of the images `items` draw. A renderer keeps its decoded copies of
79/// these and drops the rest, so a shader that paints a new image every
80/// frame does not grow the cache.
81#[cfg(any(feature = "cpu", feature = "gpu"))]
82pub(crate) fn image_ids(items: &[DrawItem]) -> std::collections::BTreeSet<u64> {
83 items
84 .iter()
85 .filter_map(|item| match &item.prim {
86 Primitive::Image { image, .. } => Some(image.id),
87 _ => None,
88 })
89 .collect()
90}
91
92/// Maps scene coordinates (origin at the center, y down) to output pixels.
93pub fn scene_to_pixels(scene: (u32, u32), out: (u32, u32)) -> Affine {
94 let (sw, sh) = (f64::from(scene.0), f64::from(scene.1));
95 let (ow, oh) = (f64::from(out.0), f64::from(out.1));
96 let k = (ow / sw).min(oh / sh);
97 Affine::translate((ow / 2.0, oh / 2.0)) * Affine::scale(k)
98}