1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-08-01 19:38:33 +00:00

feat(rust): introduce info overlay rendering

[no changelog]
This commit is contained in:
cepetr 2025-02-14 12:24:23 +01:00 committed by cepetr
parent c72d85296d
commit c57a59d123
12 changed files with 183 additions and 17 deletions

View File

@ -45,6 +45,7 @@ QUIET_MODE ?= 0
TREZOR_DISABLE_ANIMATION ?= $(if $(filter 0,$(PYOPT)),1,0) TREZOR_DISABLE_ANIMATION ?= $(if $(filter 0,$(PYOPT)),1,0)
STORAGE_INSECURE_TESTING_MODE ?= 0 STORAGE_INSECURE_TESTING_MODE ?= 0
RUST_PRINT_TYPES_SIZES ?= 0 RUST_PRINT_TYPES_SIZES ?= 0
UI_DEBUG_OVERLAY ?= 0
# OpenOCD interface default. Alternative: ftdi/olimex-arm-usb-tiny-h # OpenOCD interface default. Alternative: ftdi/olimex-arm-usb-tiny-h
OPENOCD_INTERFACE ?= stlink OPENOCD_INTERFACE ?= stlink
@ -131,7 +132,8 @@ SCONS_VARS = \
TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" \ TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" \
TREZOR_EMULATOR_DEBUGGABLE=$(TREZOR_EMULATOR_DEBUGGABLE) \ TREZOR_EMULATOR_DEBUGGABLE=$(TREZOR_EMULATOR_DEBUGGABLE) \
TREZOR_MEMPERF="$(TREZOR_MEMPERF)" \ TREZOR_MEMPERF="$(TREZOR_MEMPERF)" \
TREZOR_MODEL="$(TREZOR_MODEL)" TREZOR_MODEL="$(TREZOR_MODEL)" \
UI_DEBUG_OVERLAY="$(UI_DEBUG_OVERLAY)"
SCONS_OPTS = -Q -j $(JOBS) SCONS_OPTS = -Q -j $(JOBS)
ifeq ($(QUIET_MODE),1) ifeq ($(QUIET_MODE),1)

View File

@ -9,6 +9,7 @@ CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
BOOTLOADER_QA = ARGUMENTS.get('BOOTLOADER_QA', '0') == '1' BOOTLOADER_QA = ARGUMENTS.get('BOOTLOADER_QA', '0') == '1'
PRODUCTION = 0 if BOOTLOADER_QA else ARGUMENTS.get('PRODUCTION', '0') == '1' PRODUCTION = 0 if BOOTLOADER_QA else ARGUMENTS.get('PRODUCTION', '0') == '1'
HW_REVISION = ARGUMENTS.get('HW_REVISION', None) 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"] 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 features = ['bootloader',] + FEATURES_AVAILABLE + RUST_UI_FEATURES
if UI_DEBUG_OVERLAY:
features.append('ui_debug_overlay')
rust = tools.add_rust_lib( rust = tools.add_rust_lib(
env, env,
'bootloader', 'bootloader',

View File

@ -20,6 +20,7 @@ SCM_REVISION = ARGUMENTS.get('SCM_REVISION', None)
THP = ARGUMENTS.get('THP', '0') == '1' # Trezor-Host Protocol THP = ARGUMENTS.get('THP', '0') == '1' # Trezor-Host Protocol
BENCHMARK = ARGUMENTS.get('BENCHMARK', '0') == '1' BENCHMARK = ARGUMENTS.get('BENCHMARK', '0') == '1'
DISABLE_ANIMATION = ARGUMENTS.get('TREZOR_DISABLE_ANIMATION', '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' STORAGE_INSECURE_TESTING_MODE = ARGUMENTS.get('STORAGE_INSECURE_TESTING_MODE', '0') == '1'
if STORAGE_INSECURE_TESTING_MODE and PRODUCTION: if STORAGE_INSECURE_TESTING_MODE and PRODUCTION:
@ -759,6 +760,8 @@ if PYOPT == '0':
features.append('ui_debug') features.append('ui_debug')
if EVERYTHING: if EVERYTHING:
features.append('universal_fw') features.append('universal_fw')
if UI_DEBUG_OVERLAY:
features.append('ui_debug_overlay')
rust = tools.add_rust_lib( rust = tools.add_rust_lib(
env, env,

View File

@ -8,6 +8,7 @@ CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0))
PRODUCTION = ARGUMENTS.get('PRODUCTION', '0') == '1' PRODUCTION = ARGUMENTS.get('PRODUCTION', '0') == '1'
BOOTLOADER_DEVEL = ARGUMENTS.get('BOOTLOADER_DEVEL', '0') == '1' BOOTLOADER_DEVEL = ARGUMENTS.get('BOOTLOADER_DEVEL', '0') == '1'
HW_REVISION = ARGUMENTS.get('HW_REVISION', None) 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"] 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 features = ['prodtest',] + FEATURES_AVAILABLE + RUST_UI_FEATURES
if UI_DEBUG_OVERLAY:
features.append('ui_debug_overlay')
rust = tools.add_rust_lib( rust = tools.add_rust_lib(
env, env,

View File

@ -20,6 +20,7 @@ display_mono = []
display_rgb565 = ["ui_antialiasing"] display_rgb565 = ["ui_antialiasing"]
display_rgba8888 = ["ui_antialiasing"] display_rgba8888 = ["ui_antialiasing"]
ui_debug = [] ui_debug = []
ui_debug_overlay = []
ui_antialiasing = [] ui_antialiasing = []
ui_blurring = [] ui_blurring = []
ui_image_buffer = [] ui_image_buffer = []

View File

@ -1,5 +1,8 @@
use super::{geometry::Rect, CommonUI}; use super::{geometry::Rect, CommonUI};
#[cfg(feature = "ui_debug_overlay")]
use super::{shape, DebugOverlay};
#[cfg(feature = "bootloader")] #[cfg(feature = "bootloader")]
pub mod bootloader; pub mod bootloader;
pub mod component; pub mod component;
@ -76,4 +79,9 @@ impl CommonUI for UIBolt {
let mut frame = WelcomeScreen::new(false); let mut frame = WelcomeScreen::new(false);
show(&mut frame, fade_in); 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
}
} }

View File

@ -1,5 +1,8 @@
use super::{geometry::Rect, CommonUI}; use super::{geometry::Rect, CommonUI};
#[cfg(feature = "ui_debug_overlay")]
use super::{shape, DebugOverlay};
#[cfg(feature = "bootloader")] #[cfg(feature = "bootloader")]
pub mod bootloader; pub mod bootloader;
@ -31,4 +34,9 @@ impl CommonUI for UICaesar {
fn screen_boot_stage_2(fade_in: bool) { fn screen_boot_stage_2(fade_in: bool) {
screens::screen_boot_stage_2(fade_in); 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
}
} }

View File

@ -1,4 +1,15 @@
use super::{geometry::Rect, CommonUI}; 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; use theme::backlight;
#[cfg(feature = "bootloader")] #[cfg(feature = "bootloader")]
@ -69,4 +80,34 @@ impl CommonUI for UIDelizia {
fn screen_boot_stage_2(fade_in: bool) { fn screen_boot_stage_2(fade_in: bool) {
screens::screen_boot_stage_2(fade_in); 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);
}
} }

View File

@ -36,6 +36,9 @@ pub mod ui_firmware;
pub use ui_common::CommonUI; pub use ui_common::CommonUI;
#[cfg(feature = "ui_debug_overlay")]
pub use ui_common::DebugOverlay;
#[cfg(all( #[cfg(all(
feature = "layout_delizia", feature = "layout_delizia",
not(feature = "layout_caesar"), not(feature = "layout_caesar"),

View File

@ -1,17 +1,29 @@
use crate::ui::{ use crate::{
display::Color, trezorhal::display,
geometry::Offset, ui::{
shape::{ display::Color,
render::ScopedRenderer, BasicCanvas, DirectRenderer, DrawingCache, Rgb565Canvas, Viewport, 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 super::bumps;
use crate::trezorhal::display;
pub type ConcreteRenderer<'a, 'alloc> = DirectRenderer<'a, 'alloc, Rgb565Canvas<'alloc>>; 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 /// Creates the `Renderer` object for drawing on a display and invokes a
/// user-defined function that takes a single argument `target`. The user's /// user-defined function that takes a single argument `target`. The user's
/// function can utilize the `target` for drawing on the display. /// function can utilize the `target` for drawing on the display.
@ -32,8 +44,16 @@ where
let cache = DrawingCache::new(bump_a, bump_b); 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(); 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() { if fb_info.is_none() {
return; return;
} }
@ -53,6 +73,21 @@ where
let mut target = ScopedRenderer::new(DirectRenderer::new(&mut canvas, bg_color, &cache)); 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);
}
}); });
} }

View File

@ -1,17 +1,29 @@
use crate::ui::{ use crate::{
display::Color, trezorhal::display,
geometry::Offset, ui::{
shape::{ display::Color,
render::ScopedRenderer, BasicCanvas, DirectRenderer, DrawingCache, Rgba8888Canvas, Viewport, 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 super::bumps;
use crate::trezorhal::display;
pub type ConcreteRenderer<'a, 'alloc> = DirectRenderer<'a, 'alloc, Rgba8888Canvas<'alloc>>; 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 /// Creates the `Renderer` object for drawing on a display and invokes a
/// user-defined function that takes a single argument `target`. The user's /// user-defined function that takes a single argument `target`. The user's
/// function can utilize the `target` for drawing on the display. /// function can utilize the `target` for drawing on the display.
@ -32,8 +44,16 @@ where
let cache = DrawingCache::new(bump_a, bump_b); 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(); 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() { if fb_info.is_none() {
return; return;
} }
@ -53,6 +73,21 @@ where
let mut target = ScopedRenderer::new(DirectRenderer::new(&mut canvas, bg_color, &cache)); 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);
}
}); });
} }

View File

@ -1,5 +1,22 @@
use crate::ui::geometry::Rect; 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 { pub trait CommonUI {
fn fadein() {} fn fadein() {}
fn fadeout() {} fn fadeout() {}
@ -26,4 +43,9 @@ pub trait CommonUI {
fn screen_fatal_error(title: &str, msg: &str, footer: &str); fn screen_fatal_error(title: &str, msg: &str, footer: &str);
fn screen_boot_stage_2(fade_in: bool); 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);
} }