Skip to main content

telar_renderer_core/
renderer.rs

1use crate::{Color, DrawCommand, FontConfig, RendererError};
2
3pub trait RenderBackend {
4    /// Begin a new frame. Note: `scale_factor` and `generation` may be ignored by backends that receive pre-scaled commands (see `SoftwareRenderer::begin_frame`).
5    fn begin_frame(
6        &mut self,
7        width: u32,
8        height: u32,
9        scale_factor: f32,
10        generation: u64,
11    ) -> Result<(), RendererError>;
12    /// Process and present all draw commands for this frame. Must be called exactly once per frame after `begin_frame`.
13    fn render_frame(
14        &mut self,
15        commands: &[DrawCommand],
16        clear_color: Option<Color>,
17    ) -> Result<(), RendererError>;
18    /// The most recently rendered frame as premultiplied RGBA8888 (`[R, G, B, A]` per pixel, row-major), if
19    /// this backend renders to an offscreen target. Windowed/on-screen backends present directly and return
20    /// `None`. Used to read back pixels from a headless render pass.
21    fn read_rgba(&self) -> Option<Vec<u8>> {
22        None
23    }
24
25    /// Called once on the thread that will drive this backend, before its first frame.
26    ///
27    /// A backend built on the UI thread and then moved to a render thread has to re-establish whatever
28    /// per-thread state its constructor set up there. The software rasteriser keeps its glyph shaper and
29    /// shadow caches in a thread-local, so without this the render thread finds an empty slot and builds a
30    /// default one — with no font config. On desktop that silently falls back to system fonts; on Android
31    /// there are none to find and cosmic-text aborts the process with "no default font found".
32    fn bind_to_render_thread(&mut self) {}
33
34    /// How long the render thread should go without a frame before calling
35    /// [`sweep_idle_caches`](Self::sweep_idle_caches). `None` (the default) means never.
36    ///
37    /// Exists because a backend's caches may be thread-local: they then belong to the render thread, and
38    /// nothing on the UI thread can reach them — so the sweep has to be driven from the thread that owns
39    /// them, and only that thread knows when it has been idle.
40    fn idle_sweep_after(&self) -> Option<std::time::Duration> {
41        None
42    }
43
44    /// Drops cache entries no frame has asked for within their idle horizon. Called once per idle stretch,
45    /// on the render thread, after [`idle_sweep_after`](Self::idle_sweep_after) has elapsed with no frame.
46    fn sweep_idle_caches(&mut self) {}
47
48    /// Whether this backend applies `begin_frame`'s `scale_factor` itself — the hardware path folds it into
49    /// the shader's transform. A backend that returns `false` (the default, and what the software rasteriser
50    /// does) must be handed commands already scaled into physical pixels, which is why the frame pipeline
51    /// runs [`ScaleScratch`](crate::ScaleScratch) for it.
52    fn applies_scale_factor(&self) -> bool {
53        false
54    }
55}
56
57// Lets an installed renderer travel the frame pipeline, which is generic over `R: RenderBackend + Send` so it can own a concrete backend and hand it back on join.
58impl RenderBackend for Box<dyn RenderBackend + Send> {
59    fn begin_frame(
60        &mut self,
61        width: u32,
62        height: u32,
63        scale_factor: f32,
64        generation: u64,
65    ) -> Result<(), RendererError> {
66        (**self).begin_frame(width, height, scale_factor, generation)
67    }
68
69    fn render_frame(
70        &mut self,
71        commands: &[DrawCommand],
72        clear_color: Option<Color>,
73    ) -> Result<(), RendererError> {
74        (**self).render_frame(commands, clear_color)
75    }
76
77    fn read_rgba(&self) -> Option<Vec<u8>> {
78        (**self).read_rgba()
79    }
80
81    fn bind_to_render_thread(&mut self) {
82        (**self).bind_to_render_thread()
83    }
84
85    fn idle_sweep_after(&self) -> Option<std::time::Duration> {
86        (**self).idle_sweep_after()
87    }
88
89    fn sweep_idle_caches(&mut self) {
90        (**self).sweep_idle_caches()
91    }
92
93    fn applies_scale_factor(&self) -> bool {
94        (**self).applies_scale_factor()
95    }
96}
97
98/// What a renderer is built from, beyond the surface it draws on.
99pub struct RendererBuild<'a> {
100    /// The faces the app's text is shaped with — the same set the layout-time measurer was configured with, since
101    /// measure and draw have to agree on what a string is as wide as.
102    pub fonts: &'a FontConfig,
103    /// Whether the app asked for a transparent surface. A renderer is *built* for one or the other.
104    pub transparent: bool,
105}
106
107/// Builds the renderer for a surface — the seam an out-of-tree frontend installs to draw Telar's frames itself.
108///
109/// Generic over the window type because that is the platform's business: whoever brings a `Platform` brings the
110/// window this draws on. The backend is boxed and `Send` so the frame pipeline can move it to its own thread.
111pub trait RendererFactory<W>: 'static {
112    fn build(
113        &self,
114        window: &W,
115        build: RendererBuild<'_>,
116    ) -> Result<Box<dyn RenderBackend + Send>, RendererError>;
117}