Skip to main content

rlvgl_core/
renderer.rs

1//! Rendering interface used by widgets.
2//!
3//! Implementors of this trait can target displays, off-screen buffers or
4//! simulator windows.
5
6use crate::cmd::CommandList;
7use crate::raster::{self, CoverageSink, Obb};
8use crate::widget::{Color, Rect};
9
10/// Target-agnostic drawing interface.
11///
12/// Renderers are supplied to widgets during the draw phase. Implementations
13/// may target a physical display, an off-screen buffer or a simulator window.
14pub trait Renderer {
15    /// Fill the given rectangle with a solid color.
16    fn fill_rect(&mut self, rect: Rect, color: Color);
17
18    /// Draw UTF‑8 text with its baseline anchored at the provided position using the color.
19    fn draw_text(&mut self, position: (i32, i32), text: &str, color: Color);
20
21    /// Blend a rectangle onto the target, honoring the alpha channel of `color`
22    /// for source-over compositing.
23    ///
24    /// The default implementation ignores alpha and falls back to
25    /// [`fill_rect`](Self::fill_rect). Backends with blending support should
26    /// override this for correct anti-aliased rendering.
27    fn blend_rect(&mut self, rect: Rect, color: Color) {
28        self.fill_rect(rect, color);
29    }
30
31    /// Blit a buffer of pixels to the target at the given position.
32    ///
33    /// The default implementation falls back to per-pixel [`fill_rect`](Self::fill_rect)
34    /// calls. Backends with bulk-copy support (e.g. DMA2D) should override this.
35    fn draw_pixels(&mut self, position: (i32, i32), pixels: &[Color], width: u32, height: u32) {
36        for y in 0..height as i32 {
37            for x in 0..width as i32 {
38                let idx = (y as u32 * width + x as u32) as usize;
39                if let Some(&c) = pixels.get(idx) {
40                    self.fill_rect(
41                        Rect {
42                            x: position.0 + x,
43                            y: position.1 + y,
44                            width: 1,
45                            height: 1,
46                        },
47                        c,
48                    );
49                }
50            }
51        }
52    }
53
54    /// Blend a horizontal run of pixels with per-pixel anti-aliased coverage.
55    ///
56    /// `coverage[i]` modulates `color`'s alpha for the pixel at
57    /// `(x + i, y)`. This is the AA inner-loop primitive: every higher-level
58    /// AA method funnels coverage spans through here, so backends with
59    /// hardware blend support (e.g. DMA2D's blend mode with per-pixel alpha
60    /// modulation) should override this for performance.
61    ///
62    /// The default implementation walks the run via [`blend_rect`](Self::blend_rect)
63    /// per pixel — correct, slow, sufficient for non-hot paths.
64    fn blend_row(&mut self, x: i32, y: i32, color: Color, coverage: &[u8]) {
65        for (i, &cov) in coverage.iter().enumerate() {
66            if cov == 0 {
67                continue;
68            }
69            let alpha = ((color.3 as u16 * cov as u16) / 255) as u8;
70            self.blend_rect(
71                Rect {
72                    x: x + i as i32,
73                    y,
74                    width: 1,
75                    height: 1,
76                },
77                Color(color.0, color.1, color.2, alpha),
78            );
79        }
80    }
81
82    /// Fill an oriented bounding box with anti-aliased coverage.
83    ///
84    /// `obb`'s center is in absolute framebuffer coordinates with sub-pixel
85    /// precision; `theta` is supplied via pre-computed `(cos_t, sin_t)` on
86    /// the [`Obb`] itself. `color`'s alpha is multiplied by per-pixel
87    /// coverage before blending.
88    ///
89    /// The default implementation rasterizes via
90    /// [`raster::rasterize_obb`] and emits coverage spans through
91    /// [`blend_row`](Self::blend_row). Backends that have hardware OBB
92    /// rasterization can override this directly; backends with hardware
93    /// blend but software geometry should override `blend_row` instead and
94    /// inherit this default.
95    fn fill_obb_aa(&mut self, obb: Obb, color: Color) {
96        let clip = obb.aabb();
97        let mut sink = RowBlendSink { r: self, color };
98        raster::rasterize_obb(&obb, clip, &mut sink);
99    }
100
101    /// Fill a disc (filled circle) with anti-aliased coverage at the
102    /// boundary. Sub-pixel center; sqrt is restricted to the 1-pixel AA
103    /// ring so the inner-area fast-path stays integer-arithmetic only.
104    ///
105    /// The default implementation routes through
106    /// [`raster::rasterize_disc`] + [`blend_row`](Self::blend_row), so
107    /// any backend that has overridden `blend_row` for hardware blend
108    /// inherits the acceleration here automatically.
109    fn fill_disc_aa(&mut self, center: crate::raster::PointF, radius: f32, color: Color) {
110        let pad = radius + 1.0;
111        let clip = Rect {
112            x: (center.x - pad) as i32 - 1,
113            y: (center.y - pad) as i32 - 1,
114            width: (pad * 2.0) as i32 + 3,
115            height: (pad * 2.0) as i32 + 3,
116        };
117        let mut sink = RowBlendSink { r: self, color };
118        raster::rasterize_disc(center, radius, clip, &mut sink);
119    }
120
121    /// Stroke a line between `a` and `b` with given `width`, anti-aliased.
122    /// Endpoints are square-cut; see [`raster::rasterize_line`].
123    ///
124    /// Default implementation routes through
125    /// [`raster::rasterize_line`] + [`blend_row`](Self::blend_row), so
126    /// `blend_row` overrides apply automatically.
127    fn stroke_line_aa(
128        &mut self,
129        a: crate::raster::PointF,
130        b: crate::raster::PointF,
131        width: f32,
132        color: Color,
133    ) {
134        // Conservative AABB: full canvas span — `rasterize_line` clips
135        // internally to the OBB AABB anyway, so passing a permissive clip
136        // here only costs a single rect-intersect inside the kernel.
137        let clip = Rect {
138            x: i32::MIN / 2,
139            y: i32::MIN / 2,
140            width: i32::MAX / 2,
141            height: i32::MAX / 2,
142        };
143        let mut sink = RowBlendSink { r: self, color };
144        raster::rasterize_line(a, b, width, clip, &mut sink);
145    }
146
147    /// Fill an annular arc / pie slice with anti-aliased coverage.
148    /// See [`raster::rasterize_arc`] for the angle convention; in short,
149    /// `(start_cos, start_sin)` and `(end_cos, end_sin)` are pre-computed
150    /// boundary-ray unit vectors and `extent` is the *signed* angular
151    /// magnitude. `r_inner = 0.0` produces a pie slice; `r_inner > 0.0`
152    /// produces a ring segment.
153    ///
154    /// Default impl routes through [`raster::rasterize_arc`] +
155    /// [`blend_row`](Self::blend_row).
156    #[allow(clippy::too_many_arguments)]
157    fn fill_arc_aa(
158        &mut self,
159        center: crate::raster::PointF,
160        r_outer: f32,
161        r_inner: f32,
162        start_cos: f32,
163        start_sin: f32,
164        end_cos: f32,
165        end_sin: f32,
166        extent: f32,
167        color: Color,
168    ) {
169        let pad = r_outer + 1.0;
170        let clip = Rect {
171            x: (center.x - pad) as i32 - 1,
172            y: (center.y - pad) as i32 - 1,
173            width: (pad * 2.0) as i32 + 3,
174            height: (pad * 2.0) as i32 + 3,
175        };
176        let mut sink = RowBlendSink { r: self, color };
177        raster::rasterize_arc(
178            center, r_outer, r_inner, start_cos, start_sin, end_cos, end_sin, extent, clip,
179            &mut sink,
180        );
181    }
182
183    /// Execute a captured [`CommandList`] against this renderer.
184    ///
185    /// Default implementation walks the list and dispatches each
186    /// command via [`crate::cmd::Cmd::dispatch_to`] — equivalent to
187    /// having issued the corresponding trait calls directly. Backends
188    /// override this to apply pre-pass optimizations: occlusion
189    /// culling, opaque-cmd skip, hardware command-buffer chaining,
190    /// tile binning. Overrides must preserve byte-identical output to
191    /// the default path.
192    ///
193    /// This is the "graphics-language" entry point on the [`Renderer`]
194    /// trait — code holding `&mut dyn Renderer` can submit captured
195    /// command lists and pick up backend specializations
196    /// transparently. See [`crate::cmd`] for the language model.
197    fn submit(&mut self, list: &CommandList) {
198        list.replay(self);
199    }
200}
201
202struct RowBlendSink<'r, R: Renderer + ?Sized> {
203    r: &'r mut R,
204    color: Color,
205}
206
207impl<R: Renderer + ?Sized> CoverageSink for RowBlendSink<'_, R> {
208    fn row(&mut self, x: i32, y: i32, coverage: &[u8]) {
209        self.r.blend_row(x, y, self.color, coverage);
210    }
211}