Add LVGL Rust Global Allocator feature #48

Merged
rafaelcaricio merged 2 commits from lvgl-alloc-feature into master 2021-05-26 18:55:38 +00:00
16 changed files with 168 additions and 96 deletions

View file

@ -1,7 +1,6 @@
[workspace]
members = [
"lvgl",
"lvgl-sys",
"lvgl-codegen",
"examples"
"lvgl-sys",
]

View file

@ -1,18 +1,19 @@
<h1 align="center"> LittlevGL - Open-source Embedded GUI Library in Rust</h1>
<h1 align="center"> LVGL - Open-source Embedded GUI Library in Rust</h1>
![Original LittlevGL demo image](lv_demo.png)
![Original LVGL demo image](lv_demo.png)
<p align="center">
LittlevGL provides everything you need to create a Graphical User Interface (GUI) on embedded systems with easy-to-use graphical elements, beautiful visual effects and low memory footprint.
LVGL provides everything you need to create a Graphical User Interface (GUI) on embedded systems with easy-to-use graphical elements, beautiful visual effects and low memory footprint.
</p>
<p align="center">
LittlevGL is compatible with <samp>#![no_std]</samp> environments by default.
LVGL is compatible with <samp>#![no_std]</samp> environments by default.
</p>
<h4 align="center">
<a href="https://lvgl.io/">Official LittlevGL Website </a> &middot;
<a href="https://github.com/rafaelcaricio/lvgl-rs-wasm">Rust to WASM demo</a> &middot;
<a href="https://lvgl.io/">Official LVGL Website </a> &middot;
<a href="https://github.com/littlevgl/lvgl">C library repository</a> &middot;
<a href="https://lvgl.io/demos">Live demo</a>
<a href="https://lvgl.io/demos">Official live demos</a>
</h4>
---
@ -42,7 +43,7 @@ $ cargo add lvgl
The build requires the environment variable bellow to be set:
- `DEP_LV_CONFIG_PATH`: Path to the directory containing the `lv_conf.h` header file used for configuration of LittlevGL library.
- `DEP_LV_CONFIG_PATH`: Path to the directory containing the `lv_conf.h` header file used for configuration of LVGL library.
We recommend the `lv_conf.h` file to be in your project's root directory. If so, the command to build your project would be:
```shell script
@ -58,10 +59,17 @@ for `no_std`, so we need to use a workaround to build "lvgl-rs". The mainstrem i
```shell
$ DEP_LV_CONFIG_PATH=`pwd` cargo build -Zfeatures=build_dep
```
### LVGL Global Allocator
A [global allocator](https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html) for Rust leveraging the
[LVGL memory allocator](https://github.com/lvgl/lvgl/blob/master/src/misc/lv_mem.h) is provided, but not enabled
by default. Can be enabled by the feature `lvgl_alloc`. This will make all dynamic memory to be allocated by LVGL
internal memory manager.
## Running the demo
**Hint for macOS users**: Before you run the demos you need to make sure you have [libsdl](https://www.libsdl.org) installed on your machine. To install it, use HomeBrew:
**Hint for macOS users**: Before you run the demos you need to make sure you have [libsdl](https://www.libsdl.org)
installed on your machine. To install it, use HomeBrew:
```shell
$ brew install sdl2

View file

@ -1,34 +0,0 @@
[package]
name = "demo"
version = "0.1.0"
authors = ["Rafael Caricio <crates@caric.io>"]
edition = "2018"
publish = false
[dev-dependencies]
lvgl = { path = "../lvgl" }
lvgl-sys = { path = "../lvgl-sys" }
embedded-graphics = "0.6.2"
embedded-graphics-simulator = "0.2.1"
heapless = "0.5.5"
cstr_core = { version = "0.2.0", features = ["alloc"] }
[[example]]
name = "demo"
path = "demo.rs"
[[example]]
name = "bar"
path = "bar.rs"
[[example]]
name = "button_click"
path = "button_click.rs"
[[example]]
name = "gauge"
path = "gauge.rs"
[[example]]
name = "arc"
path = "arc.rs"

View file

@ -11,7 +11,31 @@ use lvgl::{LvError, Widget};
use lvgl_sys;
use std::time::Instant;
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
}
fn main() -> Result<(), LvError> {
println!("meminfo init: {:?}", mem_info());
run_arc_demo()?;
println!("meminfo end: {:?}", mem_info());
Ok(())
}
fn run_arc_demo() -> Result<(), LvError> {
let display: SimulatorDisplay<Rgb565> = SimulatorDisplay::new(Size::new(
lvgl_sys::LV_HOR_RES_MAX,
lvgl_sys::LV_VER_RES_MAX,
@ -58,6 +82,7 @@ fn main() -> Result<(), LvError> {
if i > 270 {
forward = if forward { false } else { true };
i = 1;
println!("meminfo running: {:?}", mem_info());
}
angle = if forward { angle + 1 } else { angle - 1 };
arc.set_end_angle(angle + 135)?;

View file

@ -88,7 +88,7 @@ fn main() -> Result<(), LvError> {
Ok(())
}
// Reference to native font for LittlevGL, defined in the file: "fonts_noto_sans_numeric_80.c"
// Reference to native font for LVGL, defined in the file: "fonts_noto_sans_numeric_80.c"
// TODO: Create a macro for defining a safe wrapper for fonts.
// Maybe sometihng like:
//

View file

@ -80,7 +80,7 @@ typedef int16_t lv_coord_t;
#define LV_MEM_CUSTOM 0
#if LV_MEM_CUSTOM == 0
/* Size of the memory used by `lv_mem_alloc` in bytes (>= 2kB)*/
# define LV_MEM_SIZE (32U * 1024U)
# define LV_MEM_SIZE (1048576U) // 1Mb
/* Complier prefix for a big array declaration */
# define LV_MEM_ATTR

View file

@ -1,6 +1,6 @@
[package]
name = "lvgl-sys"
description = "Raw bindings to the LittlevGL C library."
description = "Raw bindings to the LVGL C library."
version = "0.5.2"
authors = ["Rafael Caricio <crates.lvgl-sys@caric.io>"]
edition = "2018"

View file

@ -5,7 +5,7 @@ Rust raw bindings for LittlevGL library.
Build requires environment variables to be set:
- `DEP_LV_CONFIG_PATH`: Path to the directory containing the `lv_conf.h` header file used for configuration of LittlevGL library.
- `DEP_LV_CONFIG_PATH`: Path to the directory containing the `lv_conf.h` header file used for configuration of LVGL library.
We recommend the `lv_conf.h` file to be in your project's root directory. If so, the command to build your project would be:
```shell script

View file

@ -1,6 +1,6 @@
[package]
name = "lvgl"
description = "LittlevGL bindings for Rust. A powerful and easy-to-use embedded GUI with many widgets, advanced visual effects (opacity, antialiasing, animations) and low memory requirements (16K RAM, 64K Flash)."
description = "LVGL bindings for Rust. A powerful and easy-to-use embedded GUI with many widgets, advanced visual effects (opacity, antialiasing, animations) and low memory requirements (16K RAM, 64K Flash)."
version = "0.5.2"
authors = ["Rafael Caricio <crates.lvgl@caric.io>"]
edition = "2018"
@ -18,9 +18,40 @@ embedded-graphics = "0.6.2"
cstr_core = "0.2.3"
bitflags = "1.2.1"
[features]
alloc = ["cstr_core/alloc"]
lvgl_alloc = ["alloc"]
[build-dependencies]
quote = "1.0.9"
proc-macro2 = "1.0.24"
lvgl-codegen = { version = "0.5.2", path = "../lvgl-codegen" }
lvgl-sys = { version = "0.5.2", path = "../lvgl-sys" }
[dev-dependencies]
embedded-graphics-simulator = "0.2.1"
heapless = "0.5.5"
[[example]]
name = "demo"
path = "../examples/demo.rs"
required-features = ["alloc"]
[[example]]
name = "bar"
path = "../examples/bar.rs"
required-features = ["alloc"]
[[example]]
name = "button_click"
path = "../examples/button_click.rs"
required-features = ["alloc"]
[[example]]
name = "gauge"
path = "../examples/gauge.rs"
[[example]]
name = "arc"
path = "../examples/arc.rs"
required-features = ["lvgl_alloc"]

20
lvgl/src/allocator.rs Normal file
View file

@ -0,0 +1,20 @@
use core::alloc::{GlobalAlloc, Layout};
// Register the global allocator
#[global_allocator]
static ALLOCATOR: LvglAlloc = LvglAlloc;
pub struct LvglAlloc;
unsafe impl GlobalAlloc for LvglAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// Make sure LVGL is initialized!
crate::lvgl_init();
lvgl_sys::lv_mem_alloc(layout.size() as lvgl_sys::size_t) as *mut u8
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
crate::lvgl_init();
lvgl_sys::lv_mem_free(ptr as *const cty::c_void)
}
}

View file

@ -17,13 +17,52 @@
#[macro_use]
extern crate bitflags;
pub(crate) mod mem;
#[cfg(feature = "lvgl_alloc")]
extern crate alloc;
// We can ONLY use `alloc::boxed::Box` if `lvgl_alloc` is enabled.
// That is because we use `Box` to send memory references to LVGL. Since the global allocator, when
// `lvgl_alloc` feature is enabled, is the LVGL memory manager then everything is in LVGL
// managed memory anyways. In that case we can use the Rust's provided Box definition.
//
#[cfg(feature = "lvgl_alloc")]
use ::alloc::boxed::Box;
#[cfg(feature = "lvgl_alloc")]
mod allocator;
mod support;
mod ui;
#[macro_use]
mod lv_core;
pub mod widgets;
#[cfg(not(feature = "lvgl_alloc"))]
pub(crate) mod mem;
// When LVGL allocator is not used on the Rust code, we need a way to add objects to the LVGL
// managed memory. We implement a very simple `Box` that has the minimal features to copy memory
// safely to the LVGL managed memory.
//
#[cfg(not(feature = "lvgl_alloc"))]
use crate::mem::Box;
rafaelcaricio commented 2021-05-26 13:33:25 +00:00 (Migrated from github.com)
Review

We could enable the LVGL side to use Rust memory allocator, if it's available and the feature "alloc" enabled in lvgl-rs. That way we wouldn't need to implement this custom Box implementation for that case. We would still need this implementation for cases where all features in lvgl-rs are disabled and we cannot assume that LVGL is using the same memory space.

The problem is that LVGL C always support "dynamically" allocated memory backed by a static array-based backend, just like wee_alloc. LVGL-rs is a library and the choice of allocator must be made by the firmware/application developer. Besides that, the developer might decide that they don't want to have an allocator at all on their Rust firmware. LVGL C will still use their own memory management implementation.

One option is to always require users of LVGL-rs to define a global allocator in their project. This would be a limitation for some use cases?! In that scenario, we wouldn't need the code in mem module at all, since the memory space would be shared by LVGL C and Rust.

We could enable the LVGL side to [use Rust memory allocator](https://github.com/ezrosent/allocators-rs/tree/master/malloc-bind), if it's available and the feature "alloc" enabled in lvgl-rs. That way we wouldn't need to implement this custom `Box` implementation for that case. We would still need this implementation for cases where all features in lvgl-rs are disabled and we cannot assume that LVGL is using the same memory space. The problem is that LVGL C always support "dynamically" allocated memory backed by a static array-based backend, just like [wee_alloc](https://github.com/rustwasm/wee_alloc#about). LVGL-rs is a library and the choice of allocator must be made by the firmware/application developer. Besides that, the developer might decide that they don't want to have an allocator at all on their Rust firmware. LVGL C will still use their own memory management implementation. One option is to always require users of LVGL-rs to define a global allocator in their project. This would be a limitation for some use cases?! In that scenario, we wouldn't need the code in `mem` module at all, since the memory space would be shared by LVGL C and Rust.
pub use lv_core::*;
pub use support::*;
pub use ui::*;
use core::sync::atomic::{AtomicBool, Ordering};
// Initialize LVGL only once.
static LVGL_INITIALIZED: AtomicBool = AtomicBool::new(false);
pub(crate) fn lvgl_init() {
if LVGL_INITIALIZED
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
unsafe {
lvgl_sys::lv_init();
}
}
}

View file

@ -1,9 +1,9 @@
use crate::lv_core::style::Style;
use crate::mem::Box;
use crate::Box;
use crate::{Align, LvError, LvResult};
use core::ptr;
/// Represents a native LittlevGL object
/// Represents a native LVGL object
pub trait NativeObject {
/// Provide common way to access to the underlying native object pointer.
fn raw(&self) -> LvResult<ptr::NonNull<lvgl_sys::lv_obj_t>>;
@ -28,7 +28,7 @@ impl NativeObject for Obj {
}
}
/// A wrapper for all LittlevGL common operations on generic objects.
/// A wrapper for all LVGL common operations on generic objects.
pub trait Widget: NativeObject {
type SpecialEvent;
type Part: Into<u8>;
@ -146,8 +146,8 @@ macro_rules! define_object {
unsafe {
let mut raw = self.raw()?;
let obj = raw.as_mut();
let user_closure = $crate::mem::Box::new(f)?;
obj.user_data = $crate::mem::Box::into_raw(user_closure) as *mut cty::c_void;
let user_closure = $crate::Box::new(f);
obj.user_data = $crate::Box::into_raw(user_closure) as *mut cty::c_void;
lvgl_sys::lv_obj_set_event_cb(
obj,
lvgl_sys::lv_event_cb_t::Some($crate::support::event_callback::<Self, F>),

View file

@ -1,4 +1,4 @@
use crate::mem::Box;
use crate::Box;
use crate::{Color, LvResult, State};
use core::mem;
use cstr_core::CStr;
@ -31,7 +31,7 @@ impl Default for Style {
let raw = unsafe {
let mut style = mem::MaybeUninit::<lvgl_sys::lv_style_t>::uninit();
lvgl_sys::lv_style_init(style.as_mut_ptr());
Box::new(style.assume_init()).unwrap()
Box::new(style.assume_init())
};
Self { raw }
}

View file

@ -1,4 +1,3 @@
use crate::{LvError, LvResult};
use core::mem;
use core::ops::{Deref, DerefMut};
use core::ptr::NonNull;
@ -12,7 +11,7 @@ pub(crate) struct Box<T>(NonNull<T>);
impl<T> Box<T> {
/// Allocate memory using LVGL memory API and place `T` in the LVGL tracked memory.
pub fn new(value: T) -> LvResult<Box<T>> {
pub fn new(value: T) -> Box<T> {
let size = mem::size_of::<T>();
let inner = unsafe {
let ptr = lvgl_sys::lv_mem_alloc(size as lvgl_sys::size_t) as *mut T;
@ -29,9 +28,11 @@ impl<T> Box<T> {
p.as_ptr().write(value);
p
})
.ok_or(LvError::LvOOMemory)?
.unwrap_or_else(|| {
panic!("Could not allocate memory {} bytes", size);
})
};
Ok(Box(inner))
Box(inner)
}
pub fn into_raw(self) -> *mut T {
@ -71,32 +72,21 @@ impl<T> AsMut<T> for Box<T> {
#[cfg(test)]
mod test {
use super::*;
use std::sync::Once;
use std::vec::Vec;
static INIT_LVGL: Once = Once::new();
fn init() {
INIT_LVGL.call_once(|| {
unsafe {
lvgl_sys::lv_init();
};
});
}
#[test]
fn place_value_in_lv_mem() {
init();
crate::lvgl_init();
let v = Box::new(5).unwrap();
let v = Box::new(5);
drop(v);
let v = Box::new(10).unwrap();
let v = Box::new(10);
drop(v);
}
#[test]
fn place_complex_value_in_lv_mem() {
init();
crate::lvgl_init();
#[repr(C)]
#[derive(Debug)]
@ -119,11 +109,7 @@ mod test {
};
println!("{:?}", p);
let mut b = Box::new(p).unwrap_or_else(|_| {
let info = mem_info();
println!("mem info: {:?}", &info);
panic!("OOM");
});
let mut b = Box::new(p);
println!("memory address is {:p}", b.as_mut());
@ -159,7 +145,7 @@ mod test {
used_cnt: 0,
max_used: 0,
used_pct: 0,
frag_pct: 0
frag_pct: 0,
};
unsafe {
lvgl_sys::lv_mem_monitor(&mut info as *mut _);

View file

@ -65,7 +65,7 @@ impl From<Color> for Rgb565 {
}
}
/// Events are triggered in LittlevGL when something happens which might be interesting to
/// Events are triggered in LVGL when something happens which might be interesting to
/// the user, e.g. if an object:
/// - is clicked
/// - is dragged

View file

@ -1,4 +1,4 @@
use crate::mem::Box;
use crate::Box;
use crate::{Color, Event, LvError, LvResult, Obj, Widget};
use core::marker::PhantomData;
use core::mem::MaybeUninit;
@ -10,7 +10,7 @@ use embedded_graphics::pixelcolor::PixelColor;
use embedded_graphics::prelude::*;
use embedded_graphics::{drawable, DrawTarget};
// There can only be a single reference to LittlevGL library.
// There can only be a single reference to LVGL library.
static LVGL_IN_USE: AtomicBool = AtomicBool::new(false);
// TODO: Make this an external configuration
@ -23,13 +23,13 @@ where
T: DrawTarget<C>,
C: PixelColor + From<Color>,
{
// LittlevGL is not thread-safe by default.
// LVGL is not thread-safe by default.
_not_sync: PhantomData<*mut ()>,
// Later we can add possibility to have multiple displays by using `heapless::Vec`
display_data: Option<DisplayUserData<T, C>>,
}
// LittlevGL does not use thread locals.
// LVGL does not use thread locals.
unsafe impl<T, C> Send for UI<T, C>
where
T: DrawTarget<C>,
@ -47,9 +47,7 @@ where
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
unsafe {
lvgl_sys::lv_init();
}
crate::lvgl_init();
Ok(Self {
_not_sync: PhantomData,
display_data: None,
@ -75,15 +73,15 @@ where
// Initialize the display buffer
lvgl_sys::lv_disp_buf_init(
disp_buf.as_mut_ptr(),
Box::into_raw(Box::new(refresh_buffer1)?) as *mut cty::c_void,
Box::into_raw(Box::new(refresh_buffer2)?) as *mut cty::c_void,
Box::into_raw(Box::new(refresh_buffer1)) as *mut cty::c_void,
Box::into_raw(Box::new(refresh_buffer2)) as *mut cty::c_void,
lvgl_sys::LV_HOR_RES_MAX * REFRESH_BUFFER_LEN as u32,
);
// Basic initialization of the display driver
lvgl_sys::lv_disp_drv_init(disp_drv.as_mut_ptr());
let mut disp_drv = Box::new(disp_drv.assume_init())?;
let mut disp_drv = Box::new(disp_drv.assume_init());
// Assign the buffer to the display
disp_drv.buffer = Box::into_raw(Box::new(disp_buf.assume_init())?);
disp_drv.buffer = Box::into_raw(Box::new(disp_buf.assume_init()));
// Set your driver function
disp_drv.flush_cb = Some(display_callback_wrapper::<T, C>);
disp_drv.user_data = &mut self.display_data as *mut _ as *mut cty::c_void;
@ -166,12 +164,12 @@ unsafe extern "C" fn display_callback_wrapper<T, C>(
// TODO: Can we do anything when there is a error while flushing?
let _ = display_flush(&mut user_data.display, (x1, x2), (y1, y2), color_p);
}
// Indicate to LittlevGL that we are ready with the flushing
// Indicate to LVGL that we are ready with the flushing
lvgl_sys::lv_disp_flush_ready(disp_drv);
}
// We separate this display flush function to reduce the amount of unsafe code we need to write.
// This also provides a good separation of concerns, what is necessary from LittlevGL to work and
// This also provides a good separation of concerns, what is necessary from LVGL to work and
// what is the lvgl-rs wrapper responsibility.
fn display_flush<T, C>(
display: &mut T,