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
- 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,
};
use lvgl;
use lvgl::display::{DefaultDisplay, Display};
use lvgl::display::{DefaultDisplay, Display, DrawBuffer};
use lvgl::style::Style;
use lvgl::widgets::{Label, LabelAlign};
use lvgl::{Align, Color, LvError, Part, State, Widget, UI};
@ -16,6 +16,8 @@ use std::thread;
use std::thread::sleep;
use std::time::{Duration, Instant};
static DRAW_BUFFER: DrawBuffer = DrawBuffer::new();
fn main() -> Result<(), LvError> {
lvgl::init();
@ -29,11 +31,13 @@ fn main() -> Result<(), LvError> {
// Implement and register your 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
let mut screen = DefaultDisplay::get_scr_act()?;
println!("Before all widgets: {:?}", mem_info());
let mut screen_style = Style::default();
screen_style.set_bg_color(State::DEFAULT, Color::from_rgb((0, 0, 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));
}
println!("Final part of demo app: {:?}", mem_info());
Ok(())
}
@ -109,3 +116,20 @@ fn main() -> Result<(), LvError> {
extern "C" {
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
/// 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.
#[derive(Clone, Debug)]
pub struct CParameter {
/// 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.
c_type: String,
pub c_type: String,
/// 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.
mutable: bool,
pub mutable: bool,
/// We need to check if the value is optional in the C code. We need to check
/// the function comments for this information.
/// - "if NULL then"
/// - "if not NULL then"
/// - "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) => {
let val = if self.is_str() {
quote!(&cstr_core::CStr)
} else if (self.literal_name.contains("lv_")) {
} else if self.literal_name.contains("lv_") {
let ident = format_ident!("{}", name);
quote!(&#ident)
} else {
@ -672,9 +672,8 @@ mod test {
}
pub fn new() -> crate::LvResult<Self> {
Ok(Self::create_at(
&mut crate::display::DefaultDisplay::get_scr_act(),
)?)
let mut parent = 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"
parking_lot = "0.11.1"
lazy_static = "1.4.0"
heapless = "0.7.1"
[features]
alloc = ["cstr_core/alloc"]
@ -32,7 +33,6 @@ lvgl-sys = { version = "0.5.2", path = "../lvgl-sys" }
[dev-dependencies]
embedded-graphics-simulator = "0.2.1"
heapless = "0.5.5"
[[example]]
name = "app"

View file

@ -2,15 +2,16 @@ use crate::functions::CoreError;
use crate::Box;
use crate::{disp_drv_register, disp_get_default, get_str_act};
use crate::{Color, Obj};
use core::cell::{Cell, RefCell};
use core::marker::PhantomData;
use core::mem::{ManuallyDrop, MaybeUninit};
use core::mem::MaybeUninit;
use core::ptr::NonNull;
use core::sync::atomic::{AtomicBool, Ordering};
use core::{ptr, result};
use embedded_graphics::drawable;
use embedded_graphics::prelude::*;
#[cfg(feature = "alloc")]
use parking_lot::Mutex; // TODO: Can this really be used in no_std envs with alloc?
use parking_lot::const_mutex;
use parking_lot::Mutex;
#[cfg(feature = "alloc")]
use alloc::sync::Arc;
@ -20,11 +21,6 @@ const REFRESH_BUFFER_LEN: usize = 2;
// Declare a buffer for the refresh rate
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)]
pub enum DisplayError {
NotAvailable,
@ -46,23 +42,26 @@ impl Display {
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
T: DrawTarget<C>,
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)?)
}
#[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
T: DrawTarget<C>,
C: PixelColor + From<Color>,
{
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)?)
}
@ -73,7 +72,7 @@ impl Display {
impl Default for Display {
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 {
pub fn new() -> Self {
unsafe {
lvgl_sys::lv_disp_buf_init(
DRAW_BUFFER.as_mut_ptr(),
&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,
);
};
impl DrawBuffer {
pub const fn new() -> Self {
Self {
initialized: AtomicBool::new(false),
refresh_buffer: const_mutex(RefCell::new(heapless::Vec::new())),
}
}
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>,
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 inner = MaybeUninit::uninit();
lvgl_sys::lv_disp_drv_init(inner.as_mut_ptr());
inner.assume_init()
};
// Safety: The variable `disp_buf` is statically allocated, no need to worry about this being dropped.
unsafe {
disp_drv.buffer = DRAW_BUFFER.as_mut_ptr();
}
// Safety: The variable `draw_buffer` is statically allocated, no need to worry about this being dropped.
disp_drv.buffer = draw_buffer
.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,
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
// are used in this instance of `DisplayDriver`.
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.
Self {
Ok(Self {
disp_drv,
phantom_color: PhantomData,
phantom_display: PhantomData,
}
})
}
#[cfg(feature = "alloc")]
pub fn new_shared(
_display_buffer: DisplayBuffer,
draw_buffer: &'static DrawBuffer,
shared_native_display: SharedNativeDisplay<T>,
) -> Self {
) -> Result<Self> {
let mut disp_drv = unsafe {
let mut inner = MaybeUninit::uninit();
lvgl_sys::lv_disp_drv_init(inner.as_mut_ptr());
inner.assume_init()
};
// Safety: The variable `disp_buf` is statically allocated, no need to worry about this being dropped.
unsafe {
disp_drv.buffer = DRAW_BUFFER.as_mut_ptr();
}
// Safety: The variable `draw_buffer` is statically allocated, no need to worry about this being dropped.
disp_drv.buffer = draw_buffer
.get_ptr()
.map(|ptr| Box::into_raw(ptr) as *mut _)
.ok_or(DisplayError::FailedToRegister)?;
let native_display = SharedDisplayUserData {
display: shared_native_display,
@ -178,11 +199,11 @@ where
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.
Self {
Ok(Self {
disp_drv,
phantom_color: PhantomData,
phantom_display: PhantomData,
}
})
}
}

View file

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