Allow to create statically allocated DrawBuffer
This commit is contained in:
parent
6ba1fcdaff
commit
00e2004822
7 changed files with 164 additions and 72 deletions
3
.github/workflows/rust.yml
vendored
3
.github/workflows/rust.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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)?)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue