#[cfg(not(test))]
use crate::drm_render_target::{FramebufferTarget, RenderTarget};
use crate::framebuffer::DmaReadyFramebuffer;
use log::{debug, error, info, trace};
use std::sync::Arc;
#[cfg(not(feature = "tokio-threads"))]
use std::sync::Mutex;
#[cfg(feature = "tokio-threads")]
use tokio::sync::Mutex;
pub struct DoubleBuffer {
#[cfg(not(feature = "tokio-threads"))]
sender: Option<std::sync::mpsc::Sender<usize>>,
#[cfg(feature = "tokio-threads")]
sender: Option<tokio::sync::mpsc::Sender<usize>>,
toggle: bool,
size: usize,
fbuf0: DmaReadyFramebuffer,
fbuf1: DmaReadyFramebuffer,
mutex: Arc<Mutex<bool>>,
}
impl DoubleBuffer {
pub fn new(width: usize, height: usize) -> Self {
trace!("Creating new DoubleBuffer with raw framebuffers");
let fbuf0 = DmaReadyFramebuffer::new(width, height, true);
let fbuf1 = DmaReadyFramebuffer::new(width, height, true);
Self {
sender: None,
toggle: false,
size: width * height,
fbuf0,
fbuf1,
mutex: Arc::new(Mutex::new(true)),
}
}
pub fn start_thread(&mut self, #[cfg(not(test))] mut display: RenderTarget) {
debug!("Creating RenderTarget from card");
info!("Starting fb writer thread");
#[cfg(not(feature = "tokio-threads"))]
let (send, receive) = std::sync::mpsc::channel();
#[cfg(feature = "tokio-threads")]
let (send, mut receive) = tokio::sync::mpsc::channel(16);
self.sender = Some(send);
let mutex2 = self.mutex.clone();
let size = self.size;
#[cfg(not(feature = "tokio-threads"))]
std::thread::spawn(move || {
trace!("Framebuffer writer thread started for std runtime");
loop {
match receive.recv() {
Ok(ptr) => {
trace!("Received framebuffer pointer: {ptr}");
unsafe {
let _lock = mutex2.lock().unwrap();
let ptr = ptr as *mut u32;
let slice = std::slice::from_raw_parts_mut(ptr, size);
#[cfg(not(test))]
display.eat_framebuffer(slice).unwrap();
slice.fill(0); };
}
_ => {
debug!("Leaving framebuffer loop");
break;
}
}
}
});
#[cfg(feature = "tokio-threads")]
tokio::spawn(async move {
trace!("Framebuffer writer thread started for tokio runtime");
loop {
match receive.recv().await {
Some(ptr) => {
trace!("Received framebuffer pointer: {}", ptr);
unsafe {
let _lock = mutex2.lock().await;
let ptr = ptr as *mut u32;
let slice = std::slice::from_raw_parts_mut(ptr, size);
#[cfg(not(test))]
display.eat_framebuffer(slice).unwrap();
slice.fill(0); };
}
_ => {
debug!("Leaving framebuffer loop");
break;
}
}
}
});
}
pub fn swap_framebuffer(&mut self) -> &mut DmaReadyFramebuffer {
trace!("Swapping framebuffer from {}", self.toggle);
self.toggle = !self.toggle;
if self.toggle {
&mut self.fbuf0
} else {
&mut self.fbuf1
}
}
pub fn get_current_framebuffer(&mut self) -> &mut DmaReadyFramebuffer {
if self.toggle {
&mut self.fbuf0
} else {
&mut self.fbuf1
}
}
#[cfg(not(feature = "tokio-threads"))]
pub fn send_framebuffer(&mut self) {
{
let _lock = self.mutex.lock().unwrap();
std::mem::drop(_lock);
}
let fbuf = if self.toggle {
trace!(
"sending framebuffer 0 ({:?})",
self.fbuf0.framebuffer.as_ptr()
);
&mut self.fbuf0
} else {
trace!(
"sending framebuffer 1 ({:?})",
self.fbuf1.framebuffer.as_ptr()
);
&mut self.fbuf1
};
if let Some(sender) = &self.sender {
sender
.send(fbuf.framebuffer.as_ptr() as usize)
.inspect_err(|msg| {
error!("Failed to send framebuffer: {msg}");
})
.unwrap();
}
}
#[cfg(feature = "tokio-threads")]
pub async fn send_framebuffer(&mut self) {
trace!("Sending framebuffer in async context");
{
let _lock = self.mutex.lock().await;
std::mem::drop(_lock);
}
let fbuf = if self.toggle {
trace!(
"sending framebuffer 0 ({:?})",
self.fbuf0.framebuffer.as_ptr()
);
&mut self.fbuf0
} else {
trace!(
"sending framebuffer 1 ({:?})",
self.fbuf1.framebuffer.as_ptr()
);
&mut self.fbuf1
};
if let Some(sender) = &self.sender {
sender
.send(fbuf.framebuffer.as_ptr() as usize)
.await
.inspect_err(|msg| {
error!("Failed to send framebuffer: {}", msg);
})
.unwrap();
}
}
}
unsafe impl Send for DoubleBuffer {}
#[cfg(test)]
mod tests {
use embedded_graphics::prelude::RgbColor;
use super::*;
#[test]
fn test_doublebuffer_creation() {
const WIDTH: usize = 64;
const HEIGHT: usize = 64;
let db = DoubleBuffer::new(WIDTH, HEIGHT);
assert!(!db.toggle); assert!(db.sender.is_none()); }
#[test]
fn test_framebuffer_swapping() {
const WIDTH: usize = 64;
const HEIGHT: usize = 64;
let mut db = DoubleBuffer::new(WIDTH, HEIGHT);
let fb1 = db.swap_framebuffer();
use embedded_graphics_core::geometry::Point;
use embedded_graphics_core::pixelcolor::Bgr888;
fb1.set_pixel(Point::new(0, 0), Bgr888::new(255, 255, 255));
let fb2 = db.swap_framebuffer();
assert_eq!(fb2.get_pixel(Point { x: 0, y: 0 }), Some(Bgr888::BLACK));
let fb3 = db.swap_framebuffer();
assert_eq!(fb3.get_pixel(Point { x: 0, y: 0 }), Some(Bgr888::WHITE)); }
#[test]
fn test_toggle_behavior() {
const WIDTH: usize = 64;
const HEIGHT: usize = 64;
let mut db = DoubleBuffer::new(WIDTH, HEIGHT);
let initial_toggle = db.toggle;
assert!(!initial_toggle);
let _first_fb = db.swap_framebuffer();
let _second_fb = db.swap_framebuffer();
let _third_fb = db.swap_framebuffer();
assert!(db.toggle);
}
}