Allow to create statically allocated DrawBuffer

This commit is contained in:
Rafael Caricio 2021-06-04 21:39:29 +02:00
parent 6ba1fcdaff
commit 00e2004822
Signed by: rafaelcaricio
GPG key ID: 3C86DBCE8E93C947
7 changed files with 164 additions and 72 deletions

View file

@ -30,4 +30,5 @@ jobs:
run: cargo build --verbose run: cargo build --verbose
- name: Run tests - name: Run tests
run: cargo test --verbose # LVGL is not thread safe, we need to run tests sequentially
run: cargo test --verbose -- --nocapture --test-threads 1

View file

@ -5,7 +5,7 @@ use embedded_graphics_simulator::{
OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window, OutputSettingsBuilder, SimulatorDisplay, SimulatorEvent, Window,
}; };
use lvgl; use lvgl;
use lvgl::display::{DefaultDisplay, Display}; use lvgl::display::{DefaultDisplay, Display, DrawBuffer};
use lvgl::style::Style; use lvgl::style::Style;
use lvgl::widgets::{Label, LabelAlign}; use lvgl::widgets::{Label, LabelAlign};
use lvgl::{Align, Color, LvError, Part, State, Widget, UI}; use lvgl::{Align, Color, LvError, Part, State, Widget, UI};
@ -16,6 +16,8 @@ use std::thread;
use std::thread::sleep; use std::thread::sleep;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
static DRAW_BUFFER: DrawBuffer = DrawBuffer::new();
fn main() -> Result<(), LvError> { fn main() -> Result<(), LvError> {
lvgl::init(); lvgl::init();
@ -29,11 +31,13 @@ fn main() -> Result<(), LvError> {
// Implement and register your display: // Implement and register your display:
let shared_native_display = SyncArc::new(Mutex::new(embedded_graphics_display)); let shared_native_display = SyncArc::new(Mutex::new(embedded_graphics_display));
let _display = Display::register_shared(&shared_native_display)?; let _display = Display::register_shared(&DRAW_BUFFER, &shared_native_display)?;
// Create screen and widgets // Create screen and widgets
let mut screen = DefaultDisplay::get_scr_act()?; let mut screen = DefaultDisplay::get_scr_act()?;
println!("Before all widgets: {:?}", mem_info());
let mut screen_style = Style::default(); let mut screen_style = Style::default();
screen_style.set_bg_color(State::DEFAULT, Color::from_rgb((0, 0, 0))); screen_style.set_bg_color(State::DEFAULT, Color::from_rgb((0, 0, 0)));
screen_style.set_radius(State::DEFAULT, 0); screen_style.set_radius(State::DEFAULT, 0);
@ -92,9 +96,12 @@ fn main() -> Result<(), LvError> {
_ => {} _ => {}
} }
} }
println!("During run: {:?}", mem_info());
sleep(Duration::from_secs(1)); sleep(Duration::from_secs(1));
} }
println!("Final part of demo app: {:?}", mem_info());
Ok(()) Ok(())
} }
@ -109,3 +116,20 @@ fn main() -> Result<(), LvError> {
extern "C" { extern "C" {
pub static mut noto_sans_numeric_80: lvgl_sys::lv_font_t; pub static mut noto_sans_numeric_80: lvgl_sys::lv_font_t;
} }
fn mem_info() -> lvgl_sys::lv_mem_monitor_t {
let mut info = lvgl_sys::lv_mem_monitor_t {
total_size: 0,
free_cnt: 0,
free_size: 0,
free_biggest_size: 0,
used_cnt: 0,
max_used: 0,
used_pct: 0,
frag_pct: 0,
};
unsafe {
lvgl_sys::lv_mem_monitor(&mut info as *mut _);
}
info
}

View file

@ -3,23 +3,64 @@
/// This struct represents all relevant information we can extract from the C function declaration /// This struct represents all relevant information we can extract from the C function declaration
/// of a LVGL public interface. We can use this information to do inference for how the parameter /// of a LVGL public interface. We can use this information to do inference for how the parameter
/// should be represented in a safe Rust API. /// should be represented in a safe Rust API.
#[derive(Clone, Debug)]
pub struct CParameter { pub struct CParameter {
/// The name of the parameter in the C code. /// The name of the parameter in the C code.
name: String, pub name: String,
/// This is the raw representation of the Rust equivalent of the C type. /// This is the raw representation of the Rust equivalent of the C type.
c_type: String, pub c_type: String,
/// Takes a pointer to a type that is referenced by the LVGL code permanently. /// Takes a pointer to a type that is referenced by the LVGL code permanently.
owned: bool, pub scope: ParameterScope,
/// The pointer is not marked as `*const` so the referenced object can be mutated. /// The pointer is not marked as `*const` so the referenced object can be mutated.
mutable: bool, pub mutable: bool,
/// We need to check if the value is optional in the C code. We need to check /// We need to check if the value is optional in the C code. We need to check
/// the function comments for this information. /// the function comments for this information.
/// - "if NULL then" /// - "if NULL then"
/// - "if not NULL then" /// - "if not NULL then"
/// - "NULL to" /// - "NULL to"
nullable: bool, pub allow_none: bool,
/// Comment associated with the parameter, if exists.
pub comment: Option<String>,
}
#[derive(Clone, Debug)]
pub enum ParameterScope {
Call,
Static,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FunctionKind {
Constructor,
Method,
Function,
}
/// Inference from a LVGL C API function.
#[derive(Clone, Debug)]
pub struct Function {
/// Name of the function in the LVGL C API.
pub name: String,
/// Comment associated with the function, if exists.
pub comment: Option<String>,
pub kind: FunctionKind,
pub parameters: Vec<CParameter>,
pub ret: Return,
}
#[derive(Clone, Debug)]
pub enum Return {
Value(Option<CParameter>),
/// If the return is a LVGL result
ResultError(CParameter),
} }

View file

@ -356,7 +356,7 @@ impl Rusty for LvType {
Some(name) => { Some(name) => {
let val = if self.is_str() { let val = if self.is_str() {
quote!(&cstr_core::CStr) quote!(&cstr_core::CStr)
} else if (self.literal_name.contains("lv_")) { } else if self.literal_name.contains("lv_") {
let ident = format_ident!("{}", name); let ident = format_ident!("{}", name);
quote!(&#ident) quote!(&#ident)
} else { } else {
@ -672,9 +672,8 @@ mod test {
} }
pub fn new() -> crate::LvResult<Self> { pub fn new() -> crate::LvResult<Self> {
Ok(Self::create_at( let mut parent = crate::display::DefaultDisplay::get_scr_act()?;
&mut crate::display::DefaultDisplay::get_scr_act(), Ok(Self::create_at(&mut parent)?)
)?)
} }
} }
}; };

View file

@ -19,6 +19,7 @@ cstr_core = "0.2.3"
bitflags = "1.2.1" bitflags = "1.2.1"
parking_lot = "0.11.1" parking_lot = "0.11.1"
lazy_static = "1.4.0" lazy_static = "1.4.0"
heapless = "0.7.1"
[features] [features]
alloc = ["cstr_core/alloc"] alloc = ["cstr_core/alloc"]
@ -32,7 +33,6 @@ lvgl-sys = { version = "0.5.2", path = "../lvgl-sys" }
[dev-dependencies] [dev-dependencies]
embedded-graphics-simulator = "0.2.1" embedded-graphics-simulator = "0.2.1"
heapless = "0.5.5"
[[example]] [[example]]
name = "app" name = "app"

View file

@ -2,15 +2,16 @@ use crate::functions::CoreError;
use crate::Box; use crate::Box;
use crate::{disp_drv_register, disp_get_default, get_str_act}; use crate::{disp_drv_register, disp_get_default, get_str_act};
use crate::{Color, Obj}; use crate::{Color, Obj};
use core::cell::{Cell, RefCell};
use core::marker::PhantomData; use core::marker::PhantomData;
use core::mem::{ManuallyDrop, MaybeUninit}; use core::mem::MaybeUninit;
use core::ptr::NonNull; use core::ptr::NonNull;
use core::sync::atomic::{AtomicBool, Ordering};
use core::{ptr, result}; use core::{ptr, result};
use embedded_graphics::drawable; use embedded_graphics::drawable;
use embedded_graphics::prelude::*; use embedded_graphics::prelude::*;
use parking_lot::const_mutex;
#[cfg(feature = "alloc")] use parking_lot::Mutex;
use parking_lot::Mutex; // TODO: Can this really be used in no_std envs with alloc?
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::sync::Arc; use alloc::sync::Arc;
@ -20,11 +21,6 @@ const REFRESH_BUFFER_LEN: usize = 2;
// Declare a buffer for the refresh rate // Declare a buffer for the refresh rate
const BUF_SIZE: usize = lvgl_sys::LV_HOR_RES_MAX as usize * REFRESH_BUFFER_LEN; const BUF_SIZE: usize = lvgl_sys::LV_HOR_RES_MAX as usize * REFRESH_BUFFER_LEN;
static mut REFRESH_BUFFER1: [MaybeUninit<lvgl_sys::lv_color_t>; BUF_SIZE] =
[MaybeUninit::<lvgl_sys::lv_color_t>::uninit(); BUF_SIZE];
static mut DRAW_BUFFER: MaybeUninit<lvgl_sys::lv_disp_buf_t> = MaybeUninit::uninit();
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum DisplayError { pub enum DisplayError {
NotAvailable, NotAvailable,
@ -46,23 +42,26 @@ impl Display {
Self { disp } Self { disp }
} }
pub fn register<T, C>(native_display: T) -> Result<Self> pub fn register<T, C>(draw_buffer: &'static DrawBuffer, native_display: T) -> Result<Self>
where where
T: DrawTarget<C>, T: DrawTarget<C>,
C: PixelColor + From<Color>, C: PixelColor + From<Color>,
{ {
let mut display_diver = DisplayDriver::new(DisplayBuffer::new(), native_display); let mut display_diver = DisplayDriver::new(draw_buffer, native_display)?;
Ok(disp_drv_register(&mut display_diver)?) Ok(disp_drv_register(&mut display_diver)?)
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn register_shared<T, C>(shared_native_display: &SharedNativeDisplay<T>) -> Result<Self> pub fn register_shared<T, C>(
draw_buffer: &'static DrawBuffer,
shared_native_display: &SharedNativeDisplay<T>,
) -> Result<Self>
where where
T: DrawTarget<C>, T: DrawTarget<C>,
C: PixelColor + From<Color>, C: PixelColor + From<Color>,
{ {
let mut display_diver = let mut display_diver =
DisplayDriver::new_shared(DisplayBuffer::new(), Arc::clone(shared_native_display)); DisplayDriver::new_shared(draw_buffer, Arc::clone(shared_native_display))?;
Ok(disp_drv_register(&mut display_diver)?) Ok(disp_drv_register(&mut display_diver)?)
} }
@ -73,7 +72,7 @@ impl Display {
impl Default for Display { impl Default for Display {
fn default() -> Self { fn default() -> Self {
disp_get_default().expect("LVGL must be initialized") disp_get_default().expect("LVGL must be INITIALIZED")
} }
} }
@ -87,21 +86,40 @@ impl DefaultDisplay {
} }
} }
pub struct DisplayBuffer {} pub struct DrawBuffer {
initialized: AtomicBool,
refresh_buffer: Mutex<RefCell<heapless::Vec<lvgl_sys::lv_color_t, BUF_SIZE>>>,
}
impl DisplayBuffer { impl DrawBuffer {
pub fn new() -> Self { pub const fn new() -> Self {
unsafe { Self {
lvgl_sys::lv_disp_buf_init( initialized: AtomicBool::new(false),
DRAW_BUFFER.as_mut_ptr(), refresh_buffer: const_mutex(RefCell::new(heapless::Vec::new())),
&mut REFRESH_BUFFER1 as *mut _ as *mut cty::c_void, }
// Box::into_raw(refresh_buffer2) as *mut cty::c_void, }
ptr::null_mut(),
lvgl_sys::LV_HOR_RES_MAX * REFRESH_BUFFER_LEN as u32,
);
};
Self {} fn get_ptr(&self) -> Option<Box<lvgl_sys::lv_disp_buf_t>> {
if self
.initialized
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
let mut inner: MaybeUninit<lvgl_sys::lv_disp_buf_t> = MaybeUninit::uninit();
let refresh_buffer_guard = self.refresh_buffer.lock();
let draw_buf = unsafe {
lvgl_sys::lv_disp_buf_init(
inner.as_mut_ptr(),
refresh_buffer_guard.borrow_mut().as_mut_ptr() as *mut _ as *mut cty::c_void,
ptr::null_mut(),
lvgl_sys::LV_HOR_RES_MAX * REFRESH_BUFFER_LEN as u32,
);
inner.assume_init()
};
Some(Box::new(draw_buf))
} else {
None
}
} }
} }
@ -120,51 +138,54 @@ where
T: DrawTarget<C>, T: DrawTarget<C>,
C: PixelColor + From<Color>, C: PixelColor + From<Color>,
{ {
pub fn new(_display_buffer: DisplayBuffer, native_display: T) -> Self { pub fn new(draw_buffer: &'static DrawBuffer, native_display: T) -> Result<Self> {
let mut disp_drv = unsafe { let mut disp_drv = unsafe {
let mut inner = MaybeUninit::uninit(); let mut inner = MaybeUninit::uninit();
lvgl_sys::lv_disp_drv_init(inner.as_mut_ptr()); lvgl_sys::lv_disp_drv_init(inner.as_mut_ptr());
inner.assume_init() inner.assume_init()
}; };
// Safety: The variable `disp_buf` is statically allocated, no need to worry about this being dropped. // Safety: The variable `draw_buffer` is statically allocated, no need to worry about this being dropped.
unsafe { disp_drv.buffer = draw_buffer
disp_drv.buffer = DRAW_BUFFER.as_mut_ptr(); .get_ptr()
} .map(|ptr| Box::into_raw(ptr) as *mut _)
.ok_or(DisplayError::FailedToRegister)?;
let mut native_display = ManuallyDrop::new(DisplayUserData { let native_display = DisplayUserData {
display: native_display, display: native_display,
phantom: PhantomData, phantom: PhantomData,
}); };
disp_drv.user_data = &mut native_display as *mut _ as lvgl_sys::lv_disp_drv_user_data_t; disp_drv.user_data =
Box::into_raw(Box::new(native_display)) as *mut _ as lvgl_sys::lv_disp_drv_user_data_t;
// Sets trampoline pointer to the function implementation using the types (T, C) that // Sets trampoline pointer to the function implementation using the types (T, C) that
// are used in this instance of `DisplayDriver`. // are used in this instance of `DisplayDriver`.
disp_drv.flush_cb = Some(disp_flush_trampoline::<T, C>); disp_drv.flush_cb = Some(disp_flush_trampoline::<T, C>);
// We do not store any memory that can be accidentally deallocated by on the Rust side. // We do not store any memory that can be accidentally deallocated by on the Rust side.
Self { Ok(Self {
disp_drv, disp_drv,
phantom_color: PhantomData, phantom_color: PhantomData,
phantom_display: PhantomData, phantom_display: PhantomData,
} })
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub fn new_shared( pub fn new_shared(
_display_buffer: DisplayBuffer, draw_buffer: &'static DrawBuffer,
shared_native_display: SharedNativeDisplay<T>, shared_native_display: SharedNativeDisplay<T>,
) -> Self { ) -> Result<Self> {
let mut disp_drv = unsafe { let mut disp_drv = unsafe {
let mut inner = MaybeUninit::uninit(); let mut inner = MaybeUninit::uninit();
lvgl_sys::lv_disp_drv_init(inner.as_mut_ptr()); lvgl_sys::lv_disp_drv_init(inner.as_mut_ptr());
inner.assume_init() inner.assume_init()
}; };
// Safety: The variable `disp_buf` is statically allocated, no need to worry about this being dropped. // Safety: The variable `draw_buffer` is statically allocated, no need to worry about this being dropped.
unsafe { disp_drv.buffer = draw_buffer
disp_drv.buffer = DRAW_BUFFER.as_mut_ptr(); .get_ptr()
} .map(|ptr| Box::into_raw(ptr) as *mut _)
.ok_or(DisplayError::FailedToRegister)?;
let native_display = SharedDisplayUserData { let native_display = SharedDisplayUserData {
display: shared_native_display, display: shared_native_display,
@ -178,11 +199,11 @@ where
disp_drv.flush_cb = Some(shared_disp_flush_trampoline::<T, C>); disp_drv.flush_cb = Some(shared_disp_flush_trampoline::<T, C>);
// We do not store any memory that can be accidentally deallocated by on the Rust side. // We do not store any memory that can be accidentally deallocated by on the Rust side.
Self { Ok(Self {
disp_drv, disp_drv,
phantom_color: PhantomData, phantom_color: PhantomData,
phantom_display: PhantomData, phantom_display: PhantomData,
} })
} }
} }

View file

@ -55,20 +55,27 @@ pub mod widgets;
use core::sync::atomic::{AtomicBool, Ordering}; use core::sync::atomic::{AtomicBool, Ordering};
pub use functions::*; pub use functions::*;
pub use lv_core::*; pub use lv_core::*;
use parking_lot::Mutex;
pub use support::*; pub use support::*;
pub use ui::*; pub use ui::*;
lazy_static! { struct RunOnce(AtomicBool);
static ref MUTEX: Mutex<AtomicBool> = Mutex::new(AtomicBool::new(false));
impl RunOnce {
const fn new() -> Self {
Self(AtomicBool::new(false))
}
fn swap_and_get(&self) -> bool {
self.0
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
}
} }
static LVGL_INITIALIZED: RunOnce = RunOnce::new();
pub fn init() { pub fn init() {
let initialized = MUTEX.lock(); if LVGL_INITIALIZED.swap_and_get() {
if initialized
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
unsafe { unsafe {
lvgl_sys::lv_init(); lvgl_sys::lv_init();
} }
@ -78,20 +85,19 @@ pub fn init() {
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
use super::*; use super::*;
use crate::display::Display; use crate::display::{Display, DrawBuffer};
use embedded_graphics::mock_display::MockDisplay; use embedded_graphics::mock_display::MockDisplay;
use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::pixelcolor::Rgb565;
pub(crate) fn initialize_test() { pub(crate) fn initialize_test() {
init(); init();
static RUN_ONCE: Mutex<Option<u8>> = parking_lot::const_mutex(None); static DRAW_BUFFER: DrawBuffer = DrawBuffer::new();
let mut run_once = RUN_ONCE.lock(); static ONCE_INIT: RunOnce = RunOnce::new();
if run_once.is_none() { if ONCE_INIT.swap_and_get() {
let embedded_graphics_display: MockDisplay<Rgb565> = Default::default(); let embedded_graphics_display: MockDisplay<Rgb565> = Default::default();
let _ = Display::register(embedded_graphics_display).unwrap(); let _ = Display::register(&DRAW_BUFFER, embedded_graphics_display).unwrap();
*run_once = Some(1);
} }
} }
} }