Rotated UI rendering

This commit is contained in:
Jakub Hlusička 2025-12-31 22:24:26 +01:00
parent fc1aa617af
commit a9870fe133
10 changed files with 1197 additions and 231 deletions

1123
firmware2/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -49,7 +49,7 @@ itertools = { version = "0.14.0", default-features = false }
bytemuck = "1.24.0" bytemuck = "1.24.0"
slint = { version = "1.14.1", default-features = false, features = ["compat-1-2", "libm", "log", "unsafe-single-threaded", "renderer-software"]} slint = { version = "1.14.1", default-features = false, features = ["compat-1-2", "libm", "log", "unsafe-single-threaded", "renderer-software"]}
critical-section = "1.2.0" critical-section = "1.2.0"
shadow-rs = { version = "1.5.0", default-features = false } backtrace = { version = "0.3.76", default-features = false }
# Crates for serial UART CLI # Crates for serial UART CLI
embedded-cli = { version = "0.2.1", default-features = false, features = ["help", "macros"] } embedded-cli = { version = "0.2.1", default-features = false, features = ["help", "macros"] }
@ -62,7 +62,7 @@ const-gen = "1.6"
embuild = "0.33" embuild = "0.33"
cc = "1.2.9" cc = "1.2.9"
slint-build = "1.14.1" slint-build = "1.14.1"
shadow-rs = { version = "1.5.0", features = ["no_std"]} gix = { version = "0.76.0", default-features = false, features = ["max-performance", "status"] }
[[bin]] [[bin]]
name = "acid-firmware" name = "acid-firmware"

View file

@ -6,13 +6,24 @@ use std::{env, fs};
use const_gen::*; use const_gen::*;
use slint_build::{CompilerConfiguration, EmbedResourcesKind}; use slint_build::{CompilerConfiguration, EmbedResourcesKind};
use xz2::read::XzEncoder; use xz2::read::XzEncoder;
use shadow_rs::ShadowBuilder; // use shadow_rs::{BuildPattern, ShadowBuilder};
fn main() { fn main() {
ShadowBuilder::builder() if let Ok(repo) = gix::discover(env::var_os("CARGO_MANIFEST_DIR").unwrap().into_string().unwrap()) {
.deny_const(Default::default()) let commit_hash = repo.head_commit().unwrap().short_id().unwrap();
.build() println!("cargo:rustc-env=GIT_COMMIT_HASH={}", commit_hash);
.unwrap(); println!("cargo:rustc-env=GIT_COMMIT={}",
repo.find_tag(repo.head_id().unwrap())
.ok()
.map(|tag| format!("{} ({})", tag.decode().unwrap().name, commit_hash))
.unwrap_or_else(|| commit_hash.to_string())
);
}
// ShadowBuilder::builder()
// .build_pattern(BuildPattern::Lazy)
// .deny_const(Default::default())
// .build()
// .unwrap();
// Generate vial config at the root of project // Generate vial config at the root of project
println!("cargo:rerun-if-changed=vial.json"); println!("cargo:rerun-if-changed=vial.json");
@ -24,6 +35,8 @@ fn main() {
// println!("cargo:rustc-link-arg=-Tdefmt.x"); // println!("cargo:rustc-link-arg=-Tdefmt.x");
let slint_config = CompilerConfiguration::new() let slint_config = CompilerConfiguration::new()
// .with_scale_factor(4.0)
.with_style("cosmic-dark".to_string())
.embed_resources(EmbedResourcesKind::EmbedForSoftwareRenderer); .embed_resources(EmbedResourcesKind::EmbedForSoftwareRenderer);
slint_build::compile_with_config("ui/main.slint", slint_config).expect("Slint build failed"); slint_build::compile_with_config("ui/main.slint", slint_config).expect("Slint build failed");
slint_build::print_rustc_flags().unwrap() slint_build::print_rustc_flags().unwrap()

View file

@ -1,4 +1,6 @@
use core::fmt::Write;
use embedded_cli::cli::CliBuilder; use embedded_cli::cli::CliBuilder;
use embedded_cli::Command; use embedded_cli::Command;
use esp_hal::{Async, uart::{TxError, UartRx}}; use esp_hal::{Async, uart::{TxError, UartRx}};
@ -71,7 +73,7 @@ pub async fn run_console(mut uart_rx: UartRx<'_, Async>) {
// write!(cli.writer(), "Hello, {}", name.unwrap_or("World"))?; // write!(cli.writer(), "Hello, {}", name.unwrap_or("World"))?;
// } // }
Base::Version => { Base::Version => {
cli.writer().write_str(crate::build::CLAP_LONG_VERSION).unwrap(); cli.writer().write_fmt(format_args!("{} - {} - {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"), env!("GIT_COMMIT"))).unwrap();
} }
Base::Reset => { Base::Reset => {
cli.writer().write_str("Performing software reset.").unwrap(); cli.writer().write_str("Performing software reset.").unwrap();

View file

@ -1,6 +1,8 @@
use core::cell::RefCell; use core::cell::RefCell;
use core::fmt::Write; use core::fmt::Write;
use alloc::{string::String, vec::Vec};
use backtrace::{BytesOrWideString, Symbol, SymbolName};
use critical_section::{CriticalSection, Mutex}; use critical_section::{CriticalSection, Mutex};
use esp_hal::uart::UartTx; use esp_hal::uart::UartTx;
use esp_hal::Blocking; use esp_hal::Blocking;
@ -83,7 +85,7 @@ fn panic_handler(info: &core::panic::PanicInfo) -> ! {
use esp_backtrace::Backtrace; use esp_backtrace::Backtrace;
println!("{RED}"); println!("{RED}");
println!("====================== PANIC ======================"); println!("=============== CUSTOM PANIC HANDLER ==============");
println!("{info}{RESET}"); println!("{info}{RESET}");
println!(""); println!("");
println!("Backtrace:"); println!("Backtrace:");
@ -93,11 +95,39 @@ fn panic_handler(info: &core::panic::PanicInfo) -> ! {
for frame in backtrace.frames() { for frame in backtrace.frames() {
println!("0x{:x}", frame.program_counter()); println!("0x{:x}", frame.program_counter());
print_resolved_symbol(frame.program_counter());
} }
loop {} loop {}
} }
#[cfg(not(feature = "usb-log"))]
fn print_resolved_symbol(address: usize) {
unsafe {
backtrace::resolve_unsynchronized(address as _, |sym| {
println!("? {:x?}\n{}\n\n", sym.addr().map(|addr| addr as usize), sym.name().unwrap_or(SymbolName::new(b"(unknown)")));
// sym.filename_raw().unwrap_or(BytesOrWideString::Bytes(&[]));
// let segments: Vec<String> = vec![
// sym.addr().map(|a| format!("{:x?}", a as isize)),
// sym.name().map(|n| n.to_string()),
// sym.filename().map(|p| {
// format!(
// "{}{}",
// p.to_string_lossy(),
// sym.lineno()
// .map(|n| format!(" (line {})", n))
// .unwrap_or_else(|| "".to_string())
// )
// }),
// ]
// .into_iter()
// .flatten()
// .collect();
// segments.join("\n")
});
}
}
pub fn setup_alternative_logging(alt_uart: UartTx<'static, Blocking>, level_filter: LevelFilter) pub fn setup_alternative_logging(alt_uart: UartTx<'static, Blocking>, level_filter: LevelFilter)
{ {
critical_section::with(|cs| { critical_section::with(|cs| {

View file

@ -11,7 +11,6 @@ use core::time::Duration;
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::rc::Rc; use alloc::rc::Rc;
use alloc::vec; use alloc::vec;
use shadow_rs::shadow;
use bt_hci::controller::ExternalController; use bt_hci::controller::ExternalController;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use esp_alloc::{HeapRegion, MemoryCapability}; use esp_alloc::{HeapRegion, MemoryCapability};
@ -26,7 +25,7 @@ use esp_hal::lcd_cam::lcd::dpi::Dpi;
use esp_hal::mcpwm::{McPwm, PeripheralClockConfig}; use esp_hal::mcpwm::{McPwm, PeripheralClockConfig};
use esp_hal::psram::{FlashFreq, PsramConfig, PsramSize, SpiRamFreq, SpiTimingConfigCoreClock}; use esp_hal::psram::{FlashFreq, PsramConfig, PsramSize, SpiRamFreq, SpiTimingConfigCoreClock};
use esp_hal::rng::TrngSource; use esp_hal::rng::TrngSource;
use esp_hal::system::Stack; use esp_hal::system::{CpuControl, Stack};
use esp_hal::time::Instant; use esp_hal::time::Instant;
use esp_hal::timer::timg::TimerGroup; use esp_hal::timer::timg::TimerGroup;
use esp_hal::{Blocking, ram}; use esp_hal::{Blocking, ram};
@ -44,14 +43,17 @@ use rmk::{join_all};
use rmk::keyboard::Keyboard; use rmk::keyboard::Keyboard;
use rmk::storage::async_flash_wrapper; use rmk::storage::async_flash_wrapper;
use rmk::{initialize_keymap_and_storage, run_devices, run_rmk}; use rmk::{initialize_keymap_and_storage, run_devices, run_rmk};
use slint::platform::software_renderer::Rgb565Pixel; use slint::platform::WindowAdapter;
use slint::platform::software_renderer::{MinimalSoftwareWindow, RenderingRotation, RepaintBufferType, Rgb565Pixel, SoftwareRenderer};
use slint::{ComponentHandle, PhysicalSize, WindowSize}; use slint::{ComponentHandle, PhysicalSize, WindowSize};
use static_cell::StaticCell; use static_cell::StaticCell;
use ui::AppWindow; use ui::AppWindow;
use ui::window_adapter::SoftwareWindowAdapter;
use {esp_alloc as _, esp_backtrace as _}; use {esp_alloc as _, esp_backtrace as _};
use crate::matrix::IoeMatrix; use crate::matrix::IoeMatrix;
use crate::peripherals::st7701s::St7701s; use crate::peripherals::st7701s::St7701s;
use crate::ui::backend::{FramebufferPtr, SlintBackend};
use crate::vial::{VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID}; use crate::vial::{VIAL_KEYBOARD_DEF, VIAL_KEYBOARD_ID};
mod keymap; mod keymap;
@ -64,8 +66,6 @@ mod logging;
#[cfg(feature = "alt-log")] #[cfg(feature = "alt-log")]
mod console; mod console;
shadow!(build);
// This creates a default app-descriptor required by the esp-idf bootloader. // This creates a default app-descriptor required by the esp-idf bootloader.
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description> // For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
esp_bootloader_esp_idf::esp_app_desc!(); esp_bootloader_esp_idf::esp_app_desc!();
@ -267,10 +267,11 @@ async fn main(_spawner: Spawner) {
960, 960,
)); ));
let window_size = [framebuffer.width, framebuffer.height]; // let window_size = [framebuffer.width, framebuffer.height];
let window_size = [framebuffer.height, framebuffer.width];
let framebuffer_ptr = FramebufferPtr(framebuffer.as_target_pixels() as _); let framebuffer_ptr = FramebufferPtr(framebuffer.as_target_pixels() as _);
static SECOND_CORE_STACK: StaticCell<Stack<8192>> = StaticCell::new(); static SECOND_CORE_STACK: StaticCell<Stack<{8192 * 2}>> = StaticCell::new();
let second_core_stack = SECOND_CORE_STACK.init(Stack::new()); let second_core_stack = SECOND_CORE_STACK.init(Stack::new());
esp_rtos::start_second_core( esp_rtos::start_second_core(
peripherals.CPU_CTRL, peripherals.CPU_CTRL,
@ -381,50 +382,6 @@ impl Framebuffer {
} }
} }
struct SlintBackend {
window_size: [u32; 2],
window: RefCell<Option<Rc<slint::platform::software_renderer::MinimalSoftwareWindow>>>,
framebuffer: FramebufferPtr,
// peripherals: RefCell<Option<Peripherals>>,
}
struct FramebufferPtr(*mut [Rgb565Pixel]);
unsafe impl Send for FramebufferPtr {}
impl slint::platform::Platform for SlintBackend {
fn create_window_adapter(&self) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
let window = slint::platform::software_renderer::MinimalSoftwareWindow::new(
slint::platform::software_renderer::RepaintBufferType::ReusedBuffer,
);
window.set_size(WindowSize::Physical(PhysicalSize::new(self.window_size[0], self.window_size[1])));
self.window.replace(Some(window.clone()));
Ok(window)
}
fn duration_since_start(&self) -> Duration {
Duration::from_millis(Instant::now().duration_since_epoch().as_millis())
}
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
loop {
slint::platform::update_timers_and_animations();
if let Some(window) = self.window.borrow().clone() {
// window.try_dispatch_event(todo!())?;
window.draw_if_needed(|renderer| {
// TODO: Proper synchronization.
let framebuffer = unsafe { &mut *self.framebuffer.0 };
// TODO: Try using height to see if it rotates the screen correctly. Might need
// to swap dimensions elsewhere.
renderer.render(framebuffer, self.window_size[0] as usize);
});
}
}
}
}
// impl DrawTarget for Framebuffer { // impl DrawTarget for Framebuffer {
// type Color = Rgb565; // type Color = Rgb565;
// type Error = (); // type Error = ();

View file

@ -0,0 +1,56 @@
use core::{cell::RefCell, time::Duration};
use alloc::rc::Rc;
use esp_hal::time::Instant;
use log::info;
use slint::{PhysicalSize, WindowSize, platform::software_renderer::{RenderingRotation, RepaintBufferType, Rgb565Pixel, SoftwareRenderer}};
use super::window_adapter::SoftwareWindowAdapter;
pub struct FramebufferPtr(pub *mut [Rgb565Pixel]);
unsafe impl Send for FramebufferPtr {}
pub struct SlintBackend {
pub window_size: [u32; 2],
pub window: RefCell<Option<Rc<SoftwareWindowAdapter>>>,
pub framebuffer: FramebufferPtr,
// pub peripherals: RefCell<Option<Peripherals>>,
}
impl slint::platform::Platform for SlintBackend {
fn create_window_adapter(&self) -> Result<Rc<dyn slint::platform::WindowAdapter>, slint::PlatformError> {
// TODO: Custom window adapter impl needs to be implemented, so we can change `rotation` on
// `SoftwareRenderer`.
let renderer = SoftwareRenderer::new_with_repaint_buffer_type(RepaintBufferType::ReusedBuffer /* TODO: Implement a swapchain */);
renderer.set_rendering_rotation(RenderingRotation::Rotate270);
let window = SoftwareWindowAdapter::new(renderer);
// window.set_scale_factor(4.0);
window.set_size(WindowSize::Physical(PhysicalSize::new(self.window_size[0], self.window_size[1])));
self.window.replace(Some(window.clone()));
Ok(window)
}
fn duration_since_start(&self) -> Duration {
Duration::from_millis(Instant::now().duration_since_epoch().as_millis())
}
fn run_event_loop(&self) -> Result<(), slint::PlatformError> {
loop {
slint::platform::update_timers_and_animations();
if let Some(window) = self.window.borrow().clone() {
// window.try_dispatch_event(todo!())?;
window.draw_if_needed(|renderer| {
// TODO: Proper synchronization.
let framebuffer = unsafe { &mut *self.framebuffer.0 };
// TODO: Try using height to see if it rotates the screen correctly. Might need
// to swap dimensions elsewhere.
renderer.render(framebuffer, self.window_size[1] as usize);
info!("UI rendered.");
});
}
}
}
}

View file

@ -1,3 +1,6 @@
// #![cfg_attr(not(feature = "simulator"), no_main)] // #![cfg_attr(not(feature = "simulator"), no_main)]
pub mod window_adapter;
pub mod backend;
slint::include_modules!(); slint::include_modules!();

View file

@ -0,0 +1,86 @@
use core::{cell::Cell, ops::{Deref, DerefMut}};
use alloc::rc::{Rc, Weak};
use slint::{PhysicalSize, Window, WindowSize, platform::{Renderer, WindowAdapter, WindowEvent, software_renderer::{RepaintBufferType, SoftwareRenderer}}};
/// This is a minimal adapter for a Window that doesn't have any other feature than rendering
/// using the software renderer.
pub struct SoftwareWindowAdapter {
pub window: Window,
pub renderer: SoftwareRenderer,
needs_redraw: Cell<bool>,
size: Cell<PhysicalSize>,
}
impl SoftwareWindowAdapter {
/// Instantiate a new MinimalWindowAdaptor
///
/// The `repaint_buffer_type` parameter specify what kind of buffer are passed to the [`SoftwareRenderer`]
pub fn new(renderer: SoftwareRenderer) -> Rc<Self> {
Rc::new_cyclic(|w: &Weak<Self>| Self {
window: Window::new(w.clone()),
renderer,
needs_redraw: Cell::new(true),
size: Default::default(),
})
}
/// If the window needs to be redrawn, the callback will be called with the
/// [renderer](SoftwareRenderer) that should be used to do the drawing.
///
/// [`SoftwareRenderer::render()`] or [`SoftwareRenderer::render_by_line()`] should be called
/// in that callback.
///
/// Return true if something was redrawn.
pub fn draw_if_needed(&self, render_callback: impl FnOnce(&SoftwareRenderer)) -> bool {
if self.needs_redraw.replace(false) /*|| self.renderer.rendering_metrics_collector.is_some()*/ {
render_callback(&self.renderer);
true
} else {
false
}
}
pub fn set_scale_factor(&self, scale_factor: f32) {
self.window.dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor });
}
}
impl WindowAdapter for SoftwareWindowAdapter {
fn window(&self) -> &Window {
&self.window
}
fn renderer(&self) -> &dyn Renderer {
&self.renderer
}
fn size(&self) -> PhysicalSize {
self.size.get()
}
fn set_size(&self, size: WindowSize) {
let sf = self.window.scale_factor();
self.size.set(size.to_physical(sf));
self.window
.dispatch_event(WindowEvent::Resized { size: size.to_logical(sf) })
}
fn request_redraw(&self) {
self.needs_redraw.set(true);
}
}
impl Deref for SoftwareWindowAdapter {
type Target = Window;
fn deref(&self) -> &Self::Target {
&self.window
}
}
impl DerefMut for SoftwareWindowAdapter {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.window
}
}

View file

@ -1,18 +1,40 @@
import { Button, VerticalBox } from "std-widgets.slint"; import { Button, VerticalBox, LineEdit, GridBox } from "std-widgets.slint";
export component AppWindow inherits Window { export component AppWindow inherits Window {
y: 0px;
in-out property <int> counter: 42; in-out property <int> counter: 42;
default-font-family: "IBM Plex Sans";
default-font-size: 16pt;
callback request-increase-value(); callback request-increase-value();
VerticalBox { GridBox {
Text { height: 240px;
text: "Counter: \{root.counter}"; padding: 0px;
padding-top: 8px;
width: 960px;
VerticalBox {
Text {
text: "Counter: \{root.counter}";
}
Button {
text: "Increase value";
clicked => {
root.request-increase-value();
}
}
LineEdit {
input-type: InputType.password;
text: "LineEdit";
}
} }
Button { Button {
text: "Increase value"; text: "Button";
clicked => { }
root.request-increase-value();
} Button {
text: "Button";
} }
} }
} }