Skip to main content

drm_gfx/
framebuffer.rs

1use embedded_graphics_core::pixelcolor::raw::RawU24;
2use embedded_graphics_core::{
3    draw_target::DrawTarget,
4    geometry::{OriginDimensions, Point},
5    pixelcolor::{Bgr888, IntoStorage},
6};
7
8pub struct DmaReadyFramebuffer {
9    pub width: usize,
10    pub height: usize,
11    pub framebuffer: Box<[u32]>,
12    big_endian: bool,
13}
14
15impl DmaReadyFramebuffer {
16    pub fn new(width: usize, height: usize, big_endian: bool) -> DmaReadyFramebuffer {
17        DmaReadyFramebuffer {
18            framebuffer: vec![0u32; width * height].into_boxed_slice(),
19            width,
20            height,
21            big_endian,
22        }
23    }
24
25    pub fn set_pixel(&mut self, point: Point, color: Bgr888) {
26        if point.x >= 0
27            && point.x < self.width as i32
28            && point.y >= 0
29            && point.y < self.height as i32
30        {
31            let framebuffer = &mut *self.framebuffer;
32
33            if self.big_endian {
34                framebuffer[point.y as usize * self.width + point.x as usize] =
35                    color.into_storage().to_be();
36            } else {
37                framebuffer[point.y as usize * self.width + point.x as usize] =
38                    color.into_storage();
39            }
40        }
41    }
42
43    pub fn get_pixel(&mut self, point: Point) -> Option<Bgr888> {
44        if point.x >= 0
45            && point.x < self.width as i32
46            && point.y >= 0
47            && point.y < self.height as i32
48        {
49            if self.big_endian {
50                Some(Bgr888::from(RawU24::new(u32::from_be(
51                    self.framebuffer[point.y as usize * self.width + point.x as usize],
52                ))))
53            } else {
54                Some(Bgr888::from(RawU24::new(
55                    self.framebuffer[point.y as usize * self.width + point.x as usize],
56                )))
57            }
58        } else {
59            None
60        }
61    }
62}
63
64impl DrawTarget for DmaReadyFramebuffer {
65    type Color = Bgr888;
66    type Error = core::convert::Infallible;
67
68    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
69    where
70        I: IntoIterator<Item = embedded_graphics_core::prelude::Pixel<Self::Color>>,
71    {
72        for pixel in pixels {
73            let embedded_graphics_core::prelude::Pixel(point, color) = pixel;
74
75            self.set_pixel(point, color);
76        }
77        Ok(())
78    }
79
80    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
81        if self.big_endian {
82            self.framebuffer.fill(color.into_storage().to_be());
83        } else {
84            self.framebuffer.fill(color.into_storage());
85        }
86
87        Ok(())
88    }
89}
90
91impl OriginDimensions for DmaReadyFramebuffer {
92    fn size(&self) -> embedded_graphics_core::geometry::Size {
93        embedded_graphics_core::geometry::Size::new(self.width as u32, self.height as u32)
94    }
95}
96
97// Add at the end of framebuffer.rs
98// SAFETY: The raw pointer is only used to access memory owned by RenderTarget,
99// which lives for the entire duration of the program. Access is synchronized
100// via mutex in DoubleBuffer.
101unsafe impl Send for DmaReadyFramebuffer {}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use embedded_graphics_core::prelude::*;
107
108    #[test]
109    fn test_framebuffer_creation() {
110        const WIDTH: usize = 64;
111        const HEIGHT: usize = 32;
112
113        // Create framebuffer with little-endian
114        let fb_le = DmaReadyFramebuffer::new(WIDTH, HEIGHT, false);
115        assert!(!fb_le.big_endian);
116
117        // Create framebuffer with big-endian
118        let fb_be = DmaReadyFramebuffer::new(WIDTH, HEIGHT, true);
119        assert!(fb_be.big_endian);
120    }
121
122    #[test]
123    fn test_set_pixel() {
124        const WIDTH: usize = 32;
125        const HEIGHT: usize = 32;
126
127        // Create framebuffer
128        let mut fb = DmaReadyFramebuffer::new(WIDTH, HEIGHT, false);
129
130        // Set a pixel at a valid position
131        // In Bgr888, the parameters are (blue, green, red)
132        let color = Bgr888::new(64, 128, 255); // B=64, G=128, R=255
133        let point = Point::new(5, 10);
134        fb.set_pixel(point, color);
135
136        // Access the buffer and check the pixel value
137        let value = fb.framebuffer[10 * WIDTH + 5]; // y * width + x
138
139        // When Bgr888 is stored, it's stored as 0x00RRGGBB
140        let expected = color.into_storage();
141        assert_eq!(value, expected);
142    }
143
144    #[test]
145    fn test_set_pixel_big_endian() {
146        const WIDTH: usize = 32;
147        const HEIGHT: usize = 32;
148
149        // Create framebuffer with big-endian flag
150        let mut fb = DmaReadyFramebuffer::new(WIDTH, HEIGHT, true);
151
152        // Set a pixel at a valid position
153        // In Bgr888, the parameters are (blue, green, red)
154        let color = Bgr888::new(64, 128, 255); // B=64, G=128, R=255
155        let point = Point::new(5, 10);
156        fb.set_pixel(point, color);
157
158        // Access the buffer and check the pixel value
159        let value = fb.framebuffer[10 * WIDTH + 5]; // y * width + x
160
161        // When stored in big endian, the bytes are swapped
162        let expected = color.into_storage().to_be();
163        assert_eq!(value, expected);
164    }
165
166    #[test]
167    fn test_get_pixel_roundtrip() {
168        const WIDTH: usize = 32;
169        const HEIGHT: usize = 32;
170
171        // Test little-endian
172        let mut fb_le = DmaReadyFramebuffer::new(WIDTH, HEIGHT, false);
173        let color = Bgr888::new(64, 128, 255);
174        let point = Point::new(5, 10);
175        fb_le.set_pixel(point, color);
176        assert_eq!(fb_le.get_pixel(point), Some(color));
177
178        // Test big-endian
179        let mut fb_be = DmaReadyFramebuffer::new(WIDTH, HEIGHT, true);
180        fb_be.set_pixel(point, color);
181        assert_eq!(fb_be.get_pixel(point), Some(color));
182    }
183
184    #[test]
185    fn test_set_pixel_out_of_bounds() {
186        const WIDTH: usize = 32;
187        const HEIGHT: usize = 32;
188
189        // Create framebuffer
190        let mut fb = DmaReadyFramebuffer::new(WIDTH, HEIGHT, false);
191
192        // Set a pixel outside the bounds (should be ignored)
193        let color = Bgr888::new(255, 0, 0);
194
195        // Test out of bounds in X
196        fb.set_pixel(Point::new(-1, 10), color);
197        fb.set_pixel(Point::new(WIDTH as i32, 10), color);
198
199        // Test out of bounds in Y
200        fb.set_pixel(Point::new(5, -1), color);
201        fb.set_pixel(Point::new(5, HEIGHT as i32), color);
202
203        // Verify no crash occurred and buffer is untouched at those locations
204    }
205
206    #[test]
207    fn test_as_slice() {
208        const WIDTH: usize = 4;
209        const HEIGHT: usize = 2;
210
211        // Create framebuffer and initialize with a pattern
212        let mut fb = DmaReadyFramebuffer::new(WIDTH, HEIGHT, false);
213
214        // Set some pixels - remember in Bgr888::new the parameters are (blue, green, red)
215        fb.set_pixel(Point::new(0, 0), Bgr888::new(0, 0, 1)); // Mostly red
216        fb.set_pixel(Point::new(1, 0), Bgr888::new(0, 0, 2)); // Slightly brighter red
217        fb.set_pixel(Point::new(0, 1), Bgr888::new(0, 0, 3)); // Even brighter red
218
219        // Get slice and verify length
220        let slice = fb.framebuffer;
221        assert_eq!(slice.len(), WIDTH * HEIGHT);
222
223        // Check that the slice contains our pixel values
224        assert_eq!(slice[0], Bgr888::new(0, 0, 1).into_storage()); // (0,0)
225        assert_eq!(slice[1], Bgr888::new(0, 0, 2).into_storage()); // (1,0)
226        assert_eq!(slice[WIDTH], Bgr888::new(0, 0, 3).into_storage()); // (0,1)
227    }
228
229    #[test]
230    fn test_as_mut_slice() {
231        const WIDTH: usize = 4;
232        const HEIGHT: usize = 2;
233
234        // Create framebuffer
235        let mut fb = DmaReadyFramebuffer::new(WIDTH, HEIGHT, false);
236
237        // Get mutable slice and modify it
238        let slice = &mut fb.framebuffer;
239        assert_eq!(slice.len(), WIDTH * HEIGHT);
240
241        // Set some values directly
242        slice[0] = 0x00FF0000; // Red in first pixel
243        slice[1] = 0x0000FF00; // Green in second pixel
244
245        // Verify using as_slice
246        let check_slice = fb.framebuffer;
247        assert_eq!(check_slice[0], 0x00FF0000);
248        assert_eq!(check_slice[1], 0x0000FF00);
249    }
250
251    #[test]
252    fn test_draw_target_draw_iter() {
253        const WIDTH: usize = 32;
254        const HEIGHT: usize = 32;
255
256        // Create framebuffer
257        let mut fb = DmaReadyFramebuffer::new(WIDTH, HEIGHT, false);
258
259        // Create some test pixels - parameters for Bgr888::new are (blue, green, red)
260        let pixels = [
261            Pixel(Point::new(1, 1), Bgr888::new(0, 0, 255)), // Red (B=0, G=0, R=255)
262            Pixel(Point::new(2, 1), Bgr888::new(0, 255, 0)), // Green (B=0, G=255, R=0)
263            Pixel(Point::new(3, 1), Bgr888::new(255, 0, 0)), // Blue (B=255, G=0, R=0)
264        ];
265
266        // Draw the pixels
267        fb.draw_iter(pixels).unwrap();
268
269        // Check each pixel was set correctly using as_slice
270        let slice = fb.framebuffer;
271
272        // Red pixel at (1,1) = y*width + x = 1*WIDTH + 1
273        assert_eq!(slice[1 * WIDTH + 1], Bgr888::new(0, 0, 255).into_storage());
274
275        // Green pixel at (2,1)
276        assert_eq!(slice[1 * WIDTH + 2], Bgr888::new(0, 255, 0).into_storage());
277
278        // Blue pixel at (3,1)
279        assert_eq!(slice[1 * WIDTH + 3], Bgr888::new(255, 0, 0).into_storage());
280    }
281
282    #[test]
283    fn test_draw_target_clear() {
284        const WIDTH: usize = 16;
285        const HEIGHT: usize = 16;
286
287        // Create framebuffer
288        let mut fb = DmaReadyFramebuffer::new(WIDTH, HEIGHT, false);
289
290        // Set some initial pixels
291        fb.set_pixel(Point::new(0, 0), Bgr888::new(255, 0, 0));
292        fb.set_pixel(Point::new(1, 1), Bgr888::new(0, 255, 0));
293
294        // Clear with blue
295        fb.clear(Bgr888::new(0, 0, 128)).unwrap();
296
297        // Check that all pixels are now blue
298        let slice = fb.framebuffer;
299        let blue_value = Bgr888::new(0, 0, 128).into_storage();
300
301        for pixel in slice {
302            assert_eq!(pixel, blue_value);
303        }
304    }
305
306    #[test]
307    fn test_draw_target_clear_big_endian() {
308        const WIDTH: usize = 16;
309        const HEIGHT: usize = 16;
310
311        // Create big-endian framebuffer
312        let mut fb = DmaReadyFramebuffer::new(WIDTH, HEIGHT, true);
313
314        // Clear with a color
315        let color = Bgr888::new(10, 20, 30);
316        fb.clear(color).unwrap();
317
318        // Check that all pixels are set to the big-endian value
319        let slice = fb.framebuffer;
320        let expected_value = color.into_storage().to_be();
321
322        for pixel in slice {
323            assert_eq!(pixel, expected_value);
324        }
325    }
326
327    #[test]
328    fn test_origin_dimensions() {
329        const WIDTH: usize = 64;
330        const HEIGHT: usize = 32;
331
332        // Create framebuffer
333        let fb = DmaReadyFramebuffer::new(WIDTH, HEIGHT, false);
334
335        // Check dimensions
336        let size = fb.framebuffer.len();
337        assert_eq!(size, WIDTH * HEIGHT);
338    }
339}