diff --git a/core/embed/bootloader/bootui.c b/core/embed/bootloader/bootui.c index 90aee5ab2..5794502e7 100644 --- a/core/embed/bootloader/bootui.c +++ b/core/embed/bootloader/bootui.c @@ -123,7 +123,7 @@ void ui_screen_boot(const vendor_header *const vhdr, #endif - PIXELDATA_DIRTY(); + display_pixeldata_dirty(); display_refresh(); } @@ -134,7 +134,7 @@ void ui_screen_boot_wait(int wait_seconds) { boot_background); display_text_center(DISPLAY_RESX / 2, DISPLAY_RESY - 5, wait_str, -1, FONT_NORMAL, COLOR_BL_BG, boot_background); - PIXELDATA_DIRTY(); + display_pixeldata_dirty(); display_refresh(); } @@ -182,7 +182,7 @@ void ui_screen_boot_click(void) { display_bar(0, BOOT_WAIT_Y_TOP, DISPLAY_RESX, BOOT_WAIT_HEIGHT, boot_background); bld_continue_label(boot_background); - PIXELDATA_DIRTY(); + display_pixeldata_dirty(); display_refresh(); ui_click(); } diff --git a/core/embed/lib/colors.c b/core/embed/lib/colors.c index abda0331a..f12eff854 100644 --- a/core/embed/lib/colors.c +++ b/core/embed/lib/colors.c @@ -24,6 +24,7 @@ uint32_t rgb565_to_rgb888(uint16_t color) { res |= ((((((uint32_t)color & 0xF800) >> 11) * 527) + 23) >> 6) << 16; res |= ((((((uint32_t)color & 0x07E0) >> 5) * 259) + 33) >> 6) << 8; res |= ((((((uint32_t)color & 0x001F) >> 0) * 527) + 23) >> 6) << 0; + res |= 0xFF000000; return res; } @@ -37,5 +38,5 @@ uint32_t interpolate_rgb888_color(uint32_t color0, uint32_t color1, ((color1 & 0xFF00) >> 8) * (15 - step)) / 15; cb = ((color0 & 0x00FF) * step + (color1 & 0x00FF) * (15 - step)) / 15; - return (cr << 16) | (cg << 8) | cb; + return (cr << 16) | (cg << 8) | cb | 0xFF000000; } diff --git a/core/embed/lib/display.c b/core/embed/lib/display.c index 71eede34c..7cb7b79a3 100644 --- a/core/embed/lib/display.c +++ b/core/embed/lib/display.c @@ -51,6 +51,9 @@ static inline void clamp_coords(int x, int y, int w, int h, int *x0, int *y0, } void display_clear(void) { +#ifdef DISPLAY_EFFICIENT_CLEAR + display_efficient_clear(); +#else const int saved_orientation = display_get_orientation(); display_reset_state(); @@ -68,7 +71,8 @@ void display_clear(void) { // if valid, go back to the saved orientation display_orientation(saved_orientation); // flag display for refresh - PIXELDATA_DIRTY(); +#endif + display_pixeldata_dirty(); } void display_bar(int x, int y, int w, int h, uint16_t c) { @@ -80,104 +84,7 @@ void display_bar(int x, int y, int w, int h, uint16_t c) { for (int i = 0; i < (x1 - x0 + 1) * (y1 - y0 + 1); i++) { PIXELDATA(c); } - PIXELDATA_DIRTY(); -} - -#define CORNER_RADIUS 16 - -static const uint8_t cornertable[CORNER_RADIUS * CORNER_RADIUS] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 9, 12, 14, 15, 0, 0, 0, - 0, 0, 0, 0, 0, 3, 9, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, - 0, 8, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 3, 12, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 3, 14, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 0, 0, 0, 3, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 0, 0, 0, 12, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, - 8, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 3, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 9, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 1, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 5, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 9, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 12, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, -}; - -void display_bar_radius(int x, int y, int w, int h, uint16_t c, uint16_t b, - uint8_t r) { - if (r != 2 && r != 4 && r != 8 && r != 16) { - return; - } else { - r = 16 / r; - } - uint16_t colortable[16] = {0}; - set_color_table(colortable, c, b); - x += DISPLAY_OFFSET.x; - y += DISPLAY_OFFSET.y; - int x0 = 0, y0 = 0, x1 = 0, y1 = 0; - clamp_coords(x, y, w, h, &x0, &y0, &x1, &y1); - display_set_window(x0, y0, x1, y1); - for (int j = y0; j <= y1; j++) { - for (int i = x0; i <= x1; i++) { - int rx = i - x; - int ry = j - y; - if (rx < CORNER_RADIUS / r && ry < CORNER_RADIUS / r) { - uint8_t c = cornertable[rx * r + ry * r * CORNER_RADIUS]; - PIXELDATA(colortable[c]); - } else if (rx < CORNER_RADIUS / r && ry >= h - CORNER_RADIUS / r) { - uint8_t c = cornertable[rx * r + (h - 1 - ry) * r * CORNER_RADIUS]; - PIXELDATA(colortable[c]); - } else if (rx >= w - CORNER_RADIUS / r && ry < CORNER_RADIUS / r) { - uint8_t c = cornertable[(w - 1 - rx) * r + ry * r * CORNER_RADIUS]; - PIXELDATA(colortable[c]); - } else if (rx >= w - CORNER_RADIUS / r && ry >= h - CORNER_RADIUS / r) { - uint8_t c = - cornertable[(w - 1 - rx) * r + (h - 1 - ry) * r * CORNER_RADIUS]; - PIXELDATA(colortable[c]); - } else { - PIXELDATA(c); - } - } - } - PIXELDATA_DIRTY(); -} - -void display_bar_radius_buffer(int x, int y, int w, int h, uint8_t r, - buffer_text_t *buffer) { - if (h > TEXT_BUFFER_HEIGHT) { - return; - } - if (r != 2 && r != 4 && r != 8 && r != 16) { - return; - } else { - r = 16 / r; - } - int x0 = 0, y0 = 0, x1 = 0, y1 = 0; - clamp_coords(x, y, w, h, &x0, &y0, &x1, &y1); - for (int j = y0; j <= y1; j++) { - for (int i = x0; i <= x1; i++) { - int rx = i - x; - int ry = j - y; - int p = j * DISPLAY_RESX + i; - uint8_t c = 0; - if (rx < CORNER_RADIUS / r && ry < CORNER_RADIUS / r) { - c = cornertable[rx * r + ry * r * CORNER_RADIUS]; - } else if (rx < CORNER_RADIUS / r && ry >= h - CORNER_RADIUS / r) { - c = cornertable[rx * r + (h - 1 - ry) * r * CORNER_RADIUS]; - } else if (rx >= w - CORNER_RADIUS / r && ry < CORNER_RADIUS / r) { - c = cornertable[(w - 1 - rx) * r + ry * r * CORNER_RADIUS]; - } else if (rx >= w - CORNER_RADIUS / r && ry >= h - CORNER_RADIUS / r) { - c = cornertable[(w - 1 - rx) * r + (h - 1 - ry) * r * CORNER_RADIUS]; - } else { - c = 15; - } - int b = p / 2; - if (p % 2) { - buffer->buffer[b] |= c << 4; - } else { - buffer->buffer[b] |= (c); - } - } - } + display_pixeldata_dirty(); } void display_text_render_buffer(const char *text, int textlen, int font, @@ -357,7 +264,7 @@ void display_print(const char *text, int textlen) { PIXELDATA(display_print_bgcolor); } } - PIXELDATA_DIRTY(); + display_pixeldata_dirty(); display_refresh(); } @@ -384,6 +291,67 @@ void display_printf(const char *fmt, ...) { #endif // TREZOR_PRINT_DISABLE +#ifdef FRAMEBUFFER +static void display_text_render(int x, int y, const char *text, int textlen, + int font, uint16_t fgcolor, uint16_t bgcolor) { + // determine text length if not provided + if (textlen < 0) { + textlen = strlen(text); + } + + int total_adv = 0; + + uint32_t *fb = display_get_fb_addr(); + + uint16_t colortable[16] = {0}; + set_color_table(colortable, fgcolor, bgcolor); + + // render glyphs + for (int c_idx = 0; c_idx < textlen; c_idx++) { + const uint8_t *g = font_get_glyph(font, (uint8_t)text[c_idx]); + if (!g) continue; + const uint8_t w = g[0]; // width + const uint8_t h = g[1]; // height + const uint8_t adv = g[2]; // advance + const uint8_t bearX = g[3]; // bearingX + const uint8_t bearY = g[4]; // bearingY + if (w && h) { + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + const int a = i + j * w; +#if TREZOR_FONT_BPP == 1 + const uint8_t c = ((g[5 + a / 8] >> (7 - (a % 8) * 1)) & 0x01) * 15; +#elif TREZOR_FONT_BPP == 2 + const uint8_t c = ((g[5 + a / 4] >> (6 - (a % 4) * 2)) & 0x03) * 5; +#elif TREZOR_FONT_BPP == 4 + const uint8_t c = (g[5 + a / 2] >> (4 - (a % 2) * 4)) & 0x0F; +#elif TREZOR_FONT_BPP == 8 +#error Rendering into buffer not supported when using TREZOR_FONT_BPP = 8 + // const uint8_t c = g[5 + a / 1] >> 4; +#else +#error Unsupported TREZOR_FONT_BPP value +#endif + + int x_pos = x + i + total_adv + bearX; + int y_pos = y + j - bearY; + + if (y_pos < 0) continue; + + if (x_pos >= DISPLAY_FRAMEBUFFER_WIDTH || x_pos < 0 || + y_pos >= DISPLAY_FRAMEBUFFER_HEIGHT || y_pos < 0) { + continue; + } + + display_pixel((uint8_t *)fb, x_pos, y_pos, colortable[c]); + } + } + } + total_adv += adv; + } + display_pixeldata_dirty(); +} + +#else static void display_text_render(int x, int y, const char *text, int textlen, int font, uint16_t fgcolor, uint16_t bgcolor) { // determine text length if not provided @@ -431,8 +399,9 @@ static void display_text_render(int x, int y, const char *text, int textlen, } x += adv; } - PIXELDATA_DIRTY(); + display_pixeldata_dirty(); } +#endif void display_text(int x, int y, const char *text, int textlen, int font, uint16_t fgcolor, uint16_t bgcolor) { @@ -552,7 +521,7 @@ void display_qrcode(int x, int y, const char *data, uint8_t scale) { } } } - PIXELDATA_DIRTY(); + display_pixeldata_dirty(); } #endif @@ -610,5 +579,3 @@ void display_utf8_substr(const char *buf_start, size_t buf_len, int char_off, *out_start = buf_start + i_start; *out_len = i - i_start; } - -void display_pixeldata_dirty(void) { PIXELDATA_DIRTY(); } diff --git a/core/embed/lib/display.h b/core/embed/lib/display.h index 08f4de220..bdad4717b 100644 --- a/core/embed/lib/display.h +++ b/core/embed/lib/display.h @@ -37,24 +37,11 @@ typedef enum { TOIF_GRAYSCALE_EH = 3, // even hi } toif_format_t; -// provided by port - -void display_init(void); -void display_init_seq(void); -void display_refresh(void); -const char *display_save(const char *prefix); -void display_clear_save(void); - // provided by common void display_clear(void); void display_bar(int x, int y, int w, int h, uint16_t c); -void display_bar_radius(int x, int y, int w, int h, uint16_t c, uint16_t b, - uint8_t r); -void display_bar_radius_buffer(int x, int y, int w, int h, uint8_t r, - buffer_text_t *buffer); - bool display_toif_info(const uint8_t *buf, uint32_t len, uint16_t *out_w, uint16_t *out_h, toif_format_t *out_format); @@ -86,7 +73,4 @@ void display_fade(int start, int end, int delay); void display_utf8_substr(const char *buf_start, size_t buf_len, int char_off, int char_len, const char **out_start, int *out_len); -// pixeldata accessor -void display_pixeldata_dirty(); - #endif diff --git a/core/embed/lib/display_interface.h b/core/embed/lib/display_interface.h index 5b9c3ca82..2b48c7235 100644 --- a/core/embed/lib/display_interface.h +++ b/core/embed/lib/display_interface.h @@ -24,15 +24,28 @@ #include "common.h" #include TREZOR_BOARD +#ifndef DISPLAY_FRAMEBUFFER_OFFSET_Y +#define DISPLAY_FRAMEBUFFER_OFFSET_Y 0 +#endif + +#ifndef DISPLAY_FRAMEBUFFER_OFFSET_X +#define DISPLAY_FRAMEBUFFER_OFFSET_X 0 +#endif + +#ifndef DISPLAY_FRAMEBUFFER_WIDTH +#define DISPLAY_FRAMEBUFFER_WIDTH 0 +#endif + +#ifndef DISPLAY_FRAMEBUFFER_HEIGHT +#define DISPLAY_FRAMEBUFFER_HEIGHT 0 +#endif + #ifndef PIXELDATA #define PIXELDATA(c) display_pixeldata(c) #endif void display_pixeldata(uint16_t c); - -#ifndef PIXELDATA_DIRTY -#define PIXELDATA_DIRTY() -#endif +void display_pixeldata_dirty(void); void display_reset_state(); @@ -48,4 +61,10 @@ void display_refresh(void); const char *display_save(const char *prefix); void display_clear_save(void); +void display_efficient_clear(void); +uint32_t *display_get_fb_addr(void); +uint8_t *display_get_wr_addr(void); +void display_shift_window(uint16_t pixels); +uint16_t display_get_window_offset(void); + #endif //_DISPLAY_INTERFACE_H diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml index ae829ef74..dd3e0ea16 100644 --- a/core/embed/rust/Cargo.toml +++ b/core/embed/rust/Cargo.toml @@ -14,6 +14,8 @@ micropython = [] protobuf = ["micropython"] ui = [] dma2d = [] +framebuffer = [] +framebuffer32bit = [] ui_debug = [] ui_bounds = [] bootloader = [] diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs index 357211d8d..ccc22b346 100644 --- a/core/embed/rust/build.rs +++ b/core/embed/rust/build.rs @@ -296,14 +296,17 @@ fn generate_trezorhal_bindings() { .allowlist_function("display_text") .allowlist_function("display_text_render_buffer") .allowlist_function("display_text_width") - .allowlist_function("display_bar") - .allowlist_function("display_bar_radius") - .allowlist_function("display_bar_radius_buffer") .allowlist_function("display_pixeldata") .allowlist_function("display_pixeldata_dirty") .allowlist_function("display_set_window") .allowlist_function("display_sync") + .allowlist_function("display_get_fb_addr") + .allowlist_function("display_get_wr_addr") .allowlist_var("DISPLAY_DATA_ADDRESS") + .allowlist_var("DISPLAY_FRAMEBUFFER_WIDTH") + .allowlist_var("DISPLAY_FRAMEBUFFER_HEIGHT") + .allowlist_var("DISPLAY_FRAMEBUFFER_OFFSET_X") + .allowlist_var("DISPLAY_FRAMEBUFFER_OFFSET_Y") .allowlist_var("DISPLAY_RESX") .allowlist_var("DISPLAY_RESY") .allowlist_type("toif_format_t") @@ -332,12 +335,15 @@ fn generate_trezorhal_bindings() { .allowlist_function("hal_delay") .allowlist_function("hal_ticks_ms") // dma2d + .allowlist_function("dma2d_setup_const") .allowlist_function("dma2d_setup_4bpp") .allowlist_function("dma2d_setup_16bpp") .allowlist_function("dma2d_setup_4bpp_over_4bpp") .allowlist_function("dma2d_setup_4bpp_over_16bpp") .allowlist_function("dma2d_start") .allowlist_function("dma2d_start_blend") + .allowlist_function("dma2d_start_const") + .allowlist_function("dma2d_start_const_multiline") .allowlist_function("dma2d_wait_for_transfer") //buffers .allowlist_function("buffers_get_line_16bpp") @@ -352,6 +358,7 @@ fn generate_trezorhal_bindings() { .allowlist_function("buffers_free_jpeg_work") .allowlist_function("buffers_get_blurring") .allowlist_function("buffers_free_blurring") + .allowlist_var("TEXT_BUFFER_HEIGHT") .no_copy("buffer_line_16bpp_t") .no_copy("buffer_line_4bpp_t") .no_copy("buffer_text_t") diff --git a/core/embed/rust/src/trezorhal/buffers.rs b/core/embed/rust/src/trezorhal/buffers.rs index 52064493c..76bfd1796 100644 --- a/core/embed/rust/src/trezorhal/buffers.rs +++ b/core/embed/rust/src/trezorhal/buffers.rs @@ -5,6 +5,8 @@ use core::{ use super::ffi; +pub use ffi::TEXT_BUFFER_HEIGHT; + macro_rules! buffer_wrapper { ($rust_name: ident, $type: ident, $get: ident, $free: ident) => { pub struct $rust_name(ptr::NonNull); diff --git a/core/embed/rust/src/trezorhal/display.rs b/core/embed/rust/src/trezorhal/display.rs index 0a31a869d..30481b16e 100644 --- a/core/embed/rust/src/trezorhal/display.rs +++ b/core/embed/rust/src/trezorhal/display.rs @@ -6,6 +6,20 @@ use crate::trezorhal::buffers::BufferText; pub use ffi::{DISPLAY_RESX, DISPLAY_RESY}; +#[cfg(feature = "framebuffer")] +pub use ffi::{ + DISPLAY_FRAMEBUFFER_HEIGHT, DISPLAY_FRAMEBUFFER_OFFSET_X, DISPLAY_FRAMEBUFFER_OFFSET_Y, + DISPLAY_FRAMEBUFFER_WIDTH, +}; + +#[cfg(all(feature = "framebuffer", not(feature = "framebuffer32bit")))] +#[derive(Copy, Clone)] +pub struct FrameBuffer(*mut u16); + +#[cfg(all(feature = "framebuffer", feature = "framebuffer32bit"))] +#[derive(Copy, Clone)] +pub struct FrameBuffer(*mut u32); + #[derive(PartialEq, Debug, Eq, FromPrimitive, Clone, Copy)] pub enum ToifFormat { FullColorBE = ffi::toif_format_t_TOIF_FULL_COLOR_BE as _, @@ -74,51 +88,49 @@ pub fn text_baseline(font: i32) -> i16 { unsafe { ffi::font_baseline(font).try_into().unwrap_or(i16::MAX) } } -pub fn bar(x: i16, y: i16, w: i16, h: i16, fgcolor: u16) { - unsafe { ffi::display_bar(x.into(), y.into(), w.into(), h.into(), fgcolor) } -} - -pub fn bar_radius(x: i16, y: i16, w: i16, h: i16, fgcolor: u16, bgcolor: u16, radius: u8) { +#[inline(always)] +#[cfg(all(feature = "disp_i8080_16bit_dw", not(feature = "disp_i8080_8bit_dw")))] +pub fn pixeldata(c: u16) { unsafe { - ffi::display_bar_radius( - x.into(), - y.into(), - w.into(), - h.into(), - fgcolor, - bgcolor, - radius, - ) + ffi::DISPLAY_DATA_ADDRESS.write_volatile(c); } } -pub fn bar_radius_buffer(x: i16, y: i16, w: i16, h: i16, radius: u8, buffer: &mut BufferText) { +#[cfg(feature = "framebuffer")] +pub fn get_fb_addr() -> FrameBuffer { + unsafe { FrameBuffer(ffi::display_get_fb_addr() as _) } +} + +#[inline(always)] +#[cfg(feature = "disp_i8080_8bit_dw")] +pub fn pixeldata(c: u16) { unsafe { - ffi::display_bar_radius_buffer( - x.into(), - y.into(), - w.into(), - h.into(), - radius, - buffer.deref_mut(), - ) + ffi::DISPLAY_DATA_ADDRESS.write_volatile((c & 0xff) as u8); + ffi::DISPLAY_DATA_ADDRESS.write_volatile((c >> 8) as u8); } } #[inline(always)] -#[cfg(all(feature = "disp_i8080_16bit_dw", not(feature = "disp_i8080_8bit_dw")))] -pub fn pixeldata(c: u16) { +#[cfg(all(feature = "framebuffer", not(feature = "framebuffer32bit")))] +pub fn pixel(fb: FrameBuffer, x: i16, y: i16, c: u16) { unsafe { - ffi::DISPLAY_DATA_ADDRESS.write_volatile(c); + let addr = fb.0.offset( + ((y as u32 + DISPLAY_FRAMEBUFFER_OFFSET_Y) * DISPLAY_FRAMEBUFFER_WIDTH + + (x as u32 + DISPLAY_FRAMEBUFFER_OFFSET_X)) as isize, + ); + addr.write_volatile(c); } } #[inline(always)] -#[cfg(feature = "disp_i8080_8bit_dw")] -pub fn pixeldata(c: u16) { +#[cfg(all(feature = "framebuffer", feature = "framebuffer32bit"))] +pub fn pixel(fb: FrameBuffer, x: i16, y: i16, c: u32) { unsafe { - ffi::DISPLAY_DATA_ADDRESS.write_volatile((c & 0xff) as u8); - ffi::DISPLAY_DATA_ADDRESS.write_volatile((c >> 8) as u8); + let addr = fb.0.offset( + ((y as u32 + DISPLAY_FRAMEBUFFER_OFFSET_Y) * DISPLAY_FRAMEBUFFER_WIDTH + + (x as u32 + DISPLAY_FRAMEBUFFER_OFFSET_X)) as isize, + ); + addr.write_volatile(c); } } diff --git a/core/embed/rust/src/trezorhal/dma2d.rs b/core/embed/rust/src/trezorhal/dma2d.rs index 0c32cda9a..e219ca61f 100644 --- a/core/embed/rust/src/trezorhal/dma2d.rs +++ b/core/embed/rust/src/trezorhal/dma2d.rs @@ -1,6 +1,10 @@ use super::ffi; #[allow(dead_code)] + +pub fn dma2d_setup_const() { + unsafe { ffi::dma2d_setup_const() } +} pub fn dma2d_setup_4bpp(fg_color: u16, bg_color: u16) { unsafe { ffi::dma2d_setup_4bpp(fg_color, bg_color) } } @@ -30,7 +34,7 @@ pub unsafe fn dma2d_start(buffer: &[u8], pixels: i16) { unsafe { ffi::dma2d_start( buffer.as_ptr() as _, - ffi::DISPLAY_DATA_ADDRESS as _, + ffi::display_get_wr_addr() as _, pixels as _, ); } @@ -50,12 +54,48 @@ pub unsafe fn dma2d_start_blend(overlay_buffer: &[u8], bg_buffer: &[u8], pixels: ffi::dma2d_start_blend( overlay_buffer.as_ptr() as _, bg_buffer.as_ptr() as _, - ffi::DISPLAY_DATA_ADDRESS as _, + ffi::display_get_wr_addr() as _, pixels as _, ); } } +/// Starts blending +/// +/// # Safety +/// +/// This function is unsafe because the caller has to guarantee that he: +/// 1) doesn't mutate the buffers until the transfer is finished, which is +/// guaranteed by calling `dma2d_wait_for_transfer` +/// 2) the buffer doesn't get dropped until the transfer is finished +/// 3) doesn't call this function while another transfer is running +pub unsafe fn dma2d_start_const(color: u16, pixels: i16) { + unsafe { + ffi::dma2d_start_const(color, ffi::display_get_wr_addr() as _, pixels as _); + } +} + +#[cfg(feature = "framebuffer")] +/// Starts blending +/// +/// # Safety +/// +/// This function is unsafe because the caller has to guarantee that he: +/// 1) doesn't mutate the buffers until the transfer is finished, which is +/// guaranteed by calling `dma2d_wait_for_transfer` +/// 2) the buffer doesn't get dropped until the transfer is finished +/// 3) doesn't call this function while another transfer is running +pub unsafe fn dma2d_start_const_multiline(color: u16, width: i16, height: i16) { + unsafe { + ffi::dma2d_start_const_multiline( + color, + ffi::display_get_wr_addr() as _, + width as _, + height as _, + ); + } +} + pub fn dma2d_wait_for_transfer() { unsafe { ffi::dma2d_wait_for_transfer(); diff --git a/core/embed/rust/src/ui/display/color.rs b/core/embed/rust/src/ui/display/color.rs index 8327e6185..c2290ad81 100644 --- a/core/embed/rust/src/ui/display/color.rs +++ b/core/embed/rust/src/ui/display/color.rs @@ -7,6 +7,13 @@ impl Color { pub const fn from_u16(val: u16) -> Self { Self(val) } + pub const fn from_u32(val: u32) -> Self { + Self::rgb( + ((val >> 16) & 0xFF) as u8, + ((val >> 8) & 0xFF) as u8, + (val & 0xFF) as u8, + ) + } pub const fn rgb(r: u8, g: u8, b: u8) -> Self { let r = (r as u16 & 0xF8) << 8; @@ -56,6 +63,10 @@ impl Color { self.0 } + pub fn to_u32(self) -> u32 { + ((self.r() as u32) << 16) | ((self.g() as u32) << 8) | (self.b() as u32) | 0xff000000 + } + pub fn hi_byte(self) -> u8 { (self.to_u16() >> 8) as u8 } @@ -97,3 +108,15 @@ impl From for u16 { val.to_u16() } } + +impl From for Color { + fn from(val: u32) -> Self { + Self::from_u32(val) + } +} + +impl From for u32 { + fn from(val: Color) -> Self { + val.to_u32() + } +} diff --git a/core/embed/rust/src/ui/display/mod.rs b/core/embed/rust/src/ui/display/mod.rs index 898245c48..2a3b6667e 100644 --- a/core/embed/rust/src/ui/display/mod.rs +++ b/core/embed/rust/src/ui/display/mod.rs @@ -32,6 +32,7 @@ use crate::{ }; // Reexports +use crate::trezorhal::buffers::BufferText; pub use crate::ui::display::toif::Icon; pub use color::Color; pub use font::{Font, Glyph, GlyphMetrics}; @@ -39,6 +40,13 @@ pub use loader::{ loader, loader_indeterminate, loader_small_indeterminate, LOADER_MAX, LOADER_MIN, }; +#[cfg(all(feature = "dma2d", feature = "framebuffer"))] +use crate::trezorhal::{ + display::{get_fb_addr, pixel}, + dma2d::{dma2d_setup_const, dma2d_start_const_multiline}, +}; +use crate::ui::constant::WIDTH; + pub fn backlight() -> u16 { display::backlight(-1) as u16 } @@ -78,34 +86,223 @@ pub fn fade_backlight(_: u16) {} #[cfg(not(feature = "backlight"))] pub fn fade_backlight_duration(_: u16, _: u32) {} +#[cfg(not(feature = "framebuffer"))] /// Fill a whole rectangle with a specific color. pub fn rect_fill(r: Rect, fg_color: Color) { - display::bar(r.x0, r.y0, r.width(), r.height(), fg_color.into()); + let r = r.translate(get_offset()); + let r = r.clamp(constant::screen()); + + set_window(r); + + for _ in r.y0..r.y1 { + for _ in r.x0..r.x1 { + pixeldata(fg_color.into()); + } + } + + pixeldata_dirty(); +} + +#[cfg(feature = "framebuffer")] +pub fn rect_fill(r: Rect, fg_color: Color) { + let r = r.translate(get_offset()); + let r = r.clamp(constant::screen()); + set_window(r); + dma2d_setup_const(); + unsafe { + dma2d_start_const_multiline(fg_color.into(), r.width(), r.height()); + } + dma2d_wait_for_transfer(); + pixeldata_dirty(); } pub fn rect_stroke(r: Rect, fg_color: Color) { - display::bar(r.x0, r.y0, r.width(), 1, fg_color.into()); - display::bar(r.x0, r.y0 + r.height() - 1, r.width(), 1, fg_color.into()); - display::bar(r.x0, r.y0, 1, r.height(), fg_color.into()); - display::bar(r.x0 + r.width() - 1, r.y0, 1, r.height(), fg_color.into()); + rect_fill( + Rect::from_top_left_and_size(Point::new(r.x0, r.y0), Offset::new(r.width(), 1)), + fg_color, + ); + rect_fill( + Rect::from_top_left_and_size( + Point::new(r.x0, r.y0 + r.height() - 1), + Offset::new(r.width(), 1), + ), + fg_color, + ); + rect_fill( + Rect::from_top_left_and_size(Point::new(r.x0, r.y0), Offset::new(1, r.height())), + fg_color, + ); + rect_fill( + Rect::from_top_left_and_size( + Point::new(r.x0 + r.width() - 1, r.y0), + Offset::new(1, r.height()), + ), + fg_color, + ); } +const CORNER_RADIUS: usize = 16; + +#[rustfmt::skip] +const CORNER_TABLE: [usize; CORNER_RADIUS * CORNER_RADIUS] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 5, 9, 12, 14, 15, + 0, 0, 0, 0, 0, 0, 0, 0, 3, 9, 15, 15, 15, 15, 15, 15, + 0, 0, 0, 0, 0, 0, 0, 8, 15, 15, 15, 15, 15, 15, 15, 15, + 0, 0, 0, 0, 0, 3, 12, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 0, 0, 0, 0, 3, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 0, 0, 0, 3, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 0, 0, 0, 12, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 0, 0, 8, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 0, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 0, 9, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 1, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 5, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 9, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 12, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +]; + /// Draw a rectangle with rounded corners. +#[cfg(not(feature = "framebuffer"))] pub fn rect_fill_rounded(r: Rect, fg_color: Color, bg_color: Color, radius: u8) { if radius == 1 { rect_fill_rounded1(r, fg_color, bg_color); } else { assert!([2, 4, 8, 16].iter().any(|allowed| radius == *allowed)); - display::bar_radius( - r.x0, - r.y0, - r.width(), - r.height(), - fg_color.into(), - bg_color.into(), - radius, - ); + + let color_table = get_color_table(fg_color, bg_color); + let area = r.translate(get_offset()); + let clamped = area.clamp(constant::screen()); + + set_window(clamped); + + let radius = radius as i16; + let radius_inv = 16 / radius; + + for y in area.y0..area.y1 { + for x in area.x0..area.x1 { + if x - r.x0 < radius && y - r.y0 < radius { + let c = CORNER_TABLE[((x - area.x0) * radius_inv + + (y - area.y0) * radius_inv * CORNER_RADIUS as i16) + as usize]; + pixeldata(color_table[c]); + } else if x - r.x0 < radius && y - r.y0 >= r.height() - radius { + let c = CORNER_TABLE[((x - area.x0) * radius_inv + + (r.height() - 1 - (y - area.y0)) * radius_inv * CORNER_RADIUS as i16) + as usize]; + pixeldata(color_table[c]); + } else if x - r.x0 >= r.width() - radius && y - r.y0 < radius { + let c = CORNER_TABLE[((r.width() - 1 - (x - area.x0)) * radius_inv + + (y - area.y0) * radius_inv * CORNER_RADIUS as i16) + as usize]; + pixeldata(color_table[c]); + } else if x - r.x0 >= r.width() - radius && y - r.y0 >= r.height() - radius { + let c = CORNER_TABLE[((r.width() - 1 - (x - area.x0)) * radius_inv + + (r.height() - 1 - (y - area.y0)) * radius_inv * CORNER_RADIUS as i16) + as usize]; + pixeldata(color_table[c]); + } else { + pixeldata(color_table[15]); + } + } + } + } + pixeldata_dirty(); +} + +pub fn rect_fill_rounded_buffer(r: Rect, radius: u8, buffer: &mut BufferText) { + if r.height() > r.y0 + buffers::TEXT_BUFFER_HEIGHT as i16 || r.x0 + r.width() > WIDTH { + return; + } + + assert!([2, 4, 8, 16].iter().any(|allowed| radius == *allowed)); + + let radius = radius as i16; + let radius_inv = 16 / radius; + + for y in r.y0..r.y1 { + for x in r.x0..r.x1 { + let c = if x - r.x0 < radius && y - r.y0 < radius { + CORNER_TABLE[((x - r.x0) * radius_inv + + (y - r.y0) * radius_inv * CORNER_RADIUS as i16) + as usize] + } else if x - r.x0 < radius && y - r.y0 >= r.height() - radius { + CORNER_TABLE[((x - r.x0) * radius_inv + + (r.height() - 1 - (y - r.y0)) * radius_inv * CORNER_RADIUS as i16) + as usize] + } else if x - r.x0 >= r.width() - radius && y - r.y0 < radius { + CORNER_TABLE[((r.width() - 1 - (x - r.x0)) * radius_inv + + (y - r.y0) * radius_inv * CORNER_RADIUS as i16) + as usize] + } else if x - r.x0 >= r.width() - radius && y - r.y0 >= r.height() - radius { + CORNER_TABLE[((r.width() - 1 - (x - r.x0)) * radius_inv + + (r.height() - 1 - (y - r.y0)) * radius_inv * CORNER_RADIUS as i16) + as usize] + } else { + 15usize + }; + let p = y * WIDTH + x; + let b = (p / 2) as usize; + if p % 2 != 0 { + buffer.buffer[b] |= (c << 4) as u8; + } else { + buffer.buffer[b] |= c as u8; + } + } } + pixeldata_dirty(); +} + +#[cfg(feature = "framebuffer")] +/// Draw a rectangle with rounded corners. +pub fn rect_fill_rounded(area: Rect, fg_color: Color, bg_color: Color, radius: u8) { + let radius = radius as i16; + if radius == 1 { + rect_fill_rounded1(area, fg_color, bg_color); + } else { + assert!([2, 4, 8, 16].iter().any(|allowed| radius == *allowed)); + + let r = area.translate(get_offset()); + let r = r.clamp(constant::screen()); + let fb = get_fb_addr(); + + rect_fill(r, fg_color); + let r_inv = 16 / radius; + let color_table = get_color_table(fg_color, bg_color); + + for y in 0..radius { + for x in 0..radius { + let c = CORNER_TABLE[(x * r_inv + y * r_inv * 16) as usize]; + pixel(fb, r.x0 + x, r.y0 + y, color_table[c].into()); + } + } + for y in 0..radius { + for x in 0..radius { + let c = CORNER_TABLE[((radius - x - 1) * r_inv + y * r_inv * 16) as usize]; + pixel(fb, r.x1 - radius + x, r.y0 + y, color_table[c].into()); + } + } + for y in 0..radius { + for x in 0..radius { + let c = CORNER_TABLE[(x * r_inv + (radius - y - 1) * r_inv * 16) as usize]; + pixel(fb, r.x0 + x, r.y1 - radius + y, color_table[c].into()); + } + } + for y in 0..radius { + for x in 0..radius { + let c = CORNER_TABLE + [((radius - x - 1) * r_inv + (radius - y - 1) * r_inv * 16) as usize]; + pixel( + fb, + r.x1 - radius + x, + r.y1 - radius + y, + color_table[c].into(), + ); + } + } + } + pixeldata_dirty(); } /// Filling a rectangle with a rounding of 1 pixel - removing the corners. @@ -137,7 +334,10 @@ pub fn rect_outline_rounded(r: Rect, fg_color: Color, bg_color: Color, radius: u pub fn rect_fill_corners(r: Rect, fg_color: Color) { for p in r.corner_points().iter() { // This draws a 1x1 rectangle at the given point. - display::bar(p.x, p.y, 1, 1, fg_color.into()); + rect_fill( + Rect::from_top_left_and_size(*p, Offset::uniform(1)), + fg_color, + ); } } @@ -860,7 +1060,10 @@ pub fn marquee(area: Rect, text: &str, offset: i16, font: Font, fg: Color, bg: C pub fn dotted_line(start: Point, width: i16, color: Color, step: i16) { for x in (start.x..width).step_by(step as usize) { - display::bar(x, start.y, 1, 1, color.into()); + rect_fill( + Rect::from_top_left_and_size(Point::new(x, start.y), Offset::new(1, 1)), + color, + ); } } diff --git a/core/embed/rust/src/ui/display/toif.rs b/core/embed/rust/src/ui/display/toif.rs index a58df2993..f3a5f8388 100644 --- a/core/embed/rust/src/ui/display/toif.rs +++ b/core/embed/rust/src/ui/display/toif.rs @@ -6,7 +6,7 @@ use crate::{ ui::{ component::image::Image, constant, - display::{get_color_table, get_offset, pixeldata, pixeldata_dirty, set_window}, + display::{get_offset, pixeldata_dirty, set_window}, geometry::{Alignment2D, Offset, Point, Rect}, }, }; @@ -20,6 +20,12 @@ use crate::{ ui::display::process_buffer, }; +#[cfg(not(feature = "framebuffer"))] +use crate::ui::display::{get_color_table, pixeldata}; + +#[cfg(feature = "framebuffer")] +use crate::trezorhal::{buffers::BufferLine4bpp, dma2d::dma2d_setup_4bpp}; + use super::Color; const TOIF_HEADER_LENGTH: usize = 12; @@ -28,6 +34,7 @@ pub fn render_icon(icon: &Icon, center: Point, fg_color: Color, bg_color: Color) render_toif(&icon.toif, center, fg_color, bg_color); } +#[cfg(not(feature = "framebuffer"))] pub fn render_toif(toif: &Toif, center: Point, fg_color: Color, bg_color: Color) { let r = Rect::from_center_and_size(center, toif.size()); let area = r.translate(get_offset()); @@ -63,6 +70,34 @@ pub fn render_toif(toif: &Toif, center: Point, fg_color: Color, bg_color: Color) pixeldata_dirty(); } +#[cfg(feature = "framebuffer")] +pub fn render_toif(toif: &Toif, center: Point, fg_color: Color, bg_color: Color) { + let r = Rect::from_center_and_size(center, toif.size()); + let area = r.translate(get_offset()); + + set_window(area); + + let mut b1 = BufferLine4bpp::get_cleared(); + let mut b2 = BufferLine4bpp::get_cleared(); + + let mut window = [0; UZLIB_WINDOW_SIZE]; + let mut ctx = toif.decompression_context(Some(&mut window)); + + dma2d_setup_4bpp(fg_color.into(), bg_color.into()); + + for y in area.y0..area.y1 { + let img_buffer_used = if y % 2 == 0 { &mut b1 } else { &mut b2 }; + + unwrap!(ctx.uncompress(&mut (&mut img_buffer_used.buffer)[0..(area.width() / 2) as usize])); + + dma2d_wait_for_transfer(); + unsafe { dma2d_start(&img_buffer_used.buffer, area.width()) }; + } + + dma2d_wait_for_transfer(); + pixeldata_dirty(); +} + #[no_mangle] extern "C" fn display_image( x: cty::int16_t, diff --git a/core/embed/rust/src/ui/model_tt/component/homescreen/render.rs b/core/embed/rust/src/ui/model_tt/component/homescreen/render.rs index 4a16eb71c..87b0df1bb 100644 --- a/core/embed/rust/src/ui/model_tt/component/homescreen/render.rs +++ b/core/embed/rust/src/ui/model_tt/component/homescreen/render.rs @@ -2,7 +2,6 @@ use crate::{ trezorhal::{ buffers::{BufferBlurring, BufferJpeg, BufferLine16bpp, BufferLine4bpp, BufferText}, display, - display::bar_radius_buffer, dma2d::{dma2d_setup_4bpp_over_16bpp, dma2d_start_blend, dma2d_wait_for_transfer}, uzlib::UzlibContext, }, @@ -10,7 +9,7 @@ use crate::{ component::text::TextStyle, constant::{screen, HEIGHT, WIDTH}, display::{ - position_buffer, set_window, + position_buffer, rect_fill_rounded_buffer, set_window, tjpgd::{BufferInput, BufferOutput, JDEC}, Color, Icon, }, @@ -645,11 +644,11 @@ pub fn homescreen( let mut next_text_idx = 0; let mut text_info = if let Some(notification) = notification { - bar_radius_buffer( - NOTIFICATION_BORDER, - 0, - WIDTH - NOTIFICATION_BORDER * 2, - NOTIFICATION_HEIGHT, + rect_fill_rounded_buffer( + Rect::from_top_left_and_size( + Point::new(NOTIFICATION_BORDER, 0), + Offset::new(WIDTH - NOTIFICATION_BORDER * 2, NOTIFICATION_HEIGHT), + ), 2, &mut text_buffer, ); diff --git a/core/embed/rust/src/ui/model_tt/component/mod.rs b/core/embed/rust/src/ui/model_tt/component/mod.rs index 21894efac..80cb36e1d 100644 --- a/core/embed/rust/src/ui/model_tt/component/mod.rs +++ b/core/embed/rust/src/ui/model_tt/component/mod.rs @@ -8,7 +8,7 @@ mod fido_icons; mod error; mod frame; mod hold_to_confirm; -#[cfg(feature = "dma2d")] +#[cfg(feature = "micropython")] mod homescreen; mod horizontal_page; mod keyboard; @@ -32,7 +32,7 @@ pub use error::ErrorScreen; pub use fido::{FidoConfirm, FidoMsg}; pub use frame::{Frame, FrameMsg}; pub use hold_to_confirm::{HoldToConfirm, HoldToConfirmMsg}; -#[cfg(feature = "dma2d")] +#[cfg(feature = "micropython")] pub use homescreen::{Homescreen, HomescreenMsg, Lockscreen}; pub use horizontal_page::HorizontalPage; pub use keyboard::{ diff --git a/core/embed/trezorhal/dma2d.h b/core/embed/trezorhal/dma2d.h index 790cbc2d6..3f8d8f79b 100644 --- a/core/embed/trezorhal/dma2d.h +++ b/core/embed/trezorhal/dma2d.h @@ -33,6 +33,8 @@ void dma2d_setup_4bpp_over_16bpp(uint16_t overlay_color); void dma2d_start(uint8_t* in_addr, uint8_t* out_addr, int32_t pixels); void dma2d_start_const(uint16_t color, uint8_t* out_addr, int32_t pixels); +void dma2d_start_const_multiline(uint16_t color, uint8_t* out_addr, + int32_t width, int32_t height); void dma2d_start_blend(uint8_t* overlay_addr, uint8_t* bg_addr, uint8_t* out_addr, int32_t pixels); diff --git a/core/embed/trezorhal/stm32f4/displays/ltdc.c b/core/embed/trezorhal/stm32f4/displays/ltdc.c index a3165908c..505410ccb 100644 --- a/core/embed/trezorhal/stm32f4/displays/ltdc.c +++ b/core/embed/trezorhal/stm32f4/displays/ltdc.c @@ -43,29 +43,30 @@ static int DISPLAY_ORIENTATION = -1; // this is just for compatibility with DMA2D using algorithms uint8_t *const DISPLAY_DATA_ADDRESS = 0; -uint16_t display_index_x = 0; -uint16_t display_index_y = 0; -uint16_t display_window_x0 = 0; -uint16_t display_window_y0 = MAX_DISPLAY_RESX - 1; -uint16_t display_window_x1 = 0; -uint16_t display_window_y1 = MAX_DISPLAY_RESY - 1; +uint16_t cursor_x = 0; +uint16_t cursor_y = 0; +uint16_t window_x0 = 0; +uint16_t window_y0 = MAX_DISPLAY_RESX - 1; +uint16_t window_x1 = 0; +uint16_t window_y1 = MAX_DISPLAY_RESY - 1; void display_pixeldata(uint16_t c) { - ((uint16_t *)LCD_FRAME_BUFFER)[(display_index_y * MAX_DISPLAY_RESX) + - display_index_x] = c; + ((uint16_t *)LCD_FRAME_BUFFER)[(cursor_y * MAX_DISPLAY_RESX) + cursor_x] = c; - display_index_x++; + cursor_x++; - if (display_index_x > display_window_x1) { - display_index_x = display_window_x0; - display_index_y++; + if (cursor_x > window_x1) { + cursor_x = window_x0; + cursor_y++; - if (display_index_y > display_window_y1) { - display_index_y = display_window_y0; + if (cursor_y > window_y1) { + cursor_y = window_y0; } } } +void display_pixeldata_dirty(void) {} + void display_reset_state() {} static void __attribute__((unused)) display_sleep(void) {} @@ -192,12 +193,12 @@ void BSP_LCD_SetLayerAddress_NoReload(uint32_t LayerIndex, uint32_t Address) { // static struct { uint16_t x, y; } BUFFER_OFFSET; void display_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { - display_window_x0 = x0; - display_window_x1 = x1; - display_window_y0 = y0; - display_window_y1 = y1; - display_index_x = x0; - display_index_y = y0; + window_x0 = x0; + window_x1 = x1; + window_y0 = y0; + window_y1 = y1; + cursor_x = x0; + cursor_y = y0; // /* Reconfigure the layer size */ // HAL_LTDC_SetWindowSize_NoReload(&LtdcHandler, x1-x0 + 1, y1-y0 + 1, 0); @@ -371,3 +372,46 @@ void display_sync(void) {} const char *display_save(const char *prefix) { return NULL; } void display_clear_save(void) {} + +void display_efficient_clear(void) { + memzero((void *)LCD_FRAME_BUFFER, 153600); +} + +uint8_t *display_get_wr_addr(void) { + uint32_t address = LCD_FRAME_BUFFER; + /* Get the rectangle start address */ + address = (address + (2 * ((cursor_y)*MAX_DISPLAY_RESX + (cursor_x)))); + + return (uint8_t *)address; +} + +uint32_t *display_get_fb_addr(void) { return (uint32_t *)LCD_FRAME_BUFFER; } + +uint16_t display_get_window_width(void) { return window_x1 - window_x0 + 1; } + +uint16_t display_get_window_height(void) { return window_y1 - window_y0 + 1; } + +void display_shift_window(uint16_t pixels) { + uint16_t w = display_get_window_width(); + uint16_t h = display_get_window_height(); + + uint16_t line_rem = w - (cursor_x - window_x0); + + if (pixels < line_rem) { + cursor_x += pixels; + return; + } + + // start of next line + pixels = pixels - line_rem; + cursor_x = window_x0; + cursor_y++; + + // add the rest of pixels + cursor_y = window_y0 + (((cursor_y - window_y0) + (pixels / w)) % h); + cursor_x += pixels % w; +} + +uint16_t display_get_window_offset(void) { + return MAX_DISPLAY_RESX - display_get_window_width(); +} diff --git a/core/embed/trezorhal/stm32f4/displays/ltdc.h b/core/embed/trezorhal/stm32f4/displays/ltdc.h index 7d57c8dcf..2efb2be96 100644 --- a/core/embed/trezorhal/stm32f4/displays/ltdc.h +++ b/core/embed/trezorhal/stm32f4/displays/ltdc.h @@ -5,7 +5,19 @@ #include STM32_HAL_H #define TREZOR_FONT_BPP 4 +#define DISPLAY_FRAMEBUFFER_WIDTH MAX_DISPLAY_RESX +#define DISPLAY_FRAMEBUFFER_HEIGHT MAX_DISPLAY_RESY +#define DISPLAY_FRAMEBUFFER_OFFSET_X 0 +#define DISPLAY_FRAMEBUFFER_OFFSET_Y 0 +#define DISPLAY_COLOR_MODE DMA2D_OUTPUT_RGB565 +#define DISPLAY_EFFICIENT_CLEAR 1 -extern uint8_t *const DISPLAY_DATA_ADDRESS; +extern uint8_t* const DISPLAY_DATA_ADDRESS; + +static inline void display_pixel(uint8_t* fb, int16_t x, int16_t y, + uint16_t color) { + uint32_t p = 2 * (y * DISPLAY_FRAMEBUFFER_WIDTH + x); + *((uint16_t*)(fb + p)) = color; +} #endif //_LTDC_H diff --git a/core/embed/trezorhal/stm32f4/displays/st7789v.c b/core/embed/trezorhal/stm32f4/displays/st7789v.c index 681531ab7..f46e5a192 100644 --- a/core/embed/trezorhal/stm32f4/displays/st7789v.c +++ b/core/embed/trezorhal/stm32f4/displays/st7789v.c @@ -70,6 +70,8 @@ static int DISPLAY_ORIENTATION = -1; void display_pixeldata(uint16_t c) { PIXELDATA(c); } +void display_pixeldata_dirty(void) {} + static uint32_t read_display_id(uint8_t command) { volatile uint8_t c = 0; uint32_t id = 0; @@ -470,3 +472,9 @@ void display_set_big_endian(void) { const char *display_save(const char *prefix) { return NULL; } void display_clear_save(void) {} + +uint8_t *display_get_wr_addr(void) { return (uint8_t *)DISPLAY_DATA_ADDRESS; } + +uint16_t display_get_window_offset(void) { return 0; } + +void display_shift_window(uint16_t pixels) {} diff --git a/core/embed/trezorhal/stm32f4/displays/st7789v.h b/core/embed/trezorhal/stm32f4/displays/st7789v.h index 70445ff1f..00315ed6f 100644 --- a/core/embed/trezorhal/stm32f4/displays/st7789v.h +++ b/core/embed/trezorhal/stm32f4/displays/st7789v.h @@ -7,6 +7,7 @@ // ILI9341V, GC9307 and ST7789V drivers support 240px x 320px display resolution #define MAX_DISPLAY_RESX 240 #define MAX_DISPLAY_RESY 320 +#define DISPLAY_COLOR_MODE DMA2D_OUTPUT_RGB565 #define TREZOR_FONT_BPP 4 #ifdef USE_DISP_I8080_16BIT_DW diff --git a/core/embed/trezorhal/stm32f4/displays/ug-2828tswig01.c b/core/embed/trezorhal/stm32f4/displays/ug-2828tswig01.c index b40a0c6c2..897958af7 100644 --- a/core/embed/trezorhal/stm32f4/displays/ug-2828tswig01.c +++ b/core/embed/trezorhal/stm32f4/displays/ug-2828tswig01.c @@ -33,9 +33,6 @@ (1 << DISPLAY_MEMORY_PIN))))) #define DATA(X) (ADDR) = (X) -// noop on TR as we don't need to push data to display -#define PIXELDATA_DIRTY() - static int DISPLAY_BACKLIGHT = -1; static int DISPLAY_ORIENTATION = -1; struct { @@ -97,7 +94,7 @@ void display_pixeldata(uint16_t c) { } } -#define PIXELDATA(c) display_pixeldata(c) +void display_pixeldata_dirty(void) {} void display_reset_state(void) { memzero(DISPLAY_STATE.RAM, sizeof(DISPLAY_STATE.RAM)); diff --git a/core/embed/trezorhal/stm32f4/displays/vg-2864ksweg01.c b/core/embed/trezorhal/stm32f4/displays/vg-2864ksweg01.c index 7de21fd0e..ca894efff 100644 --- a/core/embed/trezorhal/stm32f4/displays/vg-2864ksweg01.c +++ b/core/embed/trezorhal/stm32f4/displays/vg-2864ksweg01.c @@ -92,11 +92,9 @@ void display_pixeldata(uint16_t c) { } } -#define PIXELDATA(c) display_pixeldata(c) - void display_reset_state() {} -void pixeldata_dirty(void) { pixeldata_dirty_flag = true; } +void display_pixeldata_dirty(void) { pixeldata_dirty_flag = true; } void display_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { PIXELWINDOW.start.x = x0; diff --git a/core/embed/trezorhal/stm32f4/displays/vg-2864ksweg01.h b/core/embed/trezorhal/stm32f4/displays/vg-2864ksweg01.h index 8165b2228..ad8994e3b 100644 --- a/core/embed/trezorhal/stm32f4/displays/vg-2864ksweg01.h +++ b/core/embed/trezorhal/stm32f4/displays/vg-2864ksweg01.h @@ -7,7 +7,4 @@ #define DISPLAY_RESY 64 #define TREZOR_FONT_BPP 1 -void pixeldata_dirty(void); -#define PIXELDATA_DIRTY() pixeldata_dirty(); - #endif //_VG_2864KSWEG01_H diff --git a/core/embed/trezorhal/stm32f4/dma2d.c b/core/embed/trezorhal/stm32f4/dma2d.c index ea8e2e7b3..d71b61bbe 100644 --- a/core/embed/trezorhal/stm32f4/dma2d.c +++ b/core/embed/trezorhal/stm32f4/dma2d.c @@ -20,6 +20,7 @@ #include "dma2d.h" #include "colors.h" #include STM32_HAL_H +#include "display_interface.h" typedef enum { DMA2D_LAYER_FG = 1, @@ -27,12 +28,14 @@ typedef enum { } dma2d_layer_t; static DMA2D_HandleTypeDef dma2d_handle = {0}; +static uint16_t current_width = 0; +static uint16_t current_height = 0; void dma2d_init(void) { __HAL_RCC_DMA2D_CLK_ENABLE(); dma2d_handle.Instance = (DMA2D_TypeDef*)DMA2D_BASE; - dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_RGB565; + dma2d_handle.Init.ColorMode = DISPLAY_COLOR_MODE; dma2d_handle.Init.OutputOffset = 0; } @@ -61,11 +64,13 @@ static void dma2d_init_clut(uint16_t fg, uint16_t bg, dma2d_layer_t layer) { void dma2d_setup_const(void) { dma2d_handle.Init.Mode = DMA2D_R2M; + dma2d_handle.Init.OutputOffset = display_get_window_offset(); HAL_DMA2D_Init(&dma2d_handle); } void dma2d_setup_4bpp(uint16_t fg_color, uint16_t bg_color) { dma2d_handle.Init.Mode = DMA2D_M2M_PFC; + dma2d_handle.Init.OutputOffset = display_get_window_offset(); dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4; dma2d_handle.LayerCfg[1].InputOffset = 0; dma2d_handle.LayerCfg[1].AlphaMode = 0; @@ -79,6 +84,7 @@ void dma2d_setup_4bpp(uint16_t fg_color, uint16_t bg_color) { void dma2d_setup_16bpp(void) { dma2d_handle.Init.Mode = DMA2D_M2M_PFC; + dma2d_handle.Init.OutputOffset = display_get_window_offset(); dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565; dma2d_handle.LayerCfg[1].InputOffset = 0; dma2d_handle.LayerCfg[1].AlphaMode = 0; @@ -90,6 +96,7 @@ void dma2d_setup_16bpp(void) { void dma2d_setup_4bpp_over_16bpp(uint16_t overlay_color) { dma2d_handle.Init.Mode = DMA2D_M2M_BLEND; + dma2d_handle.Init.OutputOffset = display_get_window_offset(); dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A4; dma2d_handle.LayerCfg[1].InputOffset = 0; dma2d_handle.LayerCfg[1].AlphaMode = 0; @@ -109,6 +116,7 @@ void dma2d_setup_4bpp_over_16bpp(uint16_t overlay_color) { void dma2d_setup_4bpp_over_4bpp(uint16_t fg_color, uint16_t bg_color, uint16_t overlay_color) { dma2d_handle.Init.Mode = DMA2D_M2M_BLEND; + dma2d_handle.Init.OutputOffset = display_get_window_offset(); dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A4; dma2d_handle.LayerCfg[1].InputOffset = 0; dma2d_handle.LayerCfg[1].AlphaMode = 0; @@ -127,17 +135,31 @@ void dma2d_setup_4bpp_over_4bpp(uint16_t fg_color, uint16_t bg_color, } void dma2d_start(uint8_t* in_addr, uint8_t* out_addr, int32_t pixels) { + current_width = pixels; + current_height = 1; HAL_DMA2D_Start(&dma2d_handle, (uint32_t)in_addr, (uint32_t)out_addr, pixels, 1); } void dma2d_start_const(uint16_t color, uint8_t* out_addr, int32_t pixels) { + current_width = pixels; + current_height = 1; HAL_DMA2D_Start(&dma2d_handle, rgb565_to_rgb888(color), (uint32_t)out_addr, pixels, 1); } +void dma2d_start_const_multiline(uint16_t color, uint8_t* out_addr, + int32_t width, int32_t height) { + current_width = width; + current_height = height; + HAL_DMA2D_Start(&dma2d_handle, rgb565_to_rgb888(color), (uint32_t)out_addr, + width, height); +} + void dma2d_start_blend(uint8_t* overlay_addr, uint8_t* bg_addr, uint8_t* out_addr, int32_t pixels) { + current_width = pixels; + current_height = 1; HAL_DMA2D_BlendingStart(&dma2d_handle, (uint32_t)overlay_addr, (uint32_t)bg_addr, (uint32_t)out_addr, pixels, 1); } @@ -145,4 +167,7 @@ void dma2d_start_blend(uint8_t* overlay_addr, uint8_t* bg_addr, void dma2d_wait_for_transfer(void) { while (HAL_DMA2D_PollForTransfer(&dma2d_handle, 10) != HAL_OK) ; + display_shift_window(current_width * current_height); + current_width = 0; + current_height = 0; } diff --git a/core/embed/trezorhal/unix/display-unix.c b/core/embed/trezorhal/unix/display-unix.c index 86b26e514..fc4f880c3 100644 --- a/core/embed/trezorhal/unix/display-unix.c +++ b/core/embed/trezorhal/unix/display-unix.c @@ -115,7 +115,7 @@ void display_pixeldata(pixel_color c) { } } -#define PIXELDATA(c) display_pixeldata(c) +void display_pixeldata_dirty(void) {} void display_reset_state() {} @@ -332,3 +332,5 @@ void display_clear_save(void) { SDL_FreeSurface(PREV_SAVED); PREV_SAVED = NULL; } + +uint8_t *display_get_wr_addr(void) { return (uint8_t *)DISPLAY_DATA_ADDRESS; } diff --git a/core/site_scons/boards/discovery.py b/core/site_scons/boards/discovery.py index b3d25b4f1..8f0738429 100644 --- a/core/site_scons/boards/discovery.py +++ b/core/site_scons/boards/discovery.py @@ -38,11 +38,18 @@ def configure( ] sources += [f"embed/trezorhal/stm32f4/displays/{display}"] sources += ["embed/trezorhal/stm32f4/displays/ili9341_spi.c"] - sources += ["embed/trezorhal/stm32f4/dma.c"] + sources += ["embed/trezorhal/stm32f4/dma2d.c"] + sources += [ + "vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma2d.c" + ] sources += ["embed/trezorhal/stm32f4/sdram.c"] sources += [ "vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma.c" ] + defines += ["USE_DMA2D"] + defines += ["FRAMEBUFFER"] + features_available.append("dma2d") + features_available.append("framebuffer") if "input" in features_wanted: sources += ["embed/trezorhal/stm32f4/i2c.c"] @@ -50,11 +57,6 @@ def configure( sources += ["embed/lib/touch.c"] features_available.append("touch") - if "dma2d" in features_wanted: - defines += ["USE_DMA2D"] - sources += ["embed/lib/dma2d_emul.c"] - features_available.append("dma2d") - if "usb" in features_wanted: sources += [ "embed/trezorhal/stm32f4/usb.c",