Skip to main content

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}