diff --git a/core/Makefile b/core/Makefile index 3bca1bb31d..6a5fe4d97e 100644 --- a/core/Makefile +++ b/core/Makefile @@ -45,6 +45,7 @@ QUIET_MODE ?= 0 TREZOR_DISABLE_ANIMATION ?= $(if $(filter 0,$(PYOPT)),1,0) STORAGE_INSECURE_TESTING_MODE ?= 0 RUST_PRINT_TYPES_SIZES ?= 0 +UI_DEBUG_OVERLAY ?= 0 # OpenOCD interface default. Alternative: ftdi/olimex-arm-usb-tiny-h OPENOCD_INTERFACE ?= stlink @@ -131,7 +132,8 @@ SCONS_VARS = \ TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" \ TREZOR_EMULATOR_DEBUGGABLE=$(TREZOR_EMULATOR_DEBUGGABLE) \ TREZOR_MEMPERF="$(TREZOR_MEMPERF)" \ - TREZOR_MODEL="$(TREZOR_MODEL)" + TREZOR_MODEL="$(TREZOR_MODEL)" \ + UI_DEBUG_OVERLAY="$(UI_DEBUG_OVERLAY)" SCONS_OPTS = -Q -j $(JOBS) ifeq ($(QUIET_MODE),1) diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader index 9c28ababad..54c95fbcf5 100644 --- a/core/SConscript.bootloader +++ b/core/SConscript.bootloader @@ -9,6 +9,7 @@ CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0)) BOOTLOADER_QA = ARGUMENTS.get('BOOTLOADER_QA', '0') == '1' PRODUCTION = 0 if BOOTLOADER_QA else ARGUMENTS.get('PRODUCTION', '0') == '1' HW_REVISION = ARGUMENTS.get('HW_REVISION', None) +UI_DEBUG_OVERLAY = ARGUMENTS.get('UI_DEBUG_OVERLAY', '0') == '1' FEATURES_WANTED = ["input", "rgb_led", "consumption_mask", "usb", "optiga", "dma2d"] @@ -187,6 +188,9 @@ cmake_gen = env.Command( # features = ['bootloader',] + FEATURES_AVAILABLE + RUST_UI_FEATURES +if UI_DEBUG_OVERLAY: + features.append('ui_debug_overlay') + rust = tools.add_rust_lib( env, 'bootloader', diff --git a/core/SConscript.firmware b/core/SConscript.firmware index d049c62fc9..aa98a85721 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -20,6 +20,7 @@ SCM_REVISION = ARGUMENTS.get('SCM_REVISION', None) THP = ARGUMENTS.get('THP', '0') == '1' # Trezor-Host Protocol BENCHMARK = ARGUMENTS.get('BENCHMARK', '0') == '1' DISABLE_ANIMATION = ARGUMENTS.get('TREZOR_DISABLE_ANIMATION', '0') == '1' +UI_DEBUG_OVERLAY = ARGUMENTS.get('UI_DEBUG_OVERLAY', '0') == '1' STORAGE_INSECURE_TESTING_MODE = ARGUMENTS.get('STORAGE_INSECURE_TESTING_MODE', '0') == '1' if STORAGE_INSECURE_TESTING_MODE and PRODUCTION: @@ -759,6 +760,8 @@ if PYOPT == '0': features.append('ui_debug') if EVERYTHING: features.append('universal_fw') +if UI_DEBUG_OVERLAY: + features.append('ui_debug_overlay') rust = tools.add_rust_lib( env, diff --git a/core/SConscript.prodtest b/core/SConscript.prodtest index f21f822ed9..3e7e2a15a3 100644 --- a/core/SConscript.prodtest +++ b/core/SConscript.prodtest @@ -8,6 +8,7 @@ CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0)) PRODUCTION = ARGUMENTS.get('PRODUCTION', '0') == '1' BOOTLOADER_DEVEL = ARGUMENTS.get('BOOTLOADER_DEVEL', '0') == '1' HW_REVISION = ARGUMENTS.get('HW_REVISION', None) +UI_DEBUG_OVERLAY = ARGUMENTS.get('UI_DEBUG_OVERLAY', '0') == '1' FEATURES_WANTED = ["input", "sbu", "nfc", "sd_card", "rgb_led", "usb", "consumption_mask", "optiga", "haptic", "tropic"] @@ -209,6 +210,9 @@ obj_program.extend(env.Object(source=SOURCE_HAL)) # features = ['prodtest',] + FEATURES_AVAILABLE + RUST_UI_FEATURES +if UI_DEBUG_OVERLAY: + features.append('ui_debug_overlay') + rust = tools.add_rust_lib( env, diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml index 8db6885bb5..3deab49cf7 100644 --- a/core/embed/rust/Cargo.toml +++ b/core/embed/rust/Cargo.toml @@ -20,6 +20,7 @@ display_mono = [] display_rgb565 = ["ui_antialiasing"] display_rgba8888 = ["ui_antialiasing"] ui_debug = [] +ui_debug_overlay = [] ui_antialiasing = [] ui_blurring = [] ui_image_buffer = [] diff --git a/core/embed/rust/src/ui/layout_bolt/mod.rs b/core/embed/rust/src/ui/layout_bolt/mod.rs index 70da78a46b..8d53334b5c 100644 --- a/core/embed/rust/src/ui/layout_bolt/mod.rs +++ b/core/embed/rust/src/ui/layout_bolt/mod.rs @@ -1,5 +1,8 @@ use super::{geometry::Rect, CommonUI}; +#[cfg(feature = "ui_debug_overlay")] +use super::{shape, DebugOverlay}; + #[cfg(feature = "bootloader")] pub mod bootloader; pub mod component; @@ -76,4 +79,9 @@ impl CommonUI for UIBolt { let mut frame = WelcomeScreen::new(false); show(&mut frame, fade_in); } + + #[cfg(feature = "ui_debug_overlay")] + fn render_debug_overlay<'s>(_target: &mut impl shape::Renderer<'s>, _info: DebugOverlay) { + // Not implemented + } } diff --git a/core/embed/rust/src/ui/layout_caesar/mod.rs b/core/embed/rust/src/ui/layout_caesar/mod.rs index 49e51b088c..66d7709445 100644 --- a/core/embed/rust/src/ui/layout_caesar/mod.rs +++ b/core/embed/rust/src/ui/layout_caesar/mod.rs @@ -1,5 +1,8 @@ use super::{geometry::Rect, CommonUI}; +#[cfg(feature = "ui_debug_overlay")] +use super::{shape, DebugOverlay}; + #[cfg(feature = "bootloader")] pub mod bootloader; @@ -31,4 +34,9 @@ impl CommonUI for UICaesar { fn screen_boot_stage_2(fade_in: bool) { screens::screen_boot_stage_2(fade_in); } + + #[cfg(feature = "ui_debug_overlay")] + fn render_debug_overlay<'s>(_target: &mut impl shape::Renderer<'s>, _info: DebugOverlay) { + // Not implemented + } } diff --git a/core/embed/rust/src/ui/layout_delizia/mod.rs b/core/embed/rust/src/ui/layout_delizia/mod.rs index 3b210d235d..09d02d707b 100644 --- a/core/embed/rust/src/ui/layout_delizia/mod.rs +++ b/core/embed/rust/src/ui/layout_delizia/mod.rs @@ -1,4 +1,15 @@ use super::{geometry::Rect, CommonUI}; + +#[cfg(feature = "ui_debug_overlay")] +use super::{ + display::Color, + geometry::{Alignment, Alignment2D, Offset, Point}, + shape, DebugOverlay, +}; + +#[cfg(feature = "ui_debug_overlay")] +use crate::strutil::ShortString; + use theme::backlight; #[cfg(feature = "bootloader")] @@ -69,4 +80,34 @@ impl CommonUI for UIDelizia { fn screen_boot_stage_2(fade_in: bool) { screens::screen_boot_stage_2(fade_in); } + + #[cfg(feature = "ui_debug_overlay")] + fn render_debug_overlay<'s>(target: &mut impl shape::Renderer<'s>, info: DebugOverlay) { + let mut text = ShortString::new(); + let t1 = info.render_time.min(99999) as u32; + let t2 = info.refresh_time.min(99999) as u32; + unwrap!(ufmt::uwrite!( + text, + "{}.{}|{}.{}", + t1 / 1000, + (t1 % 1000) / 100, + t2 / 1000, + (t2 % 1000) / 100 + )); + let font = fonts::FONT_SUB; + let size = Offset::new( + font.visible_text_width("00.0|00.0"), + font.visible_text_height("0"), + ); + let pos = Point::new(constant::WIDTH, 0); + let r = Rect::snap(pos, size, Alignment2D::TOP_RIGHT); + shape::Bar::new(r) + .with_alpha(192) + .with_bg(Color::black()) + .render(target); + shape::Text::new(r.bottom_right(), &text, font) + .with_align(Alignment::End) + .with_fg(Color::rgb(255, 255, 0)) + .render(target); + } } diff --git a/core/embed/rust/src/ui/mod.rs b/core/embed/rust/src/ui/mod.rs index c99f669d5c..bbff0427ff 100644 --- a/core/embed/rust/src/ui/mod.rs +++ b/core/embed/rust/src/ui/mod.rs @@ -36,6 +36,9 @@ pub mod ui_firmware; pub use ui_common::CommonUI; +#[cfg(feature = "ui_debug_overlay")] +pub use ui_common::DebugOverlay; + #[cfg(all( feature = "layout_delizia", not(feature = "layout_caesar"), diff --git a/core/embed/rust/src/ui/shape/display/fb_rgb565.rs b/core/embed/rust/src/ui/shape/display/fb_rgb565.rs index 221b6125de..5bf6786366 100644 --- a/core/embed/rust/src/ui/shape/display/fb_rgb565.rs +++ b/core/embed/rust/src/ui/shape/display/fb_rgb565.rs @@ -1,17 +1,29 @@ -use crate::ui::{ - display::Color, - geometry::Offset, - shape::{ - render::ScopedRenderer, BasicCanvas, DirectRenderer, DrawingCache, Rgb565Canvas, Viewport, +use crate::{ + trezorhal::display, + ui::{ + display::Color, + geometry::Offset, + shape::{ + render::ScopedRenderer, BasicCanvas, DirectRenderer, DrawingCache, Rgb565Canvas, + Viewport, + }, }, }; +#[cfg(feature = "ui_debug_overlay")] +use crate::{ + trezorhal::time, + ui::{CommonUI, DebugOverlay, ModelUI}, +}; + use super::bumps; -use crate::trezorhal::display; - pub type ConcreteRenderer<'a, 'alloc> = DirectRenderer<'a, 'alloc, Rgb565Canvas<'alloc>>; +// Time of the last frame buffer get operation +#[cfg(feature = "ui_debug_overlay")] +static mut FRAME_BUFFER_GET_TIME: u64 = 0; + /// Creates the `Renderer` object for drawing on a display and invokes a /// user-defined function that takes a single argument `target`. The user's /// function can utilize the `target` for drawing on the display. @@ -32,8 +44,16 @@ where let cache = DrawingCache::new(bump_a, bump_b); + #[cfg(feature = "ui_debug_overlay")] + let refresh_time = unsafe { time::ticks_us() - FRAME_BUFFER_GET_TIME }; + let fb_info = display::get_frame_buffer(); + #[cfg(feature = "ui_debug_overlay")] + unsafe { + FRAME_BUFFER_GET_TIME = time::ticks_us() + }; + if fb_info.is_none() { return; } @@ -53,6 +73,21 @@ where let mut target = ScopedRenderer::new(DirectRenderer::new(&mut canvas, bg_color, &cache)); - func(&mut target); + // In debug mode, measure the time spent on rendering. + #[cfg(feature = "ui_debug_overlay")] + { + let render_time = time::measure_us(|| func(&mut target)); + let info = DebugOverlay { + render_time, + refresh_time, + }; + ModelUI::render_debug_overlay(&mut target, info); + } + + // In production, just execute the drawing function without timing. + #[cfg(not(feature = "ui_debug_overlay"))] + { + func(&mut target); + } }); } diff --git a/core/embed/rust/src/ui/shape/display/fb_rgba8888.rs b/core/embed/rust/src/ui/shape/display/fb_rgba8888.rs index 15d5303555..75ab15d601 100644 --- a/core/embed/rust/src/ui/shape/display/fb_rgba8888.rs +++ b/core/embed/rust/src/ui/shape/display/fb_rgba8888.rs @@ -1,17 +1,29 @@ -use crate::ui::{ - display::Color, - geometry::Offset, - shape::{ - render::ScopedRenderer, BasicCanvas, DirectRenderer, DrawingCache, Rgba8888Canvas, Viewport, +use crate::{ + trezorhal::display, + ui::{ + display::Color, + geometry::Offset, + shape::{ + render::ScopedRenderer, BasicCanvas, DirectRenderer, DrawingCache, Rgba8888Canvas, + Viewport, + }, }, }; +#[cfg(feature = "ui_debug_overlay")] +use crate::{ + trezorhal::time, + ui::{CommonUI, DebugOverlay, ModelUI}, +}; + use super::bumps; -use crate::trezorhal::display; - pub type ConcreteRenderer<'a, 'alloc> = DirectRenderer<'a, 'alloc, Rgba8888Canvas<'alloc>>; +// Time of the last frame buffer get operation +#[cfg(feature = "ui_debug_overlay")] +static mut FRAME_BUFFER_GET_TIME: u64 = 0; + /// Creates the `Renderer` object for drawing on a display and invokes a /// user-defined function that takes a single argument `target`. The user's /// function can utilize the `target` for drawing on the display. @@ -32,8 +44,16 @@ where let cache = DrawingCache::new(bump_a, bump_b); + #[cfg(feature = "ui_debug_overlay")] + let refresh_time = unsafe { time::ticks_us() - FRAME_BUFFER_GET_TIME }; + let fb_info = display::get_frame_buffer(); + #[cfg(feature = "ui_debug_overlay")] + unsafe { + FRAME_BUFFER_GET_TIME = time::ticks_us() + }; + if fb_info.is_none() { return; } @@ -53,6 +73,21 @@ where let mut target = ScopedRenderer::new(DirectRenderer::new(&mut canvas, bg_color, &cache)); - func(&mut target); + // In debug mode, measure the time spent on rendering. + #[cfg(feature = "ui_debug_overlay")] + { + let render_time = time::measure_us(|| func(&mut target)); + let info = DebugOverlay { + render_time, + refresh_time, + }; + ModelUI::render_debug_overlay(&mut target, info); + } + + // In production, just execute the drawing function without timing. + #[cfg(not(feature = "ui_debug_overlay"))] + { + func(&mut target); + } }); } diff --git a/core/embed/rust/src/ui/ui_common.rs b/core/embed/rust/src/ui/ui_common.rs index e01fe817e3..6ce9144ea8 100644 --- a/core/embed/rust/src/ui/ui_common.rs +++ b/core/embed/rust/src/ui/ui_common.rs @@ -1,5 +1,22 @@ use crate::ui::geometry::Rect; +#[cfg(feature = "ui_debug_overlay")] +use crate::ui::shape::Renderer; + +/// A structure containing information to be displayed in the debug overlay +/// on the screen when the "ui_debug_overlay" feature is enabled. +#[cfg(feature = "ui_debug_overlay")] +pub struct DebugOverlay { + /// Time (in microseconds) spent by the rendering functions. + pub render_time: u64, + + /// Time (in microseconds) between two consecutive screen refreshes. + /// This includes the time spent on rendering, as well as time used by + /// other activities in Rust and MicroPython, such as event processing + /// and animation calculations. + pub refresh_time: u64, +} + pub trait CommonUI { fn fadein() {} fn fadeout() {} @@ -26,4 +43,9 @@ pub trait CommonUI { fn screen_fatal_error(title: &str, msg: &str, footer: &str); fn screen_boot_stage_2(fade_in: bool); + + /// Renders a partially transparent overlay over the screen content + /// using data from the `DebugOverlay` struct. + #[cfg(feature = "ui_debug_overlay")] + fn render_debug_overlay<'s>(target: &mut impl Renderer<'s>, info: DebugOverlay); }