Skip to main content

drm_gfx/
doublebuffer.rs

1#[cfg(not(test))]
2use crate::drm_render_target::{FramebufferTarget, RenderTarget};
3use crate::framebuffer::DmaReadyFramebuffer;
4use log::{debug, error, info, trace};
5use std::sync::Arc;
6#[cfg(not(feature = "tokio-threads"))]
7use std::sync::Mutex;
8#[cfg(feature = "tokio-threads")]
9use tokio::sync::Mutex;
10
11pub struct DoubleBuffer {
12    #[cfg(not(feature = "tokio-threads"))]
13    sender: Option<std::sync::mpsc::Sender<usize>>,
14    #[cfg(feature = "tokio-threads")]
15    sender: Option<tokio::sync::mpsc::Sender<usize>>,
16    toggle: bool,
17    size: usize,
18    fbuf0: DmaReadyFramebuffer,
19    fbuf1: DmaReadyFramebuffer,
20    mutex: Arc<Mutex<bool>>,
21}
22
23impl DoubleBuffer {
24    pub fn new(width: usize, height: usize) -> Self {
25        trace!("Creating new DoubleBuffer with raw framebuffers");
26
27        let fbuf0 = DmaReadyFramebuffer::new(width, height, true);
28        let fbuf1 = DmaReadyFramebuffer::new(width, height, true);
29
30        Self {
31            sender: None,
32            toggle: false,
33            size: width * height,
34            fbuf0,
35            fbuf1,
36            mutex: Arc::new(Mutex::new(true)),
37        }
38    }
39
40    pub fn start_thread(&mut self, #[cfg(not(test))] mut display: RenderTarget) {
41        debug!("Creating RenderTarget from card");
42
43        info!("Starting fb writer thread");
44        #[cfg(not(feature = "tokio-threads"))]
45        let (send, receive) = std::sync::mpsc::channel();
46        #[cfg(feature = "tokio-threads")]
47        let (send, mut receive) = tokio::sync::mpsc::channel(16);
48
49        self.sender = Some(send);
50        let mutex2 = self.mutex.clone();
51        let size = self.size;
52
53        #[cfg(not(feature = "tokio-threads"))]
54        std::thread::spawn(move || {
55            trace!("Framebuffer writer thread started for std runtime");
56            loop {
57                match receive.recv() {
58                    Ok(ptr) => {
59                        trace!("Received framebuffer pointer: {ptr}");
60                        unsafe {
61                            let _lock = mutex2.lock().unwrap();
62
63                            let ptr = ptr as *mut u32;
64                            let slice = std::slice::from_raw_parts_mut(ptr, size);
65
66                            #[cfg(not(test))]
67                            display.eat_framebuffer(slice).unwrap();
68                            slice.fill(0); // 2.2ms
69                        };
70                    }
71                    _ => {
72                        debug!("Leaving framebuffer loop");
73                        break;
74                    }
75                }
76            }
77        });
78
79        #[cfg(feature = "tokio-threads")]
80        tokio::spawn(async move {
81            trace!("Framebuffer writer thread started for tokio runtime");
82            loop {
83                match receive.recv().await {
84                    Some(ptr) => {
85                        trace!("Received framebuffer pointer: {}", ptr);
86                        unsafe {
87                            let _lock = mutex2.lock().await;
88
89                            let ptr = ptr as *mut u32;
90                            let slice = std::slice::from_raw_parts_mut(ptr, size);
91
92                            #[cfg(not(test))]
93                            display.eat_framebuffer(slice).unwrap();
94                            slice.fill(0); // 2.2ms
95                        };
96                    }
97                    _ => {
98                        debug!("Leaving framebuffer loop");
99                        break;
100                    }
101                }
102            }
103        });
104    }
105
106    pub fn swap_framebuffer(&mut self) -> &mut DmaReadyFramebuffer {
107        trace!("Swapping framebuffer from {}", self.toggle);
108        self.toggle = !self.toggle;
109
110        if self.toggle {
111            &mut self.fbuf0
112        } else {
113            &mut self.fbuf1
114        }
115    }
116
117    pub fn get_current_framebuffer(&mut self) -> &mut DmaReadyFramebuffer {
118        if self.toggle {
119            &mut self.fbuf0
120        } else {
121            &mut self.fbuf1
122        }
123    }
124
125    #[cfg(not(feature = "tokio-threads"))]
126    pub fn send_framebuffer(&mut self) {
127        {
128            let _lock = self.mutex.lock().unwrap();
129            std::mem::drop(_lock);
130        }
131
132        let fbuf = if self.toggle {
133            trace!(
134                "sending framebuffer 0 ({:?})",
135                self.fbuf0.framebuffer.as_ptr()
136            );
137            &mut self.fbuf0
138        } else {
139            trace!(
140                "sending framebuffer 1 ({:?})",
141                self.fbuf1.framebuffer.as_ptr()
142            );
143            &mut self.fbuf1
144        };
145
146        if let Some(sender) = &self.sender {
147            sender
148                .send(fbuf.framebuffer.as_ptr() as usize)
149                .inspect_err(|msg| {
150                    error!("Failed to send framebuffer: {msg}");
151                })
152                .unwrap();
153        }
154    }
155
156    #[cfg(feature = "tokio-threads")]
157    pub async fn send_framebuffer(&mut self) {
158        trace!("Sending framebuffer in async context");
159        {
160            let _lock = self.mutex.lock().await;
161            std::mem::drop(_lock);
162        }
163
164        let fbuf = if self.toggle {
165            trace!(
166                "sending framebuffer 0 ({:?})",
167                self.fbuf0.framebuffer.as_ptr()
168            );
169            &mut self.fbuf0
170        } else {
171            trace!(
172                "sending framebuffer 1 ({:?})",
173                self.fbuf1.framebuffer.as_ptr()
174            );
175            &mut self.fbuf1
176        };
177
178        if let Some(sender) = &self.sender {
179            sender
180                .send(fbuf.framebuffer.as_ptr() as usize)
181                .await
182                .inspect_err(|msg| {
183                    error!("Failed to send framebuffer: {}", msg);
184                })
185                .unwrap();
186        }
187    }
188}
189
190// Add at the end of doublebuffer.rs, after the struct definition
191// SAFETY: The raw pointers in DmaReadyFramebuffer point to memory owned by
192// RenderTarget which outlives the DoubleBuffer. Access to the framebuffers
193// is synchronized via the mutex field, ensuring only one thread accesses
194// a framebuffer at a time.
195unsafe impl Send for DoubleBuffer {}
196
197#[cfg(test)]
198mod tests {
199    use embedded_graphics::prelude::RgbColor;
200
201    use super::*;
202    // Note: We considered creating a MockRenderTarget for testing, but since we're not
203    // testing the start_thread and send_framebuffer functionality directly (due to
204    // the complexity of testing threads and channels), we've removed it to avoid unused code.
205
206    #[test]
207    fn test_doublebuffer_creation() {
208        const WIDTH: usize = 64;
209        const HEIGHT: usize = 64;
210
211        // Create doublebuffer
212        let db = DoubleBuffer::new(WIDTH, HEIGHT);
213
214        // Test initial state
215        assert!(!db.toggle); // Should start with toggle = false
216        assert!(db.sender.is_none()); // No sender initially
217    }
218
219    #[test]
220    fn test_framebuffer_swapping() {
221        const WIDTH: usize = 64;
222        const HEIGHT: usize = 64;
223
224        // Create doublebuffer
225        let mut db = DoubleBuffer::new(WIDTH, HEIGHT);
226
227        // Initial toggle is false, so swap should make it true
228        let fb1 = db.swap_framebuffer();
229        // Note: We don't check toggle state directly as it's an internal implementation detail
230
231        // Write to the first buffer using embedded-graphics compatible method
232        use embedded_graphics_core::geometry::Point;
233        use embedded_graphics_core::pixelcolor::Bgr888;
234
235        fb1.set_pixel(Point::new(0, 0), Bgr888::new(255, 255, 255));
236
237        // Swap again, should get the other buffer
238        let fb2 = db.swap_framebuffer();
239
240        // Check buffer contents using as_slice
241        assert_eq!(fb2.get_pixel(Point { x: 0, y: 0 }), Some(Bgr888::BLACK)); // Second buffer should be empty
242
243        // Swap again, should get back to the first buffer with our pixel set
244        let fb3 = db.swap_framebuffer();
245
246        // First pixel in first buffer should be white
247        assert_eq!(fb3.get_pixel(Point { x: 0, y: 0 }), Some(Bgr888::WHITE)); // Should contain our white pixel
248    }
249
250    // We can't easily test start_thread and send_framebuffer without mocking the RenderTarget trait
251    // which would require significant mocking infrastructure. These functions involve threads
252    // and channel communication which are hard to test in unit tests.
253    //
254    // For now, we'll skip these tests and focus on the core functionality that can be
255    // tested reliably.
256
257    #[test]
258    fn test_toggle_behavior() {
259        const WIDTH: usize = 64;
260        const HEIGHT: usize = 64;
261
262        // Create doublebuffer
263        let mut db = DoubleBuffer::new(WIDTH, HEIGHT);
264
265        // Track initial value - should be false
266        let initial_toggle = db.toggle;
267        assert!(!initial_toggle);
268
269        // First swap
270        let _first_fb = db.swap_framebuffer();
271
272        // Second swap
273        let _second_fb = db.swap_framebuffer();
274
275        // Third swap
276        let _third_fb = db.swap_framebuffer();
277
278        // After three swaps, toggle should be true again
279        assert!(db.toggle);
280    }
281}