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}