slint-ui / slint

Slint is a declarative GUI toolkit to build native user interfaces for Rust, C++, or JavaScript apps.
https://slint.dev
Other
16.95k stars 568 forks source link

need to know the buffer configuration to slint ui #5264

Closed MohaBeacon closed 4 months ago

MohaBeacon commented 4 months ago

![no_std]

![no_main]

extern crate alloc; use alloc::{rc::Rc, boxed::Box}; slint::include_modules!();

fn create_slint_app() -> AppWindow { let ui = AppWindow::new().expect("Failed to load UI");

let ui_handle = ui.as_weak();
ui.on_request_increase_value(move || {
    let ui = ui_handle.unwrap();
    ui.set_counter(ui.get_counter() + 1);
});
ui

}

use rp_pico as bsp;

use bsp::entry; use fugit::RateExtU32;

use display_interface_spi::SPIInterface; use embedded_graphics::prelude::*; use embedded_graphics::{ pixelcolor::Rgb565, primitives::{Circle, PrimitiveStyleBuilder, Rectangle, Triangle}, };

use bsp::hal::{ clocks::{init_clocks_and_plls, Clock}, gpio, pac, pwm, sio::Sio, spi, watchdog::Watchdog, };

use bsp::hal::prelude::*; use gc9a01a; use panichalt as ; use slint::platform::{Platform, software_renderer::MinimalSoftwareWindow}; use slint::platform::software_renderer::Rgb565Pixel;

[entry]

fn main() -> ! { use bsp::hal; use embedded_hal::timer; // Pull in any important traits use fugit::RateExtU32; use slint::platform::WindowEvent;

let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(
    slint::platform::software_renderer::RepaintBufferType::SwappedBuffers
);

const DISPLAY_WIDTH: usize = 240;
const DISPLAY_HEIGHT: usize = 240;
let mut buffer1 = [Rgb565Pixel(0); DISPLAY_WIDTH * DISPLAY_HEIGHT];
let mut buffer2 = [Rgb565Pixel(0); DISPLAY_WIDTH * DISPLAY_HEIGHT];

// -------- Setup Allocator --------
const HEAP_SIZE: usize = 200 * 1024;
static mut HEAP: [u8; HEAP_SIZE] = [0; HEAP_SIZE];
#[global_allocator]
static ALLOCATOR: embedded_alloc::Heap = embedded_alloc::Heap::empty();
unsafe { ALLOCATOR.init(&mut HEAP as *const u8 as usize, core::mem::size_of_val(&HEAP)) };

// -------- Setup peripherials --------
let mut pac = pac::Peripherals::take().unwrap();
let core = pac::CorePeripherals::take().unwrap();
let mut watchdog = Watchdog::new(pac.WATCHDOG);
let sio = Sio::new(pac.SIO);

// External high-speed crystal on the pico board is 12Mhz
let external_xtal_freq_hz = 12_000_000u32;
let clocks = init_clocks_and_plls(
    external_xtal_freq_hz,
    pac.XOSC,
    pac.CLOCKS,
    pac.PLL_SYS,
    pac.PLL_USB,
    &mut pac.RESETS,
    &mut watchdog,
)
.ok()
.unwrap();

let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());

let pins = bsp::Pins::new(
    pac.IO_BANK0,
    pac.PADS_BANK0,
    sio.gpio_bank0,
    &mut pac.RESETS,
);
let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS);

// These are implicitly used by the spi driver if they are in the correct mode
let _spi_sclk = pins.gpio10.into_mode::<gpio::FunctionSpi>();
let _spi_mosi = pins.gpio11.into_mode::<gpio::FunctionSpi>();
let spi_cs = pins.gpio9.into_push_pull_output();

// Create an SPI driver instance for the SPI1 device
let spi = spi::Spi::<_, _, 8>::new(pac.SPI1);

// Exchange the uninitialised SPI driver for an initialised one
let spi = spi.init(
    &mut pac.RESETS,
    clocks.peripheral_clock.freq(),
    8_000_000u32.Hz(),
    &embedded_hal::spi::MODE_0,
);

let dc_pin = pins.gpio8.into_push_pull_output();
let rst_pin = pins.gpio12.into_push_pull_output();

let spi_interface = SPIInterface::new(spi, dc_pin, spi_cs);

// initialize PWM for backlight
let pwm_slices = pwm::Slices::new(pac.PWM, &mut pac.RESETS);

// Configure PWM6
let mut pwm = pwm_slices.pwm4;
pwm.set_ph_correct();
pwm.enable();

// Output channel B on PWM6 to GPIO 13
let mut channel = pwm.channel_b;
channel.output_to(pins.led);

// Create display driver
let mut display = gc9a01a::GC9A01A::new(spi_interface, rst_pin, channel);
// Bring out of reset
display.reset(&mut delay).unwrap();
// Turn on backlight
display.set_backlight(55000);
// Initialize registers
display.initialize(&mut delay).unwrap();

let mut currently_displayed_buffer : &mut [_] = &mut buffer1;
let mut work_buffer : &mut [_] = &mut buffer2;

// -------- Setup the Slint backend --------
let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(Default::default());
slint::platform::set_platform(alloc::boxed::Box::new(MyPlatform {
    window: window.clone(),
    timer,
}))
.unwrap();

struct MyPlatform {
    window: alloc::rc::Rc<slint::platform::software_renderer::MinimalSoftwareWindow>,
    timer: hal::Timer,
}

impl slint::platform::Platform for MyPlatform {
    fn create_window_adapter(&self) -> Result<alloc::rc::Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
        Ok(self.window.clone())
    }
    fn duration_since_start(&self) -> core::time::Duration {
        core::time::Duration::from_micros(self.timer.get_counter_low().into())
    }

}

// -------- Configure the UI --------
// (need to be done after the call to slint::platform::set_platform)
let _ui = create_slint_app();

// -------- Event loop --------
let mut line = [slint::platform::software_renderer::Rgb565Pixel(0); 240];

loop {
    slint::platform::update_timers_and_animations();
    window.draw_if_needed(|renderer| {
        use embedded_graphics_core::prelude::*;
        struct DisplayWrapper<'a, T>(
            &'a mut T,
            &'a mut [slint::platform::software_renderer::Rgb565Pixel],
        );
        impl<T: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>
            slint::platform::software_renderer::LineBufferProvider for DisplayWrapper<'_, T>
        {
            type TargetPixel = slint::platform::software_renderer::Rgb565Pixel;
            fn process_line(
                &mut self,
                line: usize,
                range: core::ops::Range<usize>,
                render_fn: impl FnOnce(&mut [Self::TargetPixel]),
            ) {
                let rect = embedded_graphics_core::primitives::Rectangle::new(
                    Point::new(range.start as _, line as _),
                    Size::new(range.len() as _, 1),
                );
                render_fn(&mut self.1[range.clone()]);
                // NOTE! this is not an efficient way to send pixel to the screen, but it is kept simple on this template.
                // It would be much faster to use the DMA to send pixel in parallel.
                // See the example in https://github.com/slint-ui/slint/blob/master/examples/mcu-board-support/pico_st7789.rs 
                self.0
                    .fill_contiguous(
                        &rect,
                        self.1[range.clone()].iter().map(|p| {
                            embedded_graphics_core::pixelcolor::raw::RawU16::new(p.0).into()
                        }),
                    )
                    .map_err(drop)
                    .unwrap();
            }
        }
        renderer.render_by_line(DisplayWrapper(&mut display, &mut line));
    });

    if window.has_active_animations() {
        continue;
    }

    // TODO: we could save battery here by going to sleep up to
    //   slint::platform::duration_until_next_timer_update()
    // or until the next touch interrupt, whatever comes first
    // cortex_m::asm::wfe();
}
exit()

}

pub fn exit() -> ! { loop { cortex_m::asm::bkpt(); } }

[dependencies] embedded-hal = { version = "0.2.7", features = ["unproven"]} embedded-alloc = "0.5" embedded-graphics-core ="0.4.0" display-interface ="0.4.1" display-interface-spi = "0.4.1" gc9a01a = "0.1.1" cortex-m = "0.7" cortex-m-rt = "0.7" panic-halt = "0.2" tinybmp = "0.5" embedded-graphics = "0.8.0" rp-pico = "0.7" fugit = "0.3"

[dev-dependencies] cortex-m = "0.7" cortex-m-rt = "0.7" panic-halt = "0.2" tinybmp = "0.5" embedded-graphics = "0.8.0" rp-pico = "0.7" fugit = "0.3"

[dependencies.slint] version = "1.6.0" default-features = false features = ["compat-1-2", "unsafe-single-threaded", "libm", "renderer-software"]

[build-dependencies] slint-build = "1.6.0"

MohaBeacon commented 4 months ago

![no_std]

![no_main]

extern crate alloc; use core::ptr::addr_of_mut;

use alloc::{rc::Rc, boxed::Box}; slint::include_modules!();

fn create_slint_app() -> AppWindow { let ui = AppWindow::new().expect("Failed to load UI");

let ui_handle = ui.as_weak();
ui.on_request_increase_value(move || {
    let ui = ui_handle.unwrap();
    ui.set_counter(ui.get_counter() + 1);
});
ui

}

use rp_pico as bsp;

use bsp::entry; use fugit::RateExtU32;

use display_interface_spi::SPIInterface; use embedded_graphics::prelude::*; use embedded_graphics::{ pixelcolor::Rgb565, primitives::{Circle, PrimitiveStyleBuilder, Rectangle, Triangle}, };

use bsp::hal::{ clocks::{init_clocks_and_plls, Clock}, gpio, pac, pwm, sio::Sio, spi, watchdog::Watchdog, };

use bsp::hal::prelude::*; use gc9a01a; use panichalt as ; use slint::platform::{Platform, software_renderer::MinimalSoftwareWindow}; use slint::platform::software_renderer::Rgb565Pixel;

[entry]

fn main() -> ! { use bsp::hal; use embedded_hal::timer; // Pull in any important traits use fugit::RateExtU32; use slint::platform::WindowEvent;

// -------- Setup Allocator --------
const HEAP_SIZE: usize = 200 * 1024;
static mut HEAP: [u8; HEAP_SIZE] = [0; HEAP_SIZE];
#[global_allocator]
static ALLOCATOR: embedded_alloc::Heap = embedded_alloc::Heap::empty();
unsafe { ALLOCATOR.init(addr_of_mut!(HEAP) as *const u8 as usize, core::mem::size_of_val(&HEAP)) };

// -------- Setup peripherials --------
let mut pac = pac::Peripherals::take().unwrap();
let core = pac::CorePeripherals::take().unwrap();
let mut watchdog = Watchdog::new(pac.WATCHDOG);
let sio = Sio::new(pac.SIO);

// External high-speed crystal on the pico board is 12Mhz
let external_xtal_freq_hz = 12_000_000u32;
let clocks = init_clocks_and_plls(
    external_xtal_freq_hz,
    pac.XOSC,
    pac.CLOCKS,
    pac.PLL_SYS,
    pac.PLL_USB,
    &mut pac.RESETS,
    &mut watchdog,
)
.ok()
.unwrap();

let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());

let pins = bsp::Pins::new(
    pac.IO_BANK0,
    pac.PADS_BANK0,
    sio.gpio_bank0,
    &mut pac.RESETS,
);
let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS);

// These are implicitly used by the spi driver if they are in the correct mode
let _spi_sclk = pins.gpio10.into_mode::<gpio::FunctionSpi>();
let _spi_mosi = pins.gpio11.into_mode::<gpio::FunctionSpi>();
let spi_cs = pins.gpio9.into_push_pull_output();

// Create an SPI driver instance for the SPI1 device
let spi = spi::Spi::<_, _, 8>::new(pac.SPI1);

// Exchange the uninitialised SPI driver for an initialised one
let spi = spi.init(
    &mut pac.RESETS,
    clocks.peripheral_clock.freq(),
    8_000_000u32.Hz(),
    &embedded_hal::spi::MODE_0,
);

let dc_pin = pins.gpio8.into_push_pull_output();
let rst_pin = pins.gpio12.into_push_pull_output();

let spi_interface = SPIInterface::new(spi, dc_pin, spi_cs);

// initialize PWM for backlight
let pwm_slices = pwm::Slices::new(pac.PWM, &mut pac.RESETS);

// Configure PWM6
let mut pwm = pwm_slices.pwm4;
pwm.set_ph_correct();
pwm.enable();

// Output channel B on PWM6 to GPIO 13
let mut channel = pwm.channel_b;
channel.output_to(pins.led);

// Create display driver
let mut display = gc9a01a::GC9A01A::new(spi_interface, rst_pin, channel);
// Bring out of reset
display.reset(&mut delay).unwrap();
// Turn on backlight
display.set_backlight(55000);
// Initialize registers
display.initialize(&mut delay).unwrap();

// -------- Setup the Slint backend --------
let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(Default::default());
slint::platform::set_platform(alloc::boxed::Box::new(MyPlatform {
    window: window.clone(),
    timer,
}))
.unwrap();

struct MyPlatform {
    window: alloc::rc::Rc<slint::platform::software_renderer::MinimalSoftwareWindow>,
    timer: hal::Timer,
}

impl slint::platform::Platform for MyPlatform {
    fn create_window_adapter(&self) -> Result<alloc::rc::Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
        Ok(self.window.clone())
    }
    fn duration_since_start(&self) -> core::time::Duration {
        core::time::Duration::from_micros(self.timer.get_counter_low().into())
    }

}

// -------- Configure the UI --------
// (need to be done after the call to slint::platform::set_platform)
let _ui = create_slint_app();

// -------- Event loop --------
let mut line = [slint::platform::software_renderer::Rgb565Pixel(0); 240];

loop {
    slint::platform::update_timers_and_animations();
    window.draw_if_needed(|renderer| {
        use embedded_graphics_core::prelude::*;
        struct DisplayWrapper<'a, T>(
            &'a mut T,
            &'a mut [slint::platform::software_renderer::Rgb565Pixel],
        );
        impl<T: DrawTarget<Color = embedded_graphics_core::pixelcolor::Rgb565>>
            slint::platform::software_renderer::LineBufferProvider for DisplayWrapper<'_, T>
        {
            type TargetPixel = slint::platform::software_renderer::Rgb565Pixel;
            fn process_line(
                &mut self,
                line: usize,
                range: core::ops::Range<usize>,
                render_fn: impl FnOnce(&mut [Self::TargetPixel]),
            ) {
                let rect = embedded_graphics_core::primitives::Rectangle::new(
                    Point::new(range.start as _, line as _),
                    Size::new(range.len() as _, 1),
                );
                render_fn(&mut self.1[range.clone()]);
                // NOTE! this is not an efficient way to send pixel to the screen, but it is kept simple on this template.
                // It would be much faster to use the DMA to send pixel in parallel.
                // See the example in https://github.com/slint-ui/slint/blob/master/examples/mcu-board-support/pico_st7789.rs 
                self.0
                    .fill_contiguous(
                        &rect,
                        self.1[range.clone()].iter().map(|p| {
                            embedded_graphics_core::pixelcolor::raw::RawU16::new(p.0).into()
                        }),
                    )
                    .map_err(drop)
                    .unwrap();
            }
        }
        renderer.render_by_line(DisplayWrapper(&mut display, &mut line));
    });

    if window.has_active_animations() {
        continue;
    }

    // TODO: we could save battery here by going to sleep up to
    //   slint::platform::duration_until_next_timer_update()
    // or until the next touch interrupt, whatever comes first
    // cortex_m::asm::wfe();
}
exit()

}

pub fn exit() -> ! { loop { cortex_m::asm::bkpt(); } }

hunger commented 4 months ago

Thank you for taking the time to report your issue with Slint!

What is the problem here?

Maybe you could put the code into a repo on Github or elsewhere and put a link to that repo here? It is way easier to just check out something than to copy/paste things into possibly multiple files. The missing Cargo.toml makes it even harder to just try this out.

A short description of what you actually observe and what you expect to happen instead would also help.

MohaBeacon commented 4 months ago

i have tried to use https://docs.rs/gc9a01a/latest/gc9a01a/ it is working fine with draw line by line - but when trying to use buffer - embedded hal - not able to know how to // ... configure the screen driver to use buffer1 or buffer2 ... in the docs of mcu

MohaBeacon commented 4 months ago

[dependencies] embedded-hal = { version = "0.2.7", features = ["unproven"]} embedded-alloc = "0.5" embedded-graphics-core ="0.4.0" display-interface ="0.4.1" display-interface-spi = "0.4.1" shared-bus = "0.2.4" gc9a01a = "0.1.1" cortex-m = "0.7" cortex-m-rt = "0.7" panic-halt = "0.2" tinybmp = "0.5" embedded-graphics = "0.8.0" rp-pico = "0.7" fugit = "0.3"

hunger commented 4 months ago

Do you have enough memory to drive a buffer on the rp-pico?

MohaBeacon commented 4 months ago

Do you have enough memory to drive a buffer on the rp-pico?

yes i have, i feel my configuration to make it work is having a trouble. let me update and let you know on this.