use core::slice::from_raw_parts;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel};
use oxivgl_sys::{lv_area_t, lv_display_flush_ready, lv_display_t};
#[derive(Debug)]
pub enum UiError {
Display,
}
#[allow(async_fn_in_trait)]
pub trait DisplayOutput {
async fn show_raw_data(
&mut self,
x: u16,
y: u16,
w: u16,
h: u16,
data: &[u8],
) -> Result<(), UiError>;
}
#[derive(Debug)]
pub struct LvDispDrv(pub(crate) *mut lv_display_t);
unsafe impl Send for LvDispDrv {}
unsafe impl Sync for LvDispDrv {}
#[derive(Debug)]
pub struct DrawOperation {
disp_drv: LvDispDrv,
pub data: &'static [u8],
pub x: u16,
pub y: u16,
pub w: u16,
pub h: u16,
}
pub static DRAW_OPERATION: Channel<CriticalSectionRawMutex, DrawOperation, 1> = Channel::new();
pub static FLUSH_OPERATION: Channel<CriticalSectionRawMutex, LvDispDrv, 1> = Channel::new();
#[esp_hal::ram]
pub async fn flush_frame_buffer(mut display_driver: impl DisplayOutput) -> ! {
debug!("Starting flush task");
super::display::DISPLAY_READY.signal(());
let flush_sender = FLUSH_OPERATION.sender();
loop {
debug!("Flushing frame buffer");
let draw_operation = DRAW_OPERATION.receive().await;
let DrawOperation { disp_drv, data, x, y, w, h } = draw_operation;
if let Err(_e) = display_driver.show_raw_data(x, y, w, h, data).await {
error!("show_raw_data failed");
}
flush_sender.send(disp_drv).await;
debug!("Flush done");
}
}
#[esp_hal::ram]
pub(crate) unsafe extern "C" fn wait_callback(_disp: *mut lv_display_t) {
loop {
if let Ok(drv) = FLUSH_OPERATION.try_receive() {
unsafe {
lv_display_flush_ready(drv.0);
}
return;
}
#[cfg(target_os = "none")]
unsafe {
core::arch::asm!("waiti 0")
};
}
}
#[esp_hal::ram]
pub(crate) unsafe extern "C" fn flush_callback(
disp: *mut lv_display_t,
area_p: *const lv_area_t,
px_map: *mut u8,
) {
if disp.is_null() || area_p.is_null() || px_map.is_null() {
error!("flush_callback: null disp, area_p, or px_map");
return;
}
let area = unsafe { &*area_p };
if area.x2 < area.x1 || area.y2 < area.y1 {
error!("flush_callback: invalid area");
return;
}
let w = (area.x2 - area.x1 + 1) as u16;
let h = (area.y2 - area.y1 + 1) as u16;
debug!("Flushing {} x {} ({};{} .. {};{})", w, h, area.x1, area.y1, area.x2, area.y2);
let Some(len_pixels) = (w as usize).checked_mul(h as usize) else {
error!("flush_callback: w*h overflowed");
return;
};
let data_bytes = len_pixels * 2;
let op = DrawOperation {
disp_drv: LvDispDrv(disp),
data: unsafe { from_raw_parts(px_map, data_bytes) },
x: area.x1 as u16,
y: area.y1 as u16,
w,
h,
};
if let Err(_e) = DRAW_OPERATION.try_send(op) {
error!("DRAW_OPERATION channel full — should be unreachable");
}
}