From 224920afa9141e3c0b09162e4f5b7308aefe384d Mon Sep 17 00:00:00 2001 From: cepetr Date: Tue, 16 Apr 2024 15:52:17 +0200 Subject: [PATCH 01/14] feat(core): refactor display drivers [no changelog] --- core/Makefile | 20 +- core/SConscript.boardloader | 20 +- core/SConscript.bootloader | 26 +- core/SConscript.bootloader_ci | 3 +- core/SConscript.bootloader_emu | 69 +- core/SConscript.firmware | 28 +- core/SConscript.prodtest | 2 +- core/SConscript.reflash | 2 +- core/SConscript.unix | 60 +- core/embed/boardloader/main.c | 2 + core/embed/bootloader/bootui.c | 1 + core/embed/bootloader/emulator.c | 4 - core/embed/bootloader/memory_stm32u58.ld | 2 + core/embed/bootloader/memory_stm32u5a.ld | 2 + core/embed/bootloader_ci/bootui.c | 1 + core/embed/bootloader_ci/main.c | 1 + core/embed/bootloader_ci/memory_stm32f4.ld | 6 + .../extmod/modtrezorui/modtrezorui-display.h | 6 + core/embed/firmware/memory_DISC2.ld | 2 + core/embed/firmware/memory_T.ld | 5 + core/embed/lib/{display.c => display_draw.c} | 8 +- core/embed/lib/{display.h => display_draw.h} | 5 +- core/embed/lib/dma2d_emul.c | 2 +- core/embed/lib/gl_bitblt.h | 120 ++ core/embed/lib/gl_bitblt_mono8.c | 110 ++ core/embed/lib/gl_bitblt_rgb565.c | 136 ++ core/embed/lib/gl_bitblt_rgba8888.c | 156 ++ core/embed/lib/gl_color.c | 49 + core/embed/lib/gl_color.h | 337 ++++ core/embed/lib/gl_draw.c | 275 +++ core/embed/lib/gl_draw.h | 152 ++ core/embed/lib/terminal.c | 66 +- core/embed/lib/terminal.h | 2 - core/embed/prodtest/main.c | 1 + core/embed/reflash/main.c | 1 + core/embed/rust/Cargo.toml | 8 + core/embed/rust/trezorhal.h | 5 +- .../embed/trezorhal/boards/stm32f429i-disc1.h | 6 +- core/embed/trezorhal/boards/stm32u5a9j-dk.h | 6 +- core/embed/trezorhal/boards/trezor_1.h | 3 +- core/embed/trezorhal/boards/trezor_r_v10.h | 4 +- core/embed/trezorhal/boards/trezor_r_v3.h | 4 +- core/embed/trezorhal/boards/trezor_r_v4.h | 4 +- core/embed/trezorhal/boards/trezor_r_v6.h | 4 +- core/embed/trezorhal/boards/trezor_t.h | 11 +- .../embed/trezorhal/boards/trezor_t3t1_revE.h | 14 +- core/embed/trezorhal/boards/trezor_t3t1_v4.h | 14 +- .../display.h} | 16 +- core/embed/trezorhal/dma2d_bitblt.h | 45 + core/embed/trezorhal/stm32f4/common.c | 4 + .../stm32f4/display/st-7789/display_driver.c | 149 ++ .../stm32f4/display/st-7789/display_fb.c | 218 +++ .../stm32f4/display/st-7789/display_fb.h | 32 + .../stm32f4/display/st-7789/display_io.c | 145 ++ .../stm32f4/display/st-7789/display_io.h | 67 + .../stm32f4/display/st-7789/display_nofb.c | 103 ++ .../stm32f4/display/st-7789/display_panel.c | 244 +++ .../stm32f4/display/st-7789/display_panel.h | 57 + .../stm32f4/display/st-7789/panels/154a.c | 132 ++ .../stm32f4/display/st-7789/panels/154a.h | 27 + .../display/st-7789/panels/lx154a2411.c | 82 + .../display/st-7789/panels/lx154a2411.h | 27 + .../display/st-7789/panels/lx154a2422.c | 159 ++ .../display/st-7789/panels/lx154a2422.h | 29 + .../stm32f4/display/st-7789/panels/tf15411a.c | 174 ++ .../stm32f4/display/st-7789/panels/tf15411a.h | 30 + .../display/stm32f429i-disc1/display_driver.c | 154 ++ .../stm32f429i-disc1/display_internal.h | 36 + .../display/stm32f429i-disc1/display_ltdc.c | 291 +++ .../display/stm32f429i-disc1/ili9341_spi.c | 512 ++++++ .../display/stm32f429i-disc1/ili9341_spi.h | 13 + .../stm32f4/display/ug-2828/display_driver.c | 398 +++++ .../stm32f4/display/vg-2864/display_driver.c | 379 ++++ core/embed/trezorhal/stm32f4/displays/ltdc.c | 20 +- core/embed/trezorhal/stm32f4/displays/ltdc.h | 4 +- .../stm32f4/displays/panels/lx154a2422.c | 2 +- .../stm32f4/displays/panels/tf15411a.c | 2 +- .../trezorhal/stm32f4/displays/st7789v.c | 8 +- .../stm32f4/displays/ug-2828tswig01.c | 2 +- .../stm32f4/displays/vg-2864ksweg01.c | 2 +- core/embed/trezorhal/stm32f4/dma2d.c | 2 +- core/embed/trezorhal/stm32f4/dma2d_bitblt.c | 599 +++++++ core/embed/trezorhal/stm32f4/secret.c | 2 +- core/embed/trezorhal/stm32f4/touch/ft6x36.c | 4 +- core/embed/trezorhal/stm32u5/display/st-7789 | 1 + .../display/stm32u5a9j-dk/display_driver.c | 155 ++ .../display/stm32u5a9j-dk/display_fb.c | 77 + .../stm32u5a9j-dk/display_gfxmmu_lut.h | 1006 +++++++++++ .../display/stm32u5a9j-dk/display_internal.h | 58 + .../display/stm32u5a9j-dk/display_ltdc_dsi.c | 1562 +++++++++++++++++ core/embed/trezorhal/stm32u5/display/vg-2864 | 1 + core/embed/trezorhal/stm32u5/displays/dsi.h | 4 - core/embed/trezorhal/stm32u5/dma2d_bitblt.c | 1 + core/embed/trezorhal/unix/display-unix.c | 4 +- core/embed/trezorhal/unix/display_driver.c | 461 +++++ core/embed/trezorhal/unix/dma2d_bitblt.c | 22 + core/embed/trezorhal/xdisplay.h | 156 ++ core/embed/trezorhal/xdisplay_legacy.c | 43 + core/embed/trezorhal/xdisplay_legacy.h | 56 + core/embed/unix/main.c | 3 + core/site_scons/boards/discovery.py | 26 +- core/site_scons/boards/discovery2.py | 27 +- core/site_scons/boards/trezor_r_v10.py | 17 +- core/site_scons/boards/trezor_r_v3.py | 17 +- core/site_scons/boards/trezor_r_v4.py | 17 +- core/site_scons/boards/trezor_r_v6.py | 17 +- core/site_scons/boards/trezor_t.py | 51 +- core/site_scons/boards/trezor_t3t1_revE.py | 28 +- core/site_scons/boards/trezor_t3t1_v4.py | 28 +- 109 files changed, 9631 insertions(+), 142 deletions(-) rename core/embed/lib/{display.c => display_draw.c} (98%) rename core/embed/lib/{display.h => display_draw.h} (95%) create mode 100644 core/embed/lib/gl_bitblt.h create mode 100644 core/embed/lib/gl_bitblt_mono8.c create mode 100644 core/embed/lib/gl_bitblt_rgb565.c create mode 100644 core/embed/lib/gl_bitblt_rgba8888.c create mode 100644 core/embed/lib/gl_color.c create mode 100644 core/embed/lib/gl_color.h create mode 100644 core/embed/lib/gl_draw.c create mode 100644 core/embed/lib/gl_draw.h rename core/embed/{lib/display_interface.h => trezorhal/display.h} (89%) create mode 100644 core/embed/trezorhal/dma2d_bitblt.h create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/display_driver.c create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/display_fb.c create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/display_fb.h create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/display_io.c create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/display_io.h create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/display_nofb.c create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/display_panel.c create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/display_panel.h create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/panels/154a.c create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/panels/154a.h create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2411.c create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2411.h create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2422.c create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2422.h create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/panels/tf15411a.c create mode 100644 core/embed/trezorhal/stm32f4/display/st-7789/panels/tf15411a.h create mode 100644 core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/display_driver.c create mode 100644 core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/display_internal.h create mode 100644 core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/display_ltdc.c create mode 100644 core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/ili9341_spi.c create mode 100644 core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/ili9341_spi.h create mode 100644 core/embed/trezorhal/stm32f4/display/ug-2828/display_driver.c create mode 100644 core/embed/trezorhal/stm32f4/display/vg-2864/display_driver.c create mode 100644 core/embed/trezorhal/stm32f4/dma2d_bitblt.c create mode 120000 core/embed/trezorhal/stm32u5/display/st-7789 create mode 100644 core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_driver.c create mode 100644 core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_fb.c create mode 100644 core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_gfxmmu_lut.h create mode 100644 core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_internal.h create mode 100644 core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_ltdc_dsi.c create mode 120000 core/embed/trezorhal/stm32u5/display/vg-2864 create mode 120000 core/embed/trezorhal/stm32u5/dma2d_bitblt.c create mode 100644 core/embed/trezorhal/unix/display_driver.c create mode 100644 core/embed/trezorhal/unix/dma2d_bitblt.c create mode 100644 core/embed/trezorhal/xdisplay.h create mode 100644 core/embed/trezorhal/xdisplay_legacy.c create mode 100644 core/embed/trezorhal/xdisplay_legacy.h diff --git a/core/Makefile b/core/Makefile index dba27525e..ae74b037f 100644 --- a/core/Makefile +++ b/core/Makefile @@ -233,18 +233,20 @@ build_embed: build_boardloader build_bootloader build_firmware # build boardload build_boardloader: ## build boardloader $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \ - CMAKELISTS="$(CMAKELISTS)" $(BOARDLOADER_BUILD_DIR)/boardloader.bin + CMAKELISTS="$(CMAKELISTS)" NEW_RENDERING="$(NEW_RENDERING)" $(BOARDLOADER_BUILD_DIR)/boardloader.bin build_bootloader: ## build bootloader $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \ - CMAKELISTS="$(CMAKELISTS)" BOOTLOADER_QA="$(BOOTLOADER_QA)" BOOTLOADER_DEVEL="$(BOOTLOADER_DEVEL)" $(BOOTLOADER_BUILD_DIR)/bootloader.bin + CMAKELISTS="$(CMAKELISTS)" BOOTLOADER_QA="$(BOOTLOADER_QA)" BOOTLOADER_DEVEL="$(BOOTLOADER_DEVEL)" \ + NEW_RENDERING="$(NEW_RENDERING)" $(BOOTLOADER_BUILD_DIR)/bootloader.bin build_bootloader_ci: ## build CI device testing bootloader $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \ CMAKELISTS="$(CMAKELISTS)" $(BOOTLOADER_CI_BUILD_DIR)/bootloader.bin build_bootloader_emu: ## build the unix bootloader emulator - $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" $(BOOTLOADER_EMU_BUILD_DIR)/bootloader.elf + $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \ + CMAKELISTS="$(CMAKELISTS)" NEW_RENDERING="$(NEW_RENDERING)" $(BOOTLOADER_EMU_BUILD_DIR)/bootloader.elf build_prodtest: ## build production test firmware $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \ @@ -252,7 +254,7 @@ build_prodtest: ## build production test firmware build_reflash: ## build reflash firmware + reflash image $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \ - CMAKELISTS="$(CMAKELISTS)" $(REFLASH_BUILD_DIR)/reflash.bin + CMAKELISTS="$(CMAKELISTS)" NEW_RENDERING="$(NEW_RENDERING)" $(REFLASH_BUILD_DIR)/reflash.bin dd if=build/boardloader/boardloader.bin of=$(REFLASH_BUILD_DIR)/sdimage.bin bs=1 seek=0 dd if=build/bootloader/bootloader.bin of=$(REFLASH_BUILD_DIR)/sdimage.bin bs=1 seek=49152 @@ -261,24 +263,26 @@ build_firmware: templates build_cross ## build firmware with frozen modules TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" \ PYOPT="$(PYOPT)" BITCOIN_ONLY="$(BITCOIN_ONLY)" \ BOOTLOADER_QA="$(BOOTLOADER_QA)" BOOTLOADER_DEVEL="$(BOOTLOADER_DEVEL)" \ - DISABLE_OPTIGA="$(DISABLE_OPTIGA)" \ + DISABLE_OPTIGA="$(DISABLE_OPTIGA)" NEW_RENDERING="$(NEW_RENDERING)"\ $(FIRMWARE_BUILD_DIR)/firmware.bin build_unix: templates ## build unix port $(SCONS) CFLAGS="$(CFLAGS)" $(UNIX_BUILD_DIR)/trezor-emu-core $(UNIX_PORT_OPTS) \ TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" \ - PYOPT="0" BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" + PYOPT="0" BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" \ + NEW_RENDERING="$(NEW_RENDERING)" build_unix_frozen: templates build_cross ## build unix port with frozen modules $(SCONS) CFLAGS="$(CFLAGS)" $(UNIX_BUILD_DIR)/trezor-emu-core $(UNIX_PORT_OPTS) \ TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" \ PYOPT="$(PYOPT)" BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN="$(ADDRESS_SANITIZER)" \ - TREZOR_MEMPERF="$(TREZOR_MEMPERF)" TREZOR_EMULATOR_FROZEN=1 + TREZOR_MEMPERF="$(TREZOR_MEMPERF)" TREZOR_EMULATOR_FROZEN=1 NEW_RENDERING="$(NEW_RENDERING)" build_unix_debug: templates ## build unix port $(SCONS) --max-drift=1 CFLAGS="$(CFLAGS)" $(UNIX_BUILD_DIR)/trezor-emu-core $(UNIX_PORT_OPTS) \ TREZOR_MODEL="$(TREZOR_MODEL)" CMAKELISTS="$(CMAKELISTS)" \ - BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN=1 TREZOR_EMULATOR_DEBUGGABLE=1 + BITCOIN_ONLY="$(BITCOIN_ONLY)" TREZOR_EMULATOR_ASAN=1 TREZOR_EMULATOR_DEBUGGABLE=1 \ + NEW_RENDERING="$(NEW_RENDERING)" build_cross: ## build mpy-cross port $(MAKE) -C vendor/micropython/mpy-cross $(CROSS_PORT_OPTS) diff --git a/core/SConscript.boardloader b/core/SConscript.boardloader index 03a7b731b..4e604874d 100644 --- a/core/SConscript.boardloader +++ b/core/SConscript.boardloader @@ -5,6 +5,7 @@ import tools TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T') CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0)) +NEW_RENDERING = ARGUMENTS.get('NEW_RENDERING', '1') == '1' if TREZOR_MODEL in ('1', ): # skip boardloader build @@ -20,6 +21,9 @@ if TREZOR_MODEL in ('1', ): FEATURES_WANTED = ["sd_card"] +if NEW_RENDERING: + FEATURES_WANTED.append("new_rendering") + CCFLAGS_MOD = '' CPPPATH_MOD = [] CPPDEFINES_MOD = ["BOARDLOADER"] @@ -60,13 +64,27 @@ CPPPATH_MOD += [ SOURCE_MOD += [ 'embed/lib/colors.c', 'embed/lib/display_utils.c', - 'embed/lib/display.c', 'embed/lib/fonts/font_bitmap.c', 'embed/lib/fonts/fonts.c', + 'embed/lib/gl_color.c', + 'embed/lib/gl_bitblt_rgb565.c', + 'embed/lib/gl_bitblt_rgba8888.c', + 'embed/lib/gl_bitblt_mono8.c', 'embed/lib/image.c', 'embed/lib/mini_printf.c', 'embed/lib/terminal.c', ] +if NEW_RENDERING: + CPPDEFINES_MOD += ['NEW_RENDERING'] + SOURCE_MOD += [ + 'embed/lib/gl_draw.c', + ] +else: + SOURCE_MOD += [ + 'embed/lib/display_draw.c', + ] + + env = Environment(ENV=os.environ, CFLAGS='%s -DPRODUCTION=%s' % (ARGUMENTS.get('CFLAGS', ''), ARGUMENTS.get('PRODUCTION', '0')), diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader index 5ae55ca56..7b54648c0 100644 --- a/core/SConscript.bootloader +++ b/core/SConscript.bootloader @@ -7,6 +7,7 @@ TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T') 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' +NEW_RENDERING = ARGUMENTS.get('NEW_RENDERING', '1') == '1' if TREZOR_MODEL in ('1', ): # skip bootloader build @@ -22,6 +23,9 @@ if TREZOR_MODEL in ('1', ): FEATURES_WANTED = ["input", "rgb_led", "consumption_mask", "usb", "optiga", "dma2d"] +if NEW_RENDERING: + FEATURES_WANTED.append("new_rendering") + CCFLAGS_MOD = '' CPPPATH_MOD = [] CPPDEFINES_MOD = [] @@ -90,9 +94,12 @@ SOURCE_MOD += [ 'embed/lib/buffers.c', 'embed/lib/colors.c', 'embed/lib/display_utils.c', - 'embed/lib/display.c', 'embed/lib/fonts/font_bitmap.c', 'embed/lib/fonts/fonts.c', + 'embed/lib/gl_color.c', + 'embed/lib/gl_bitblt_mono8.c', + 'embed/lib/gl_bitblt_rgb565.c', + 'embed/lib/gl_bitblt_rgba8888.c', 'embed/lib/image.c', 'embed/lib/mini_printf.c', 'embed/lib/terminal.c', @@ -102,6 +109,17 @@ SOURCE_MOD += [ 'vendor/micropython/lib/uzlib/tinflate.c', ] +if NEW_RENDERING: + CPPDEFINES_MOD += ['NEW_RENDERING'] + SOURCE_MOD += [ + 'embed/lib/gl_draw.c', + ] +else: + SOURCE_MOD += [ + 'embed/lib/display_draw.c', + ] + + SOURCE_NANOPB = [ 'vendor/nanopb/pb_common.c', 'vendor/nanopb/pb_decode.c', @@ -228,6 +246,12 @@ def cargo_build(): features.append("bootloader") features.extend(FEATURES_AVAILABLE) + if NEW_RENDERING: + features.append('new_rendering') + + if TREZOR_MODEL in ('T',): + features.append('ui_antialiasing') + cargo_opts = [ f'--target={env.get("ENV")["RUST_TARGET"]}', f'--target-dir=../../build/bootloader/rust', diff --git a/core/SConscript.bootloader_ci b/core/SConscript.bootloader_ci index 1835b9305..c1f540868 100644 --- a/core/SConscript.bootloader_ci +++ b/core/SConscript.bootloader_ci @@ -5,6 +5,7 @@ import tools TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T') CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0)) +NEW_RENDERING = ARGUMENTS.get('NEW_RENDERING', '1') == '1' if TREZOR_MODEL in ('1', 'DISC1', 'DISC2'): # skip bootloader_ci build @@ -82,8 +83,8 @@ CPPPATH_MOD += [ SOURCE_MOD += [ 'embed/extmod/modtrezorcrypto/rand.c', 'embed/lib/colors.c', + 'embed/lib/display_draw.c', 'embed/lib/display_utils.c', - 'embed/lib/display.c', 'embed/lib/fonts/font_bitmap.c', 'embed/lib/fonts/fonts.c', 'embed/lib/image.c', diff --git a/core/SConscript.bootloader_emu b/core/SConscript.bootloader_emu index b09500f1a..c57cecd0b 100644 --- a/core/SConscript.bootloader_emu +++ b/core/SConscript.bootloader_emu @@ -6,6 +6,7 @@ import boards TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T') CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0)) +NEW_RENDERING = ARGUMENTS.get('NEW_RENDERING', '1') == '1' DMA2D = False if TREZOR_MODEL in ('1', 'DISC1'): @@ -22,6 +23,9 @@ if TREZOR_MODEL in ('1', 'DISC1'): FEATURES_WANTED = ["input", "rgb_led", "dma2d"] +if NEW_RENDERING: + FEATURES_WANTED.append("new_rendering") + CCFLAGS_MOD = '' CPPPATH_MOD = [] CPPDEFINES_MOD = [] @@ -87,10 +91,12 @@ SOURCE_MOD += [ 'embed/lib/buffers.c', 'embed/lib/colors.c', 'embed/lib/display_utils.c', - 'embed/lib/display.c', - 'embed/lib/dma2d_emul.c', 'embed/lib/fonts/font_bitmap.c', 'embed/lib/fonts/fonts.c', + 'embed/lib/gl_color.c', + 'embed/lib/gl_bitblt_mono8.c', + 'embed/lib/gl_bitblt_rgb565.c', + 'embed/lib/gl_bitblt_rgba8888.c', 'embed/lib/image.c', 'embed/lib/terminal.c', 'embed/lib/touch.c', @@ -101,6 +107,23 @@ SOURCE_MOD += [ 'vendor/trezor-storage/flash_area.c', ] +if NEW_RENDERING: + CPPDEFINES_MOD += ['NEW_RENDERING'] + if TREZOR_MODEL in ('T',): + CPPDEFINES_MOD += ['DISPLAY_RGB565'] + elif TREZOR_MODEL in ('R', '1',): + CPPDEFINES_MOD += ['XFRAMEBUFFER', 'DISPLAY_MONO'] + elif TREZOR_MODEL in ('T3T1',): + CPPDEFINES_MOD += ['XFRAMEBUFFER', 'DISPLAY_RGB565'] + SOURCE_MOD += [ + 'embed/lib/gl_draw.c', + ] +else: + SOURCE_MOD += [ + 'embed/lib/display_draw.c', + 'embed/lib/dma2d_emul.c', +] + if TREZOR_MODEL in ('1', ): SOURCE_MOD += [ 'embed/models/model_T1B1_layout.c', @@ -135,7 +158,6 @@ SOURCE_BOOTLOADER = [ SOURCE_TREZORHAL = [ 'embed/trezorhal/unix/boot_args.c', - 'embed/trezorhal/unix/display-unix.c', 'embed/trezorhal/unix/fault_handlers.c', 'embed/trezorhal/unix/flash.c', 'embed/trezorhal/unix/flash_otp.c', @@ -147,6 +169,17 @@ SOURCE_TREZORHAL = [ 'embed/trezorhal/unix/secret.c', ] +if NEW_RENDERING: + SOURCE_TREZORHAL += [ + 'embed/trezorhal/unix/display_driver.c', + 'embed/trezorhal/unix/dma2d_bitblt.c', + 'embed/trezorhal/xdisplay_legacy.c', + ] +else: + SOURCE_TREZORHAL += [ + 'embed/trezorhal/unix/display-unix.c', + ] + if TREZOR_MODEL in ('R', 'T3T1'): SOURCE_TREZORHAL += [ 'embed/trezorhal/unix/optiga_hal.c', @@ -260,17 +293,18 @@ cmake_gen = env.Command( # RUST_TARGET = 'x86_64-unknown-linux-gnu' -RUST_PROFILE = 'release' RUST_LIB = 'trezor_lib' -RUST_LIBDIR = f'build/bootloader_emu/rust/{RUST_TARGET}/{RUST_PROFILE}' + +if ARGUMENTS.get('TREZOR_EMULATOR_DEBUGGABLE', '0') == '1': + RUST_PROFILE = 'dev' + RUST_LIBDIR = f'build/bootloader_emu/rust/{RUST_TARGET}/debug' +else: + RUST_PROFILE = 'release' + RUST_LIBDIR = f'build/bootloader_emu/rust/{RUST_TARGET}/release' + RUST_LIBPATH = f'{RUST_LIBDIR}/lib{RUST_LIB}.a' def cargo_build(): - # Determine the profile build flags. - if RUST_PROFILE == 'release': - profile = '--release' - else: - profile = '' if TREZOR_MODEL in ("1",): features = ["model_t1"] elif TREZOR_MODEL in ("R",): @@ -280,6 +314,19 @@ def cargo_build(): else: features = ["model_tt"] + if NEW_RENDERING: + features.append('new_rendering') + if TREZOR_MODEL in ('T',): + features.append('display_rgb565') + features.append('ui_antialiasing') + elif TREZOR_MODEL in ('R', '1',): + features.append('display_mono') + features.append('xframebuffer') + elif TREZOR_MODEL in ('T3T1',): + features.append('display_rgb565') + features.append('xframebuffer') + features.append('ui_antialiasing') + if TREZOR_MODEL in ('T', 'T3T1'): features.append('touch') features.append('backlight') @@ -299,7 +346,7 @@ def cargo_build(): '-Z build-std-features=panic_immediate_abort', ] - return f'cd embed/rust; cargo build {profile} ' + ' '.join(cargo_opts) + return f'cd embed/rust; cargo build --profile {RUST_PROFILE} ' + ' '.join(cargo_opts) rust = env.Command( target=RUST_LIBPATH, diff --git a/core/SConscript.firmware b/core/SConscript.firmware index e7c6ea4cf..aacafa3ab 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -13,6 +13,7 @@ TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T') CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0)) PYOPT = ARGUMENTS.get('PYOPT', '1') DISABLE_OPTIGA = ARGUMENTS.get('DISABLE_OPTIGA', '0') == '1' +NEW_RENDERING = ARGUMENTS.get('NEW_RENDERING', '1') == '1' FEATURE_FLAGS = { @@ -25,6 +26,8 @@ FEATURE_FLAGS = { FEATURES_WANTED = ["input", "sbu", "sd_card", "rgb_led", "dma2d", "consumption_mask", "usb" ,"optiga", "haptic"] if DISABLE_OPTIGA and PYOPT == '0': FEATURES_WANTED.remove("optiga") +if NEW_RENDERING: + FEATURES_WANTED.append("new_rendering") CCFLAGS_MOD = '' CPPPATH_MOD = [] @@ -203,9 +206,12 @@ SOURCE_MOD += [ 'embed/lib/buffers.c', 'embed/lib/colors.c', 'embed/lib/display_utils.c', - 'embed/lib/display.c', 'embed/lib/fonts/font_bitmap.c', 'embed/lib/fonts/fonts.c', + 'embed/lib/gl_color.c', + 'embed/lib/gl_bitblt_rgb565.c', + 'embed/lib/gl_bitblt_rgba8888.c', + 'embed/lib/gl_bitblt_mono8.c', 'embed/lib/image.c', 'embed/lib/mini_printf.c', 'embed/lib/terminal.c', @@ -216,6 +222,17 @@ SOURCE_MOD += [ 'vendor/micropython/lib/uzlib/tinflate.c', ] +if NEW_RENDERING: + CPPDEFINES_MOD += ['NEW_RENDERING'] + SOURCE_MOD += [ + 'embed/lib/gl_draw.c', + ] +else: + SOURCE_MOD += [ + 'embed/lib/display_draw.c', + ] + + CPPDEFINES_MOD += [ 'TREZOR_UI2', 'TRANSLATIONS', @@ -744,8 +761,17 @@ def cargo_build(): features.append('universal_fw') features.append('ui') features.append('translations') + + if NEW_RENDERING: + features.append('new_rendering') + if PYOPT == '0': features.append('debug') + features.append('ui_debug') + if TREZOR_MODEL in ('T', 'T3T1', 'DISC1', 'DISC2'): + features.append('ui_antialiasing') + features.append('ui_blurring') + features.append('ui_jpeg_decoder') features.extend(FEATURES_AVAILABLE) diff --git a/core/SConscript.prodtest b/core/SConscript.prodtest index 22a6626d0..8f802a344 100644 --- a/core/SConscript.prodtest +++ b/core/SConscript.prodtest @@ -81,8 +81,8 @@ CPPPATH_MOD += [ SOURCE_MOD += [ 'embed/lib/colors.c', + 'embed/lib/display_draw.c', 'embed/lib/display_utils.c', - 'embed/lib/display.c', 'embed/lib/fonts/font_bitmap.c', 'embed/lib/fonts/fonts.c', 'embed/lib/image.c', diff --git a/core/SConscript.reflash b/core/SConscript.reflash index 4772d45a9..9b4e2d481 100644 --- a/core/SConscript.reflash +++ b/core/SConscript.reflash @@ -55,8 +55,8 @@ CPPPATH_MOD += [ ] SOURCE_MOD += [ 'embed/lib/colors.c', + 'embed/lib/display_draw.c', 'embed/lib/display_utils.c', - 'embed/lib/display.c', 'embed/lib/fonts/font_bitmap.c', 'embed/lib/fonts/fonts.c', 'embed/lib/image.c', diff --git a/core/SConscript.unix b/core/SConscript.unix index 64f639bfe..0a848b301 100644 --- a/core/SConscript.unix +++ b/core/SConscript.unix @@ -10,6 +10,7 @@ TREZOR_MODEL = ARGUMENTS.get('TREZOR_MODEL', 'T') DMA2D = TREZOR_MODEL in ('T', 'T3T1') OPTIGA = TREZOR_MODEL in ('R', 'T3T1') CMAKELISTS = int(ARGUMENTS.get('CMAKELISTS', 0)) +NEW_RENDERING = ARGUMENTS.get('NEW_RENDERING', '1') == '1' if TREZOR_MODEL in ('DISC1', 'DISC2'): # skip unix build @@ -205,9 +206,12 @@ SOURCE_MOD += [ 'embed/lib/buffers.c', 'embed/lib/colors.c', 'embed/lib/display_utils.c', - 'embed/lib/display.c', 'embed/lib/fonts/font_bitmap.c', 'embed/lib/fonts/fonts.c', + 'embed/lib/gl_color.c', + 'embed/lib/gl_bitblt_rgb565.c', + 'embed/lib/gl_bitblt_rgba8888.c', + 'embed/lib/gl_bitblt_mono8.c', 'embed/lib/image.c', 'embed/lib/terminal.c', 'embed/lib/translations.c', @@ -217,6 +221,16 @@ SOURCE_MOD += [ 'vendor/micropython/lib/uzlib/tinflate.c', ] +if NEW_RENDERING: + SOURCE_MOD += [ + 'embed/lib/gl_draw.c', + ] +else: + SOURCE_MOD += [ + 'embed/lib/display_draw.c', + ] + + if TREZOR_MODEL in ('1', ): SOURCE_MOD += [ 'embed/models/model_T1B1_layout.c', @@ -250,6 +264,17 @@ if FROZEN: if RASPI: CPPDEFINES_MOD += ['TREZOR_EMULATOR_RASPI'] +if NEW_RENDERING: + CPPDEFINES_MOD += ['NEW_RENDERING'] + if TREZOR_MODEL in ('T',): + CPPDEFINES_MOD += ['DISPLAY_RGB565'] + elif TREZOR_MODEL in ('R', '1',): + CPPDEFINES_MOD += ['XFRAMEBUFFER', 'DISPLAY_MONO'] + elif TREZOR_MODEL in ('T3T1',): + CPPDEFINES_MOD += ['XFRAMEBUFFER', 'DISPLAY_RGB565'] + + + # modtrezorutils SOURCE_MOD += [ 'embed/extmod/modtrezorutils/modtrezorutils.c', @@ -394,7 +419,6 @@ SOURCE_MICROPYTHON = [ SOURCE_UNIX = [ 'embed/trezorhal/unix/boot_args.c', 'embed/trezorhal/unix/common.c', - 'embed/trezorhal/unix/display-unix.c', 'embed/trezorhal/unix/flash.c', 'embed/trezorhal/unix/flash_otp.c', 'embed/trezorhal/unix/random_delays.c', @@ -410,6 +434,20 @@ SOURCE_UNIX = [ 'vendor/micropython/ports/unix/input.c', 'vendor/micropython/ports/unix/unix_mphal.c', ] + +if NEW_RENDERING: + SOURCE_MOD += [ + 'embed/trezorhal/unix/display_driver.c', + 'embed/trezorhal/unix/dma2d_bitblt.c', + 'embed/trezorhal/xdisplay_legacy.c', + ] +else: + SOURCE_MOD += [ + 'embed/trezorhal/unix/display-unix.c', + 'embed/lib/dma2d_emul.c', + ] + + if TREZOR_MODEL in ('T', 'R', 'T3T1'): SOURCE_UNIX += [ 'embed/trezorhal/unix/sbu.c', @@ -424,9 +462,6 @@ if DMA2D: CPPDEFINES_MOD += [ 'USE_DMA2D', ] - SOURCE_UNIX += [ - 'embed/lib/dma2d_emul.c', - ] TRANSLATION_DATA = [ @@ -839,9 +874,24 @@ def cargo_build(): if TREZOR_MODEL in ('T', 'T3T1'): features.append('touch') features.append('sd_card') + features.append('ui_antialiasing') + features.append('ui_blurring') + features.append('ui_jpeg_decoder') if TREZOR_MODEL in ('R', '1'): features.append('button') + + if NEW_RENDERING: + features.append('new_rendering') + if TREZOR_MODEL in ('T',): + features.append('display_rgb565') + elif TREZOR_MODEL in ('R', '1',): + features.append('display_mono') + features.append('xframebuffer') + elif TREZOR_MODEL in ('T3T1',): + features.append('display_rgb565') + features.append('xframebuffer') + env.get('ENV')['TREZOR_MODEL'] = TREZOR_MODEL return f'cd embed/rust; cargo build --profile {RUST_PROFILE} --target-dir=../../build/unix/rust --no-default-features --features "{" ".join(features)}" --target {TARGET}' diff --git a/core/embed/boardloader/main.c b/core/embed/boardloader/main.c index d5dedfe02..6da1c2d8e 100644 --- a/core/embed/boardloader/main.c +++ b/core/embed/boardloader/main.c @@ -21,9 +21,11 @@ #include TREZOR_BOARD #include "board_capabilities.h" +#include "buffers.h" #include "common.h" #include "compiler_traits.h" #include "display.h" +#include "display_draw.h" #include "fault_handlers.h" #include "flash.h" #include "image.h" diff --git a/core/embed/bootloader/bootui.c b/core/embed/bootloader/bootui.c index ae5937073..0033a5e1e 100644 --- a/core/embed/bootloader/bootui.c +++ b/core/embed/bootloader/bootui.c @@ -23,6 +23,7 @@ #include "bootui.h" #include "display.h" +#include "display_draw.h" #include "display_utils.h" #ifdef TREZOR_EMULATOR #include "emulator.h" diff --git a/core/embed/bootloader/emulator.c b/core/embed/bootloader/emulator.c index 1ad001af1..221741212 100644 --- a/core/embed/bootloader/emulator.c +++ b/core/embed/bootloader/emulator.c @@ -187,10 +187,6 @@ __attribute__((noreturn)) int main(int argc, char **argv) { jump_to(NULL); } -void display_set_little_endian(void) {} - -void display_reinit(void) {} - void mpu_config_bootloader(void) {} void mpu_config_off(void) {} diff --git a/core/embed/bootloader/memory_stm32u58.ld b/core/embed/bootloader/memory_stm32u58.ld index 059b4ca2d..0812d1925 100644 --- a/core/embed/bootloader/memory_stm32u58.ld +++ b/core/embed/bootloader/memory_stm32u58.ld @@ -78,6 +78,8 @@ SECTIONS { .buf : ALIGN(4) { *(.buf*); . = ALIGN(4); + *(.no_dma_buffers*); + . = ALIGN(4); } >SRAM1 .stack : ALIGN(8) { diff --git a/core/embed/bootloader/memory_stm32u5a.ld b/core/embed/bootloader/memory_stm32u5a.ld index 9803e696d..c45ed1273 100644 --- a/core/embed/bootloader/memory_stm32u5a.ld +++ b/core/embed/bootloader/memory_stm32u5a.ld @@ -78,6 +78,8 @@ SECTIONS { .buf : ALIGN(4) { *(.buf*); . = ALIGN(4); + *(.no_dma_buffers*); + . = ALIGN(4); } >SRAM1 .stack : ALIGN(8) { diff --git a/core/embed/bootloader_ci/bootui.c b/core/embed/bootloader_ci/bootui.c index e98326536..7b6a4e288 100644 --- a/core/embed/bootloader_ci/bootui.c +++ b/core/embed/bootloader_ci/bootui.c @@ -21,6 +21,7 @@ #include "bootui.h" #include "display.h" +#include "display_draw.h" #include "display_utils.h" #include "icon_done.h" #include "icon_fail.h" diff --git a/core/embed/bootloader_ci/main.c b/core/embed/bootloader_ci/main.c index 40c56d58a..0e44e0b47 100644 --- a/core/embed/bootloader_ci/main.c +++ b/core/embed/bootloader_ci/main.c @@ -22,6 +22,7 @@ #include "common.h" #include "display.h" +#include "display_draw.h" #include "flash.h" #include "flash_otp.h" #include "image.h" diff --git a/core/embed/bootloader_ci/memory_stm32f4.ld b/core/embed/bootloader_ci/memory_stm32f4.ld index d66d8bfc3..42eb1cd20 100644 --- a/core/embed/bootloader_ci/memory_stm32f4.ld +++ b/core/embed/bootloader_ci/memory_stm32f4.ld @@ -73,4 +73,10 @@ SECTIONS { *(.boot_args*); . = ALIGN(8); } >BOOT_ARGS + + .data_ccm : ALIGN(4) { + *(.no_dma_buffers*); + . = ALIGN(4); + } >CCMRAM + } diff --git a/core/embed/extmod/modtrezorui/modtrezorui-display.h b/core/embed/extmod/modtrezorui/modtrezorui-display.h index 85662585a..2e5de1150 100644 --- a/core/embed/extmod/modtrezorui/modtrezorui-display.h +++ b/core/embed/extmod/modtrezorui/modtrezorui-display.h @@ -18,6 +18,8 @@ */ #include "display.h" +#include "display_draw.h" +#include "fonts/fonts.h" /// class Display: /// """ @@ -128,11 +130,13 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_backlight_obj, /// Saves current display contents to PNG file with given prefix. /// """ STATIC mp_obj_t mod_trezorui_Display_save(mp_obj_t self, mp_obj_t prefix) { +#ifdef TREZOR_EMULATOR mp_buffer_info_t pfx = {0}; mp_get_buffer_raise(prefix, &pfx, MP_BUFFER_READ); if (pfx.len > 0) { display_save(pfx.buf); } +#endif return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorui_Display_save_obj, @@ -143,7 +147,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorui_Display_save_obj, /// Clears buffers in display saving. /// """ STATIC mp_obj_t mod_trezorui_Display_clear_save(mp_obj_t self) { +#ifdef TREZOR_EMULATOR display_clear_save(); +#endif return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorui_Display_clear_save_obj, diff --git a/core/embed/firmware/memory_DISC2.ld b/core/embed/firmware/memory_DISC2.ld index ec4a5ae26..a79d3e078 100644 --- a/core/embed/firmware/memory_DISC2.ld +++ b/core/embed/firmware/memory_DISC2.ld @@ -89,6 +89,8 @@ SECTIONS { .data_ccm : ALIGN(4) { *(.no_dma_buffers*); . = ALIGN(4); + *(.buf*); + . = ALIGN(4); } >SRAM1 .heap : ALIGN(4) { diff --git a/core/embed/firmware/memory_T.ld b/core/embed/firmware/memory_T.ld index 8f65dd6c1..9affc00a9 100644 --- a/core/embed/firmware/memory_T.ld +++ b/core/embed/firmware/memory_T.ld @@ -87,6 +87,11 @@ SECTIONS { . = ALIGN(4); } >SRAM + .buf : ALIGN(4) { + *(.buf*); + . = ALIGN(4); + } >SRAM + .heap : ALIGN(4) { . = 37K; /* this acts as a build time assertion that at least this much memory is available for heap use */ . = ABSOLUTE(sram_end); /* this explicitly sets the end of the heap */ diff --git a/core/embed/lib/display.c b/core/embed/lib/display_draw.c similarity index 98% rename from core/embed/lib/display.c rename to core/embed/lib/display_draw.c index 5c99bfaff..138155ebb 100644 --- a/core/embed/lib/display.c +++ b/core/embed/lib/display_draw.c @@ -19,7 +19,7 @@ #define _GNU_SOURCE -#include "display.h" +#include "display_draw.h" #include "buffers.h" #include "common.h" @@ -34,7 +34,7 @@ #include "memzero.h" -#include "display_interface.h" +#include "display.h" static struct { int x, y; } DISPLAY_OFFSET; @@ -61,8 +61,8 @@ void display_clear(void) { // set MADCTL first so that we can set the window correctly next display_orientation(0); // address the complete frame memory - display_set_window(0, 0, MAX_DISPLAY_RESX - 1, MAX_DISPLAY_RESY - 1); - for (uint32_t i = 0; i < MAX_DISPLAY_RESX * MAX_DISPLAY_RESY; i++) { + display_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1); + for (uint32_t i = 0; i < DISPLAY_RESX * DISPLAY_RESY; i++) { // 2 bytes per pixel because we're using RGB 5-6-5 format PIXELDATA(0x0000); } diff --git a/core/embed/lib/display.h b/core/embed/lib/display_draw.h similarity index 95% rename from core/embed/lib/display.h rename to core/embed/lib/display_draw.h index f76c8b4cc..dcd046681 100644 --- a/core/embed/lib/display.h +++ b/core/embed/lib/display_draw.h @@ -17,8 +17,8 @@ * along with this program. If not, see . */ -#ifndef __DISPLAY_H__ -#define __DISPLAY_H__ +#ifndef __DISPLAY_DRAW_H__ +#define __DISPLAY_DRAW_H__ #include #include @@ -27,7 +27,6 @@ #include "buffers.h" #include "colors.h" #include TREZOR_BOARD -#include "display_interface.h" #include "fonts/fonts.h" // provided by common diff --git a/core/embed/lib/dma2d_emul.c b/core/embed/lib/dma2d_emul.c index a9ccd9d10..99fb880be 100644 --- a/core/embed/lib/dma2d_emul.c +++ b/core/embed/lib/dma2d_emul.c @@ -18,7 +18,7 @@ */ #include "colors.h" -#include "display_interface.h" +#include "display.h" typedef enum { DMA2D_LAYER_FG = 1, diff --git a/core/embed/lib/gl_bitblt.h b/core/embed/lib/gl_bitblt.h new file mode 100644 index 000000000..9594f4bb5 --- /dev/null +++ b/core/embed/lib/gl_bitblt.h @@ -0,0 +1,120 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GL_BITBLT_H +#define GL_BITBLT_H + +#include +#include + +#include "gl_color.h" + +// These module provides low-level bit block transfer (bitblt) +// operations on different bitmap/framebuffer types. +// +// `fill` - fills a rectangle with a solid color (with an optional +// alpha, allowing color blending). +// +// `copy` - copies a bitmap or part of it to the destination bitmap. +// +// `blend` - blends a bitmap with a 1- or 4-bit alpha channel to the +// destination using background and foreground colors. +// +// These operations might be accelerated using DMA2D (ChromART accelerator) +// on the STM32 platform. + +// Represents a set of parameters for a bit block transfer operation. +typedef struct { + // Pointer to the destination bitmap's first row + void* dst_row; + // Number of bytes per line in the destination bitmap + uint16_t dst_stride; + // X-coordinate of the top-left corner inside the destination + uint16_t dst_x; + // Y-coordinate of the top-left corner inside the destination + uint16_t dst_y; + // Height of the filled/copied/blended area + uint16_t height; + // Width of the filled/copied/blended area + uint16_t width; + + // Pointer to the source bitmap's first row + // (unused for fill operations) + void* src_row; + // Number of bytes per line in the source bitmap + // (unused for fill operations) + uint16_t src_stride; + // X-coordinate of the origin in the source bitmap + // (unused for fill operations) + uint16_t src_x; + // Y-coordinate of the origin in the source bitmap + // (unused for fill operations) + uint16_t src_y; + + // Foreground color used when copying/blending/filling + gl_color_t src_fg; + // Background color used when copying mono bitmaps + gl_color_t src_bg; + // Alpha value for fill operation (255 => normal fill, 0 => noop) + uint8_t src_alpha; + +} gl_bitblt_t; + +// Functions for RGB565 bitmap/framebuffer + +// Fills a rectangle with a solid color +void gl_rgb565_fill(const gl_bitblt_t* bb); +// Copies a mono bitmap (with 1-bit alpha channel) +void gl_rgb565_copy_mono1p(const gl_bitblt_t* bb); +// Copies a mono bitmap (with 4-bit alpha channel) +void gl_rgb565_copy_mono4(const gl_bitblt_t* bb); +// Copies an RGB565 bitmap +void gl_rgb565_copy_rgb565(const gl_bitblt_t* bb); +// Blends a mono bitmap (with 4-bit alpha channel) +// with the destination bitmap +void gl_rgb565_blend_mono4(const gl_bitblt_t* bb); + +// Functions for RGBA8888 bitmap/framebuffer +void gl_rgba8888_fill(const gl_bitblt_t* bb); +// Copies a mono bitmap (with 1-bit alpha channel) +void gl_rgba8888_copy_mono1p(const gl_bitblt_t* bb); +// Copies a mono bitmap (with 4-bit alpha channel) +void gl_rgba8888_copy_mono4(const gl_bitblt_t* bb); +// Copies an RGB565 bitmap +void gl_rgba8888_copy_rgb565(const gl_bitblt_t* bb); +// Copies an RGBA8888 bitmap +void gl_rgba8888_copy_rgba8888(const gl_bitblt_t* bb); +// Blends a mono bitmap (with 4-bit alpha channel) +// with the destination bitmap +void gl_rgba8888_blend_mono4(const gl_bitblt_t* bb); + +// Functions for Mono8 bitmap/framebuffer +void gl_mono8_fill(const gl_bitblt_t* bb); +// Copies a mono bitmap (with 1-bit alpha channel) +void gl_mono8_copy_mono1p(const gl_bitblt_t* bb); +// Copies a mono bitmap (with 4-bit alpha channel) +void gl_mono8_copy_mono4(const gl_bitblt_t* bb); +// Blends a mono bitmap (with 1-bit alpha channel) +// with the destination bitmap +void gl_mono8_blend_mono1p(const gl_bitblt_t* bb); +// Blends a mono bitmap (with 4-bit alpha channel) +// with the destination bitmap +void gl_mono8_blend_mono4(const gl_bitblt_t* bb); + +#endif // GL_BITBLT_H diff --git a/core/embed/lib/gl_bitblt_mono8.c b/core/embed/lib/gl_bitblt_mono8.c new file mode 100644 index 000000000..502944de3 --- /dev/null +++ b/core/embed/lib/gl_bitblt_mono8.c @@ -0,0 +1,110 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gl_bitblt.h" + +void gl_mono8_fill(const gl_bitblt_t* bb) { + uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x; + uint16_t height = bb->height; + + uint8_t fg = gl_color_lum(bb->src_fg); + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + dst_ptr[x] = fg; + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + } +} + +void gl_mono8_copy_mono1p(const gl_bitblt_t* bb) { + uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x; + uint8_t* src = (uint8_t*)bb->src_row; + uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x; + uint16_t height = bb->height; + + uint8_t fg = gl_color_lum(bb->src_fg); + uint8_t bg = gl_color_lum(bb->src_bg); + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + uint8_t mask = 1 << (7 - ((src_ofs + x) & 7)); + uint8_t data = src[(src_ofs + x) / 8]; + dst_ptr[x] = (data & mask) ? fg : bg; + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ofs += bb->src_stride; + } +} + +void gl_mono8_copy_mono4(const gl_bitblt_t* bb) { + uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x; + uint8_t* src_row = (uint8_t*)bb->src_row; + uint16_t height = bb->height; + + uint8_t fg = gl_color_lum(bb->src_fg); + uint8_t bg = gl_color_lum(bb->src_bg); + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + uint8_t src_data = src_row[(x + bb->src_x) / 2]; + uint8_t src_lum = (x + bb->src_x) & 1 ? src_data >> 4 : src_data & 0xF; + dst_ptr[x] = (fg * src_lum + bg * (15 - src_lum)) / 15; + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_row += bb->src_stride / sizeof(*src_row); + } +} + +void gl_mono8_blend_mono1p(const gl_bitblt_t* bb) { + uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x; + uint8_t* src = (uint8_t*)bb->src_row; + uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x; + uint16_t height = bb->height; + + uint8_t fg = gl_color_lum(bb->src_fg); + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + uint8_t mask = 1 << (7 - ((src_ofs + x) & 7)); + uint8_t data = src[(src_ofs + x) / 8]; + dst_ptr[x] = (data & mask) ? fg : dst_ptr[x]; + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ofs += bb->src_stride; + } +} + +void gl_mono8_blend_mono4(const gl_bitblt_t* bb) { + uint8_t* dst_ptr = (uint8_t*)bb->dst_row + bb->dst_x; + uint8_t* src_row = (uint8_t*)bb->src_row; + uint16_t height = bb->height; + + uint8_t fg = gl_color_lum(bb->src_fg); + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + uint8_t src_data = src_row[(x + bb->src_x) / 2]; + uint8_t src_alpha = (x + bb->src_x) & 1 ? src_data >> 4 : src_data & 0x0F; + dst_ptr[x] = (fg * src_alpha + dst_ptr[x] * (15 - src_alpha)) / 15; + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_row += bb->src_stride / sizeof(*src_row); + } +} diff --git a/core/embed/lib/gl_bitblt_rgb565.c b/core/embed/lib/gl_bitblt_rgb565.c new file mode 100644 index 000000000..15c929c28 --- /dev/null +++ b/core/embed/lib/gl_bitblt_rgb565.c @@ -0,0 +1,136 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gl_bitblt.h" + +#if USE_DMA2D +#include "dma2d_bitblt.h" +#endif + +void gl_rgb565_fill(const gl_bitblt_t* bb) { +#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR) + if (!dma2d_rgb565_fill(bb)) +#endif + { + uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x; + uint16_t height = bb->height; + + if (bb->src_alpha == 255) { + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + dst_ptr[x] = bb->src_fg; + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + } + } else { + uint8_t alpha = bb->src_alpha; + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + dst_ptr[x] = gl_color16_blend_a8(bb->src_fg, dst_ptr[x], alpha); + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + } + } + } +} + +void gl_rgb565_copy_mono1p(const gl_bitblt_t* bb) { + uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x; + uint8_t* src = (uint8_t*)bb->src_row; + uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x; + uint16_t height = bb->height; + + uint16_t fg = gl_color_to_color16(bb->src_fg); + uint16_t bg = gl_color_to_color16(bb->src_bg); + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + uint8_t mask = 1 << (7 - ((src_ofs + x) & 7)); + uint8_t data = src[(src_ofs + x) / 8]; + dst_ptr[x] = (data & mask) ? fg : bg; + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ofs += bb->src_stride; + } +} + +void gl_rgb565_copy_mono4(const gl_bitblt_t* bb) { +#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR) + if (!dma2d_rgb565_copy_mono4(bb)) +#endif + { + const gl_color16_t* gradient = + gl_color16_gradient_a4(bb->src_fg, bb->src_bg); + + uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x; + uint8_t* src_row = (uint8_t*)bb->src_row; + uint16_t height = bb->height; + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + uint8_t fg_data = src_row[(x + bb->src_x) / 2]; + uint8_t fg_lum = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0xF; + dst_ptr[x] = gradient[fg_lum]; + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_row += bb->src_stride / sizeof(*src_row); + } + } +} + +void gl_rgb565_copy_rgb565(const gl_bitblt_t* bb) { +#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR) + if (!dma2d_rgb565_copy_rgb565(bb)) +#endif + { + uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x; + uint16_t* src_ptr = (uint16_t*)bb->src_row + bb->src_x; + uint16_t height = bb->height; + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + dst_ptr[x] = src_ptr[x]; + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ptr += bb->src_stride / sizeof(*src_ptr); + } + } +} + +void gl_rgb565_blend_mono4(const gl_bitblt_t* bb) { +#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR) + if (!dma2d_rgb565_blend_mono4(bb)) +#endif + { + uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x; + uint8_t* src_row = (uint8_t*)bb->src_row; + uint16_t height = bb->height; + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + uint8_t fg_data = src_row[(x + bb->src_x) / 2]; + uint8_t fg_alpha = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0x0F; + dst_ptr[x] = gl_color16_blend_a4( + bb->src_fg, gl_color16_to_color(dst_ptr[x]), fg_alpha); + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_row += bb->src_stride / sizeof(*src_row); + } + } +} diff --git a/core/embed/lib/gl_bitblt_rgba8888.c b/core/embed/lib/gl_bitblt_rgba8888.c new file mode 100644 index 000000000..6c709d120 --- /dev/null +++ b/core/embed/lib/gl_bitblt_rgba8888.c @@ -0,0 +1,156 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gl_bitblt.h" + +#if USE_DMA2D +#include "dma2d_bitblt.h" +#endif + +void gl_rgba8888_fill(const gl_bitblt_t* bb) { +#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR) + if (!dma2d_rgba8888_fill(bb)) +#endif + { + uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x; + uint16_t height = bb->height; + + if (bb->src_alpha == 255) { + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + dst_ptr[x] = gl_color_to_color32(bb->src_fg); + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + } + } else { + uint8_t alpha = bb->src_alpha; + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + dst_ptr[x] = gl_color32_blend_a8( + bb->src_fg, gl_color32_to_color(dst_ptr[x]), alpha); + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + } + } + } +} + +void gl_rgba8888_copy_mono1p(const gl_bitblt_t* bb) { + uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x; + uint8_t* src = (uint8_t*)bb->src_row; + uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x; + uint16_t height = bb->height; + + uint32_t fg = gl_color_to_color32(bb->src_fg); + uint32_t bg = gl_color_to_color32(bb->src_bg); + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + uint8_t mask = 1 << (7 - ((src_ofs + x) & 7)); + uint8_t data = src[(src_ofs + x) / 8]; + dst_ptr[x] = (data & mask) ? fg : bg; + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ofs += bb->src_stride; + } +} + +void gl_rgba8888_copy_mono4(const gl_bitblt_t* bb) { +#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR) + if (!dma2d_rgba8888_copy_mono4(bb)) +#endif + { + const gl_color32_t* gradient = + gl_color32_gradient_a4(bb->src_fg, bb->src_bg); + + uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x; + uint8_t* src_row = (uint8_t*)bb->src_row; + uint16_t height = bb->height; + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + uint8_t fg_data = src_row[(x + bb->src_x) / 2]; + uint8_t fg_lum = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0xF; + dst_ptr[x] = gradient[fg_lum]; + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_row += bb->src_stride / sizeof(*src_row); + } + } +} + +void gl_rgba8888_copy_rgb565(const gl_bitblt_t* bb) { +#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR) + if (!dma2d_rgba8888_copy_rgb565(bb)) +#endif + { + uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x; + uint16_t* src_ptr = (uint16_t*)bb->src_row + bb->src_x; + uint16_t height = bb->height; + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + dst_ptr[x] = gl_color16_to_color32(src_ptr[x]); + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ptr += bb->src_stride / sizeof(*src_ptr); + } + } +} + +void gl_rgba8888_copy_rgba8888(const gl_bitblt_t* bb) { +#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR) + if (!dma2d_rgba8888_copy_rgba8888(bb)) +#endif + { + uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x; + uint32_t* src_ptr = (uint32_t*)bb->src_row + bb->src_x; + uint16_t height = bb->height; + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + dst_ptr[x] = src_ptr[x]; + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ptr += bb->src_stride / sizeof(*src_ptr); + } + } +} + +void gl_rgba8888_blend_mono4(const gl_bitblt_t* bb) { +#if defined(USE_DMA2D) && !defined(TREZOR_EMULATOR) + if (!dma2d_rgba8888_blend_mono4(bb)) +#endif + { + uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x; + uint8_t* src_row = (uint8_t*)bb->src_row; + uint16_t height = bb->height; + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + uint8_t fg_data = src_row[(x + bb->src_x) / 2]; + uint8_t fg_alpha = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0x0F; + dst_ptr[x] = gl_color32_blend_a4( + bb->src_fg, gl_color32_to_color(dst_ptr[x]), fg_alpha); + } + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_row += bb->src_stride / sizeof(*src_row); + } + } +} diff --git a/core/embed/lib/gl_color.c b/core/embed/lib/gl_color.c new file mode 100644 index 000000000..31841418e --- /dev/null +++ b/core/embed/lib/gl_color.c @@ -0,0 +1,49 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "gl_color.h" +#include "colors.h" + +const gl_color16_t* gl_color16_gradient_a4(gl_color_t fg_color, + gl_color_t bg_color) { + static gl_color16_t cache[16] = {0}; + + if (gl_color_to_color16(bg_color) != cache[0] || + gl_color_to_color16(fg_color) != cache[15]) { + for (int alpha = 0; alpha < 16; alpha++) { + cache[alpha] = gl_color16_blend_a4(fg_color, bg_color, alpha); + } + } + + return cache; +} + +const gl_color32_t* gl_color32_gradient_a4(gl_color_t fg_color, + gl_color_t bg_color) { + static gl_color32_t cache[16] = {0}; + + if (bg_color != gl_color32_to_color(cache[0]) || + fg_color != gl_color32_to_color(cache[15])) { + for (int alpha = 0; alpha < 16; alpha++) { + cache[alpha] = gl_color32_blend_a4(fg_color, bg_color, alpha); + } + } + + return cache; +} diff --git a/core/embed/lib/gl_color.h b/core/embed/lib/gl_color.h new file mode 100644 index 000000000..bd298741e --- /dev/null +++ b/core/embed/lib/gl_color.h @@ -0,0 +1,337 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GL_COLOR_H +#define GL_COLOR_H + +#include + +#define GL_COLOR_16BIT +// #define GL_COLOR_32BIT + +// Color in RGB565 format +// +// |15 8 | 7 0| +// |---------------------------------| +// |r r r r r g g g | g g g b b b b b| +// |---------------------------------| + +typedef uint16_t gl_color16_t; + +// Color in RGBA8888 format +// +// |31 24 |23 16 |15 8 | 7 0 | +// |----------------------------------------------------------------------| +// |a a a a a a a a | r r r r r r r r | g g g g g g g g | b b b b b b b b | +// |----------------------------------------------------------------------| +// + +typedef uint32_t gl_color32_t; + +#ifdef GL_COLOR_16BIT +#define gl_color_t gl_color16_t +#define gl_color_to_color16(c) (c) +#define gl_color16_to_color(c) (c) +#define gl_color_to_color32(c) (gl_color16_to_color32(c)) +#define gl_color32_to_color(c) (gl_color32_to_color16(c)) +#define gl_color_lum(c) (gl_color16_lum(c)) +#elif GL_COLOR_32BIT +#define gl_color_t gl_color32_t +#define gl_color_to_color16(c) (gl_color32_to_color16(c)) +#define gl_color16_to_color(c) (gl_color16_to_color32(c)) +#define gl_color_to_color32(c) (c) +#define gl_color32_to_color(c) (c) +#define gl_color_lum(c) (gl_color32_lum(c)) +#else +#error "GL_COLOR_16BIT/32BIT not specified" +#endif + +// Extracts red component from gl_color16_t and converts it to 8-bit value +#define gl_color16_to_r(c) ((((c)&0xF800) >> 8) | (((c)&0xF800) >> 13)) +// Extracts green component from gl_color16_t and converts it to 8-bit value +#define gl_color16_to_g(c) ((((c)&0x07E0) >> 3) | (((c)&0x07E0) >> 9)) +// Extracts blue component from gl_color16_t and converts it to 8-bit value +#define gl_color16_to_b(c) ((((c)&0x001F) << 3) | (((c)&0x001F) >> 2)) + +// Extracts red component from gl_color32_t +#define gl_color32_to_r(c) (((c)&0x00FF0000) >> 16) +// Extracts green component from gl_color32_t +#define gl_color32_to_g(c) (((c)&0x0000FF00) >> 8) +// Extracts blue component from gl_color32_t +#define gl_color32_to_b(c) (((c)&0x000000FF) >> 0) + +// 4-bit linear interpolation between `fg` and `bg` +#define a4_lerp(fg, bg, alpha) (((fg) * (alpha) + ((bg) * (15 - (alpha)))) / 15) +// 8-bit linear interpolation between `fg` and `bg` +#define a8_lerp(fg, bg, alpha) \ + (((fg) * (alpha) + ((bg) * (255 - (alpha)))) / 255) + +// Constructs a 16-bit color from the given red (r), +// green (g), and blue (b) values in the range 0..255 +static inline gl_color16_t gl_color16_rgb(uint8_t r, uint8_t g, uint8_t b) { + return ((r & 0xF8U) << 8) | ((g & 0xFCU) << 3) | ((b & 0xF8U) >> 3); +} + +// Constructs a 32-bit color from the given red (r), +// green (g), and blue (b) values in the range 0..255. +// Alpha is set to 255. +static inline gl_color32_t gl_color32_rgb(uint8_t r, uint8_t g, uint8_t b) { + return (0xFFU << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; +} + +// Converts a 16-bit color to a 32-bit color; alpha is set to 255 +static inline gl_color32_t gl_color16_to_color32(gl_color16_t color) { + uint32_t r = gl_color16_to_r(color); + uint32_t g = gl_color16_to_g(color); + uint32_t b = gl_color16_to_b(color); + + return gl_color32_rgb(r, g, b); +} + +// Converts 32-bit color to 16-bit color, alpha is ignored +static inline gl_color16_t gl_color32_to_color16(gl_color32_t color) { + uint16_t r = (color & 0x00F80000) >> 8; + uint16_t g = (color & 0x0000FC00) >> 5; + uint16_t b = (color & 0x000000F8) >> 3; + + return r | g | b; +} + +// Converts 16-bit color into luminance (ranging from 0 to 255) +static inline uint8_t gl_color16_lum(gl_color16_t color) { + uint32_t r = gl_color16_to_r(color); + uint32_t g = gl_color16_to_g(color); + uint32_t b = gl_color16_to_b(color); + + return (r + g + b) / 3; +} + +// Converts 32-bit color into luminance (ranging from 0 to 255) +static inline uint8_t gl_color32_lum(gl_color16_t color) { + uint32_t r = gl_color32_to_r(color); + uint32_t g = gl_color32_to_g(color); + uint32_t b = gl_color32_to_b(color); + + return (r + g + b) / 3; +} + +#ifdef GL_COLOR_16BIT +// Blends foreground and background colors with 4-bit alpha +// +// Returns a color in 16-bit format +// +// If `alpha` is 0, the function returns the background color +// If `alpha` is 15, the function returns the foreground color +static inline gl_color16_t gl_color16_blend_a4(gl_color16_t fg, gl_color16_t bg, + uint8_t alpha) { + uint16_t fg_r = (fg & 0xF800) >> 11; + uint16_t bg_r = (bg & 0xF800) >> 11; + uint16_t r = a4_lerp(fg_r, bg_r, alpha); + + uint16_t fg_g = (fg & 0x07E0) >> 5; + uint16_t bg_g = (bg & 0x07E0) >> 5; + uint16_t g = a4_lerp(fg_g, bg_g, alpha); + + uint16_t fg_b = (fg & 0x001F) >> 0; + uint16_t bg_b = (bg & 0x001F) >> 0; + uint16_t b = a4_lerp(fg_b, bg_b, alpha); + + return (r << 11) | (g << 5) | b; +} + +// Blends foreground and background colors with 8-bit alpha +// +// Returns a color in 16-bit format +// +// If `alpha` is 0, the function returns the background color +// If `alpha` is 15, the function returns the foreground color +static inline gl_color16_t gl_color16_blend_a8(gl_color16_t fg, gl_color16_t bg, + uint8_t alpha) { + uint16_t fg_r = (fg & 0xF800) >> 11; + uint16_t bg_r = (bg & 0xF800) >> 11; + uint16_t r = a8_lerp(fg_r, bg_r, alpha); + + uint16_t fg_g = (fg & 0x07E0) >> 5; + uint16_t bg_g = (bg & 0x07E0) >> 5; + uint16_t g = a8_lerp(fg_g, bg_g, alpha); + + uint16_t fg_b = (fg & 0x001F) >> 0; + uint16_t bg_b = (bg & 0x001F) >> 0; + uint16_t b = a8_lerp(fg_b, bg_b, alpha); + + return (r << 11) | (g << 5) | b; +} + +// Blends foreground and background colors with 4-bit alpha +// +// Returns a color in 32-bit format +// +// If alpha is 0, the function returns the background color +// If alpha is 15, the function returns the foreground color +static inline gl_color32_t gl_color32_blend_a4(gl_color16_t fg, gl_color16_t bg, + uint8_t alpha) { + uint16_t fg_r = gl_color16_to_r(fg); + uint16_t bg_r = gl_color16_to_r(bg); + uint16_t r = a4_lerp(fg_r, bg_r, alpha); + + uint16_t fg_g = gl_color16_to_g(fg); + uint16_t bg_g = gl_color16_to_g(bg); + uint16_t g = a4_lerp(fg_g, bg_g, alpha); + + uint16_t fg_b = gl_color16_to_b(fg); + uint16_t bg_b = gl_color16_to_b(bg); + uint16_t b = a4_lerp(fg_b, bg_b, alpha); + + return gl_color32_rgb(r, g, b); +} + +// Blends foreground and background colors with 8-bit alpha +// +// Returns a color in 32-bit format +// +// If `alpha` is 0, the function returns the background color +// If `alpha` is 255, the function returns the foreground color +static inline gl_color32_t gl_color32_blend_a8(gl_color16_t fg, gl_color16_t bg, + uint8_t alpha) { + uint16_t fg_r = gl_color16_to_r(fg); + uint16_t bg_r = gl_color16_to_r(bg); + uint16_t r = a8_lerp(fg_r, bg_r, alpha); + + uint16_t fg_g = gl_color16_to_g(fg); + uint16_t bg_g = gl_color16_to_g(bg); + uint16_t g = a8_lerp(fg_g, bg_g, alpha); + + uint16_t fg_b = gl_color16_to_b(fg); + uint16_t bg_b = gl_color16_to_b(bg); + uint16_t b = a8_lerp(fg_b, bg_b, alpha); + + return gl_color32_rgb(r, g, b); +} + +#elif GL_COLOR_32BIT + +// Blends foreground and background colors with 4-bit alpha +// +// Returns a color in 16-bit format +// +// If `alpha` is 0, the function returns the background color +// If `alpha` is 15, the function returns the foreground color +static inline gl_color16_t gl_color16_blend_a4(gl_color32_t fg, gl_color32_t bg, + uint8_t alpha) { + uint16_t fg_r = gl_color32_to_r(fg); + uint16_t bg_r = gl_color32_to_r(bg); + uint16_t r = a4_lerp(fg_r, bg_r, alpha); + + uint16_t fg_g = gl_color32_to_g(fg); + uint16_t bg_g = gl_color32_to_g(bg); + uint16_t g = a4_lerp(fg_g, bg_g, alpha); + + uint16_t fg_b = gl_color32_to_b(fg); + uint16_t bg_b = gl_color32_to_b(bg); + uint16_t b = a4_lerp(fg_b, bg_b, alpha); + + return gl_color16_rgb(r, g, b); +} + +// Blends foreground and background colors with 8-bit alpha +// +// Returns a color in 16-bit format +// +// If `alpha` is 0, the function returns the background color +// If `alpha` is 255, the function returns the foreground color +static inline gl_color16_t gl_color16_blend_a8(gl_color32_t fg, gl_color32_t bg, + uint8_t alpha) { + uint16_t fg_r = gl_color32_to_r(fg); + uint16_t bg_r = gl_color32_to_r(bg); + uint16_t r = a8_lerp(fg_r, bg_r, alpha); + + uint16_t fg_g = gl_color32_to_g(fg); + uint16_t bg_g = gl_color32_to_g(bg); + uint16_t g = a8_lerp(fg_g, bg_g, alpha); + + uint16_t fg_b = gl_color32_to_b(fg); + uint16_t bg_b = gl_color32_to_b(bg); + uint16_t b = g = a8_lerp(fg_b, bg_b, alpha); + + return gl_color16_rgb(r, g, b); +} + +// Blends foreground and background colors with 4-bit alpha +// +// Returns a color in 32-bit format +// +// If `alpha` is 0, the function returns the background color +// If `alpha` is 15, the function returns the foreground color +static inline gl_color32_t gl_color32_blend_a4(gl_color32_t fg, gl_color32_t bg, + uint8_t alpha) { + uint16_t fg_r = gl_color32_to_r(fg); + uint16_t bg_r = gl_color32_to_r(bg); + uint16_t r = a4_lerp(fg_r, bg_r, alpha); + + uint16_t fg_g = gl_color32_to_g(fg); + uint16_t bg_g = gl_color32_to_g(bg); + uint16_t g = a4_lerp(fg_g, bg_g, alpha); + + uint16_t fg_b = gl_color32_to_b(fg); + uint16_t bg_b = gl_color32_to_b(bg); + uint16_t b = a4_lerp(fg_b, bg_b, alpha); + + return gl_color32_rgb(r, g, b); +} + +// Blends foreground and background colors with 8-bit alpha +// +// Returns a color in 32-bit format +// +// If `alpha` is 0, the function returns the background color +// If `alpha` is 15, the function returns the foreground color +static inline gl_color32_t gl_color32_blend_a8(gl_color32_t fg, gl_color32_t bg, + uint8_t alpha) { + uint16_t fg_r = gl_color32_to_r(fg); + uint16_t bg_r = gl_color32_to_r(bg); + uint16_t r = a8_lerp(fg_r, bg_r, alpha); + + uint16_t fg_g = gl_color32_to_g(fg); + uint16_t bg_g = gl_color32_to_g(bg); + uint16_t g = a8_lerp(fg_g, bg_g, alpha); + + uint16_t fg_b = gl_color32_to_b(fg); + uint16_t bg_b = gl_color32_to_b(bg); + uint16_t b = a8_lerp(fg_b, bg_b, alpha); + + return gl_color32_rgb(r, g, b); +} + +#else +#error "GL_COLOR_16BIT/32BIT not specified" +#endif + +// Returns a gradient as an array of 16 consecutive 16-bit colors +// +// Each element in the array represents a color, with `retval[0]` being +// the background (`bg`) color and `retval[15]` the foreground (`fg`) color +const gl_color16_t* gl_color16_gradient_a4(gl_color_t fg, gl_color_t bg); + +// Returns a gradient as an array of 16 consecutive 32-bit colors +// +// Each element in the array represents a color, with `retval[0]` being +// the background (`bg`) color and `retval[15]` the foreground (`fg`) color +const gl_color32_t* gl_color32_gradient_a4(gl_color_t fg, gl_color_t bg); + +#endif // TREZORHAL_GL_COLOR_H diff --git a/core/embed/lib/gl_draw.c b/core/embed/lib/gl_draw.c new file mode 100644 index 000000000..64f0c46c5 --- /dev/null +++ b/core/embed/lib/gl_draw.c @@ -0,0 +1,275 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "display_draw.h" +#include "fonts/fonts.h" +#include "gl_draw.h" + +typedef struct { + int16_t dst_x; + int16_t dst_y; + int16_t src_x; + int16_t src_y; + int16_t width; + int16_t height; +} gl_clip_t; + +static inline gl_clip_t gl_clip(gl_rect_t dst, const gl_bitmap_t* bitmap) { + int16_t dst_x = dst.x0; + int16_t dst_y = dst.y0; + + int16_t src_x = 0; + int16_t src_y = 0; + + if (bitmap != NULL) { + src_x += bitmap->offset.x; + src_y += bitmap->offset.y; + + // Normalize negative x-offset of bitmap + if (src_x < 0) { + dst_x -= src_x; + src_x = 0; + } + + // Normalize negative y-offset of src bitmap + if (src_y < 0) { + dst_y -= src_y; + src_y = 0; + } + } + + // Normalize negative top-left of destination rectangle + if (dst_x < 0) { + src_x -= dst_x; + dst_x = 0; + } + + if (dst_y < 0) { + src_y -= dst_y; + dst_y = 0; + } + + // Calculate dimension of effective rectangle + int16_t width = MIN(DISPLAY_RESX, dst.x1) - dst_x; + int16_t height = MIN(DISPLAY_RESY, dst.y1) - dst_y; + + if (bitmap != NULL) { + width = MIN(width, bitmap->size.x - src_x); + height = MIN(height, bitmap->size.y - src_y); + } + + gl_clip_t clip = { + .dst_x = dst_x, + .dst_y = dst_y, + .src_x = src_x, + .src_y = src_y, + .width = width, + .height = height, + }; + + return clip; +} + +void gl_clear(void) { + gl_bitblt_t bb = { + // Destination bitmap + .height = DISPLAY_RESX, + .width = DISPLAY_RESY, + .dst_row = NULL, + .dst_x = 0, + .dst_y = 0, + .dst_stride = 0, + + // Source bitmap + .src_fg = 0, + .src_alpha = 255, + }; + + display_fill(&bb); +} + +void gl_draw_bar(gl_rect_t rect, gl_color_t color) { + gl_clip_t clip = gl_clip(rect, NULL); + + if (clip.width <= 0 || clip.height <= 0) { + return; + } + + gl_bitblt_t bb = { + // Destination bitmap + .height = clip.height, + .width = clip.width, + .dst_row = NULL, + .dst_x = clip.dst_x, + .dst_y = clip.dst_y, + .dst_stride = 0, + + // Source bitmap + .src_fg = color, + .src_alpha = 255, + }; + + display_fill(&bb); +} + +void gl_draw_bitmap(gl_rect_t rect, const gl_bitmap_t* bitmap) { + gl_clip_t clip = gl_clip(rect, bitmap); + + if (clip.width <= 0 || clip.height <= 0) { + return; + } + + gl_bitblt_t bb = { + // Destination bitmap + .height = clip.height, + .width = clip.width, + .dst_row = NULL, + .dst_x = clip.dst_x, + .dst_y = clip.dst_y, + .dst_stride = 0, + + // Source bitmap + .src_row = (uint8_t*)bitmap->ptr + bitmap->stride * clip.src_y, + .src_x = clip.src_x, + .src_y = clip.src_y, + .src_stride = bitmap->stride, + .src_fg = bitmap->fg_color, + .src_bg = bitmap->bg_color, + .src_alpha = 255, + }; + + // Currently, we use `gl_draw_bitmap` exclusively for text rendering. + // Therefore, we are including the variant of `display_copy_xxx()` that is + // specifically needed for drawing glyphs in the format we are using + // to save some space in the flash memory. + +#if TREZOR_FONT_BPP == 1 + if (bitmap->format == GL_FORMAT_MONO1P) { + display_copy_mono1p(&bb); + } +#endif +#if TREZOR_FONT_BPP == 4 + if (bitmap->format == GL_FORMAT_MONO4) { + display_copy_mono4(&bb); + } +#endif +} + +#if TREZOR_FONT_BPP == 1 +#define GLYPH_FORMAT GL_FORMAT_MONO1P +#define GLYPH_STRIDE(w) (((w) + 7) / 8) +#elif TREZOR_FONT_BPP == 2 +#error Unsupported TREZOR_FONT_BPP value +#define GLYPH_FORMAT GL_FORMAT_MONO2 +#define GLYPH_STRIDE(w) (((w) + 3) / 4) +#elif TREZOR_FONT_BPP == 4 +#define GLYPH_FORMAT GL_FORMAT_MONO4 +#define GLYPH_STRIDE(w) (((w) + 1) / 2) +#elif TREZOR_FONT_BPP == 8 +#error Unsupported TREZOR_FONT_BPP value +#define GLYPH_FORMAT GL_FORMAT_MONO8 +#define GLYPH_STRIDE(w) (w) +#else +#error Unsupported TREZOR_FONT_BPP value +#endif + +#define GLYPH_WIDTH(g) ((g)[0]) +#define GLYPH_HEIGHT(g) ((g)[1]) +#define GLYPH_ADVANCE(g) ((g)[2]) +#define GLYPH_BEARING_X(g) ((g)[3]) +#define GLYPH_BEARING_Y(g) ((g)[4]) +#define GLYPH_DATA(g) ((void*)&(g)[5]) + +void gl_draw_text(gl_offset_t pos, const char* text, size_t maxlen, + const gl_text_attr_t* attr) { + if (text == NULL) { + return; + } + + gl_bitmap_t bitmap = { + .format = GLYPH_FORMAT, + .fg_color = attr->fg_color, + .bg_color = attr->bg_color, + }; + + int max_height = font_max_height(attr->font); + int baseline = font_baseline(attr->font); + + for (int i = 0; i < maxlen; i++) { + uint8_t ch = (uint8_t)text[i]; + + if (ch == 0 || pos.x >= DISPLAY_RESX) { + break; + } + + const uint8_t* glyph = font_get_glyph(attr->font, ch); + + if (glyph == NULL) { + continue; + } + + bitmap.ptr = GLYPH_DATA(glyph); + bitmap.stride = GLYPH_STRIDE(GLYPH_WIDTH(glyph)); + bitmap.size.x = GLYPH_WIDTH(glyph); + bitmap.size.y = GLYPH_HEIGHT(glyph); + + bitmap.offset.x = -GLYPH_BEARING_X(glyph); + bitmap.offset.y = -(max_height - baseline - GLYPH_BEARING_Y(glyph)); + + gl_draw_bitmap(gl_rect(pos.x, pos.y, DISPLAY_RESX, DISPLAY_RESY), &bitmap); + + pos.x += GLYPH_ADVANCE(glyph); + } +} + +// =============================================================== +// emulation of legacy functions + +void display_clear(void) { gl_clear(); } + +void display_bar(int x, int y, int w, int h, uint16_t c) { + gl_draw_bar(gl_rect_wh(x, y, w, h), c); +} + +void display_text(int x, int y, const char* text, int textlen, int font, + uint16_t fg_color, uint16_t bg_color) { + gl_text_attr_t attr = { + .font = font, + .fg_color = fg_color, + .bg_color = bg_color, + }; + + size_t maxlen = textlen < 0 ? UINT32_MAX : textlen; + gl_draw_text(gl_offset(x, y), text, maxlen, &attr); +} + +void display_text_center(int x, int y, const char* text, int textlen, int font, + uint16_t fg_color, uint16_t bg_color) { + gl_text_attr_t attr = { + .font = font, + .fg_color = fg_color, + .bg_color = bg_color, + }; + + size_t maxlen = textlen < 0 ? UINT32_MAX : textlen; + int w = font_text_width(font, text, textlen); + gl_draw_text(gl_offset(x - w / 2, y), text, maxlen, &attr); +} diff --git a/core/embed/lib/gl_draw.h b/core/embed/lib/gl_draw.h new file mode 100644 index 000000000..38b45e8be --- /dev/null +++ b/core/embed/lib/gl_draw.h @@ -0,0 +1,152 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef GL_DRAW_H +#define GL_DRAW_H + +#include "gl_color.h" + +// 2D rectangle coordinates +// +// `x0`, `y0` - top-left coordinates +// `x1`, `y1` - bottom-right coordinates point (not included) +typedef struct { + int16_t x0; + int16_t y0; + int16_t x1; + int16_t y1; +} gl_rect_t; + +// Builds a rectangle (`gl_rect_t`) from top-left coordinates and dimensions +static inline gl_rect_t gl_rect_wh(int16_t x, int16_t y, int16_t w, int16_t h) { + gl_rect_t rect = { + .x0 = x, + .y0 = y, + .x1 = x + w, + .y1 = y + h, + }; + + return rect; +} + +// Builds a rectangle (`gl_rect_t`) from top-left and bottom-right coordinates +static inline gl_rect_t gl_rect(int16_t x0, int16_t y0, int16_t x1, + int16_t y1) { + gl_rect_t rect = { + .x0 = x0, + .y0 = y0, + .x1 = x1, + .y1 = y1, + }; + + return rect; +} + +// 2D offset/ coordinates +typedef struct { + int16_t x; + int16_t y; +} gl_offset_t; + +// Builds a `gl_offset_t` structure +static inline gl_offset_t gl_offset(int16_t x, int16_t y) { + gl_offset_t offset = { + .x = x, + .y = y, + }; + + return offset; +} + +// 2D size in pixels +typedef struct { + int16_t x; + int16_t y; +} gl_size_t; + +// Builds a `gl_size_t` structure +static inline gl_size_t gl_size(int16_t x, int16_t y) { + gl_size_t size = { + .x = x, + .y = y, + }; + + return size; +} + +// Format of pixels in a bitmap +typedef enum { + GL_FORMAT_UNKNOWN, // + GL_FORMAT_MONO1P, // 1-bpp per pixel (packed) + GL_FORMAT_MONO4, // 4-bpp per pixel + GL_FORMAT_RGB565, // 16-bpp per pixel + GL_FORMAT_RGBA8888, // 32-bpp +} gl_format_t; + +// 2D bitmap reference +typedef struct { + // pointer to top-left pixel + void* ptr; + // stride in bytes + size_t stride; + // size in pixels + gl_size_t size; + // format of pixels, GL_FORMAT_xxx + uint8_t format; + // offset used when bitmap is drawed using gl_draw_bitmap() + gl_offset_t offset; + // foreground color (used with MONOx formats) + gl_color_t fg_color; + // background color (used with MONOx formats) + gl_color_t bg_color; +} gl_bitmap_t; + +// Text attributes (font and color) +typedef struct { + // Font identifier + int font; + // Foreground color + gl_color_t fg_color; + // Background color + gl_color_t bg_color; +} gl_text_attr_t; + +// Fills a rectangle with a specified color. +void gl_draw_bar(gl_rect_t rect, gl_color_t color); + +// Draws a bitmap into the specified rectangle. +// +// The destination rectangle may not be fully filled if the source bitmap +// is smaller than destination rectangle or if the bitmap is translated by +// an offset partially or completely outside the destination rectangle. +// +// Currently, we use `gl_draw_bitmap` exclusively for text rendering. +// Not all bitmap formats are supported now. Please see the implementation. +void gl_draw_bitmap(gl_rect_t rect, const gl_bitmap_t* bitmap); + +// Draws a text to the specified position. +// +// `offset` - the most left point on the font baseline +// `text` - utf-8 text +// `maxlen` - maximum number of characters displayed (use SIZE_MAX when not +// specified) `attr` - font & text color +void gl_draw_text(gl_offset_t offset, const char* text, size_t maxlen, + const gl_text_attr_t* attr); + +#endif // GL_DRAW_H diff --git a/core/embed/lib/terminal.c b/core/embed/lib/terminal.c index 9411cbdff..9a471aebc 100644 --- a/core/embed/lib/terminal.c +++ b/core/embed/lib/terminal.c @@ -24,7 +24,8 @@ #include "display.h" #include TREZOR_BOARD -#ifndef TREZOR_PRINT_DISABLE +#include "fonts/fonts.h" +#include "gl_draw.h" #define TERMINAL_COLS (DISPLAY_RESX / 6) #define TERMINAL_ROWS (DISPLAY_RESY / 8) @@ -39,6 +40,62 @@ void term_set_color(uint16_t fgcolor, uint16_t bgcolor) { terminal_bgcolor = bgcolor; } +#ifdef NEW_RENDERING + +// Font_Bitmap contains 96 (0x20 - 0x7F) 5x7 glyphs +// Each glyph consists of 5 bytes (each byte represents one column) +// +// This function converts the glyph into the format compatible +// with `display_copy_mono1p()` functions. +static uint64_t term_glyph_bits(char ch) { + union { + uint64_t u64; + uint8_t bytes[8]; + } result = {0}; + + if (ch > ' ' && ch < 128) { + const uint8_t *b = &Font_Bitmap[(ch - ' ') * 5]; + + for (int y = 0; y < 7; y++) { + uint8_t mask = 1 << y; + result.bytes[y] |= ((b[0] & mask) ? 128 : 0) + ((b[1] & mask) ? 64 : 0) + + ((b[2] & mask) ? 32 : 0) + ((b[3] & mask) ? 16 : 0) + + ((b[4] & mask) ? 8 : 0); + } + } + return result.u64; +} + +// Redraws specified rows to the display +static void term_redraw_rows(int start_row, int row_count) { + uint64_t glyph_bits = 0; + gl_bitblt_t bb = { + .height = 8, + .width = 6, + .dst_row = NULL, + .dst_x = 0, + .dst_y = 0, + .dst_stride = 0, + + .src_row = &glyph_bits, + .src_x = 0, + .src_y = 0, + .src_stride = 8, + .src_fg = terminal_fgcolor, + .src_bg = terminal_bgcolor, + }; + + for (int y = start_row; y < start_row + row_count; y++) { + bb.dst_y = y * 8; + for (int x = 0; x < TERMINAL_COLS; x++) { + glyph_bits = term_glyph_bits(terminal_fb[y][x]); + bb.dst_x = x * 6; + display_copy_mono1p(&bb); + } + } +} +#endif // NEW_RENDERING + // display text using bitmap font void term_print(const char *text, int textlen) { static uint8_t row = 0, col = 0; @@ -77,6 +134,10 @@ void term_print(const char *text, int textlen) { } } +#ifdef NEW_RENDERING + term_redraw_rows(0, TERMINAL_ROWS); + display_refresh(); +#else // NEW RENDERING // render buffer to display display_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1); for (int i = 0; i < DISPLAY_RESX * DISPLAY_RESY; i++) { @@ -105,6 +166,7 @@ void term_print(const char *text, int textlen) { } display_pixeldata_dirty(); display_refresh(); +#endif } #ifdef TREZOR_EMULATOR @@ -127,5 +189,3 @@ void term_printf(const char *fmt, ...) { va_end(va); } } - -#endif // TREZOR_PRINT_DISABLE diff --git a/core/embed/lib/terminal.h b/core/embed/lib/terminal.h index 7cc036dff..d1dc9bfa0 100644 --- a/core/embed/lib/terminal.h +++ b/core/embed/lib/terminal.h @@ -22,11 +22,9 @@ #include "colors.h" -#ifndef TREZOR_PRINT_DISABLE void term_set_color(uint16_t fgcolor, uint16_t bgcolor); void term_print(const char *text, int textlen); void term_printf(const char *fmt, ...) __attribute__((__format__(__printf__, 1, 2))); -#endif #endif // LIB_TERMINAL_H diff --git a/core/embed/prodtest/main.c b/core/embed/prodtest/main.c index 71a822c99..69f40bb2a 100644 --- a/core/embed/prodtest/main.c +++ b/core/embed/prodtest/main.c @@ -26,6 +26,7 @@ #include "button.h" #include "common.h" #include "display.h" +#include "display_draw.h" #include "display_utils.h" #include "fault_handlers.h" #include "flash.h" diff --git a/core/embed/reflash/main.c b/core/embed/reflash/main.c index 06b3b2d3a..b98a3bceb 100644 --- a/core/embed/reflash/main.c +++ b/core/embed/reflash/main.c @@ -25,6 +25,7 @@ #include "common.h" #include "display.h" +#include "display_draw.h" #include "flash.h" #include "image.h" #include "model.h" diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml index 2faab74a7..6454358e8 100644 --- a/core/embed/rust/Cargo.toml +++ b/core/embed/rust/Cargo.toml @@ -15,10 +15,18 @@ micropython = [] protobuf = ["micropython"] ui = [] dma2d = [] +xframebuffer = [] +display_mono = [] +display_rgb565 = [] +display_rgba8888 = [] framebuffer = [] framebuffer32bit = [] ui_debug = [] ui_bounds = [] +ui_antialiasing = [] +ui_blurring = [] +ui_jpeg_decoder = ["jpeg"] +new_rendering = [] bootloader = [] button = [] touch = [] diff --git a/core/embed/rust/trezorhal.h b/core/embed/rust/trezorhal.h index 1ee7ac69a..e393d3451 100644 --- a/core/embed/rust/trezorhal.h +++ b/core/embed/rust/trezorhal.h @@ -1,12 +1,15 @@ #include TREZOR_BOARD + #include "buffers.h" #include "button.h" #include "common.h" #include "display.h" -#include "display_interface.h" +#include "display_draw.h" #include "dma2d.h" +#include "dma2d_bitblt.h" #include "flash.h" #include "fonts/fonts.h" +#include "gl_bitblt.h" #include "haptic.h" #include "model.h" #include "rgb_led.h" diff --git a/core/embed/trezorhal/boards/stm32f429i-disc1.h b/core/embed/trezorhal/boards/stm32f429i-disc1.h index 9f8b0a65e..cd37a9712 100644 --- a/core/embed/trezorhal/boards/stm32f429i-disc1.h +++ b/core/embed/trezorhal/boards/stm32f429i-disc1.h @@ -3,18 +3,16 @@ #define HSE_8MHZ -#define MAX_DISPLAY_RESX 240 -#define MAX_DISPLAY_RESY 320 #define DISPLAY_RESX 240 #define DISPLAY_RESY 320 +#define DISPLAY_COLOR_MODE DMA2D_OUTPUT_RGB565 +#define DISPLAY_LEGACY_HEADER "displays/ltdc.h" #define USE_I2C 1 #define USE_TOUCH 1 #define USE_SDRAM 1 #define USE_RGB_COLORS 1 -#include "displays/ltdc.h" - #define I2C_COUNT 1 #define I2C_INSTANCE_0 I2C3 #define I2C_INSTANCE_0_CLK_EN __HAL_RCC_I2C3_CLK_ENABLE diff --git a/core/embed/trezorhal/boards/stm32u5a9j-dk.h b/core/embed/trezorhal/boards/stm32u5a9j-dk.h index 3ff3673ee..015cc1643 100644 --- a/core/embed/trezorhal/boards/stm32u5a9j-dk.h +++ b/core/embed/trezorhal/boards/stm32u5a9j-dk.h @@ -11,7 +11,11 @@ //#define USE_DISP_I8080_8BIT_DW 1 #define USE_HASH_PROCESSOR 1 -#include "displays/dsi.h" +#define DISPLAY_RESX 240 +#define DISPLAY_RESY 240 + +#define DISPLAY_COLOR_MODE DMA2D_OUTPUT_ARGB8888 +#define DISPLAY_LEGACY_HEADER "displays/dsi.h" #define I2C_COUNT 1 #define I2C_INSTANCE_0 I2C5 diff --git a/core/embed/trezorhal/boards/trezor_1.h b/core/embed/trezorhal/boards/trezor_1.h index 4f126a720..7fec05ac7 100644 --- a/core/embed/trezorhal/boards/trezor_1.h +++ b/core/embed/trezorhal/boards/trezor_1.h @@ -5,7 +5,8 @@ #define USE_BUTTON 1 -#include "displays/vg-2864ksweg01.h" +#define DISPLAY_RESX 128 +#define DISPLAY_RESY 64 #define BTN_LEFT_PIN GPIO_PIN_5 #define BTN_LEFT_PORT GPIOC diff --git a/core/embed/trezorhal/boards/trezor_r_v10.h b/core/embed/trezorhal/boards/trezor_r_v10.h index be44e5585..b48479d52 100644 --- a/core/embed/trezorhal/boards/trezor_r_v10.h +++ b/core/embed/trezorhal/boards/trezor_r_v10.h @@ -8,7 +8,9 @@ #define USE_I2C 1 #define USE_CONSUMPTION_MASK 1 -#include "displays/vg-2864ksweg01.h" +#define DISPLAY_RESX 128 +#define DISPLAY_RESY 64 +#define DISPLAY_LEGACY_HEADER "displays/vg-2864ksweg01.h" #define BTN_LEFT_PIN GPIO_PIN_10 #define BTN_LEFT_PORT GPIOC diff --git a/core/embed/trezorhal/boards/trezor_r_v3.h b/core/embed/trezorhal/boards/trezor_r_v3.h index 242a3f541..ffe8b6914 100644 --- a/core/embed/trezorhal/boards/trezor_r_v3.h +++ b/core/embed/trezorhal/boards/trezor_r_v3.h @@ -6,7 +6,9 @@ #define USE_BUTTON 1 #define USE_SBU 1 -#include "displays/ug-2828tswig01.h" +#define DISPLAY_RESX 128 +#define DISPLAY_RESY 128 +#define DISPLAY_LEGACY_HEADER "displays/ug-2828tswig01.h" #define BTN_LEFT_PIN GPIO_PIN_0 #define BTN_LEFT_PORT GPIOA diff --git a/core/embed/trezorhal/boards/trezor_r_v4.h b/core/embed/trezorhal/boards/trezor_r_v4.h index 29054d200..9dbbb6267 100644 --- a/core/embed/trezorhal/boards/trezor_r_v4.h +++ b/core/embed/trezorhal/boards/trezor_r_v4.h @@ -6,7 +6,9 @@ #define USE_BUTTON 1 #define USE_SBU 1 -#include "displays/vg-2864ksweg01.h" +#define DISPLAY_RESX 128 +#define DISPLAY_RESY 64 +#define DISPLAY_LEGACY_HEADER "displays/vg-2864ksweg01.h" #define BTN_LEFT_PIN GPIO_PIN_5 #define BTN_LEFT_PORT GPIOC diff --git a/core/embed/trezorhal/boards/trezor_r_v6.h b/core/embed/trezorhal/boards/trezor_r_v6.h index 72a7cedea..d177c3d72 100644 --- a/core/embed/trezorhal/boards/trezor_r_v6.h +++ b/core/embed/trezorhal/boards/trezor_r_v6.h @@ -6,7 +6,9 @@ #define USE_BUTTON 1 #define USE_SBU 1 -#include "displays/vg-2864ksweg01.h" +#define DISPLAY_RESX 128 +#define DISPLAY_RESY 64 +#define DISPLAY_LEGACY_HEADER "displays/vg-2864ksweg01.h" #define BTN_LEFT_PIN GPIO_PIN_10 #define BTN_LEFT_PORT GPIOC diff --git a/core/embed/trezorhal/boards/trezor_t.h b/core/embed/trezorhal/boards/trezor_t.h index 557f8bb55..14e2ffd3f 100644 --- a/core/embed/trezorhal/boards/trezor_t.h +++ b/core/embed/trezorhal/boards/trezor_t.h @@ -3,9 +3,6 @@ #define HSE_8MHZ -#define DISPLAY_RESX 240 -#define DISPLAY_RESY 240 - #define USE_SD_CARD 1 #define USE_I2C 1 #define USE_TOUCH 1 @@ -14,12 +11,14 @@ #define USE_BACKLIGHT 1 #define USE_DISP_I8080_8BIT_DW 1 -#include "displays/panels/lx154a2422.h" -#include "displays/st7789v.h" +#define DISPLAY_RESX 240 +#define DISPLAY_RESY 240 +#define DISPLAY_COLOR_MODE DMA2D_OUTPUT_RGB565 +#define DISPLAY_LEGACY_HEADER "displays/st7789v.h" + #define DISPLAY_IDENTIFY 1 #define DISPLAY_TE_PORT GPIOD #define DISPLAY_TE_PIN GPIO_PIN_12 -#define TRANSFORM_TOUCH_COORDS lx154a2422_transform_touch_coords #define BACKLIGHT_PWM_FREQ 10000 #define BACKLIGHT_PWM_TIM TIM1 diff --git a/core/embed/trezorhal/boards/trezor_t3t1_revE.h b/core/embed/trezorhal/boards/trezor_t3t1_revE.h index 95e11f796..4fd2f7b48 100644 --- a/core/embed/trezorhal/boards/trezor_t3t1_revE.h +++ b/core/embed/trezorhal/boards/trezor_t3t1_revE.h @@ -1,9 +1,6 @@ #ifndef _TREZOR_T3T1_H #define _TREZOR_T3T1_H -#define DISPLAY_RESX 240 -#define DISPLAY_RESY 240 - #define VDD_3V3 1 #define USE_SD_CARD 1 @@ -16,8 +13,11 @@ #define USE_BACKLIGHT 1 #define USE_HASH_PROCESSOR 1 -#include "displays/panels/lx154a2422.h" -#include "displays/st7789v.h" +#define DISPLAY_RESX 240 +#define DISPLAY_RESY 240 +#define DISPLAY_LEGACY_HEADER "displays/st7789v.h" +#define DISPLAY_COLOR_MODE DMA2D_OUTPUT_RGB565 + #define DISPLAY_IDENTIFY 1 #define DISPLAY_TE_PORT GPIOD #define DISPLAY_TE_PIN GPIO_PIN_12 @@ -26,10 +26,6 @@ #define DISPLAY_TE_INTERRUPT_GPIOSEL EXTI_GPIOD #define DISPLAY_TE_INTERRUPT_EXTI_LINE EXTI_LINE_12 -#define DISPLAY_PANEL_INIT_SEQ lx154a2422_init_seq -#define DISPLAY_PANEL_ROTATE lx154a2422_rotate -#define TRANSFORM_TOUCH_COORDS lx154a2422_transform_touch_coords - #define BACKLIGHT_PWM_FREQ 12500 #define BACKLIGHT_PWM_TIM TIM17 #define BACKLIGHT_PWM_TIM_CLK_EN __HAL_RCC_TIM17_CLK_ENABLE diff --git a/core/embed/trezorhal/boards/trezor_t3t1_v4.h b/core/embed/trezorhal/boards/trezor_t3t1_v4.h index a43dee378..bf2c0a00d 100644 --- a/core/embed/trezorhal/boards/trezor_t3t1_v4.h +++ b/core/embed/trezorhal/boards/trezor_t3t1_v4.h @@ -1,9 +1,6 @@ #ifndef _TREZOR_T3T1_H #define _TREZOR_T3T1_H -#define DISPLAY_RESX 240 -#define DISPLAY_RESY 240 - #define VDD_3V3 1 #define HSE_16MHZ 1 @@ -17,8 +14,11 @@ #define USE_BACKLIGHT 1 #define USE_HASH_PROCESSOR 1 -#include "displays/panels/lx154a2422.h" -#include "displays/st7789v.h" +#define DISPLAY_RESX 240 +#define DISPLAY_RESY 240 +#define DISPLAY_LEGACY_HEADER "displays/st7789v.h" +#define DISPLAY_COLOR_MODE DMA2D_OUTPUT_RGB565 + #define DISPLAY_IDENTIFY 1 #define DISPLAY_TE_PORT GPIOD #define DISPLAY_TE_PIN GPIO_PIN_12 @@ -27,10 +27,6 @@ #define DISPLAY_TE_INTERRUPT_GPIOSEL EXTI_GPIOD #define DISPLAY_TE_INTERRUPT_EXTI_LINE EXTI_LINE_12 -#define DISPLAY_PANEL_INIT_SEQ lx154a2422_init_seq -#define DISPLAY_PANEL_ROTATE lx154a2422_rotate -#define TRANSFORM_TOUCH_COORDS lx154a2422_transform_touch_coords - #define BACKLIGHT_PWM_FREQ 12500 #define BACKLIGHT_PWM_TIM TIM8 #define BACKLIGHT_PWM_TIM_CLK_EN __HAL_RCC_TIM8_CLK_ENABLE diff --git a/core/embed/lib/display_interface.h b/core/embed/trezorhal/display.h similarity index 89% rename from core/embed/lib/display_interface.h rename to core/embed/trezorhal/display.h index 19f68161a..5e296a8f7 100644 --- a/core/embed/lib/display_interface.h +++ b/core/embed/trezorhal/display.h @@ -17,13 +17,22 @@ * along with this program. If not, see . */ -#ifndef _DISPLAY_INTERFACE_H -#define _DISPLAY_INTERFACE_H +#ifndef TREZORHAL_DISPLAY_H +#define TREZORHAL_DISPLAY_H + +#if NEW_RENDERING +#include +#else #include #include "common.h" + #include TREZOR_BOARD +#ifdef DISPLAY_LEGACY_HEADER +#include DISPLAY_LEGACY_HEADER +#endif + #ifndef DISPLAY_FRAMEBUFFER_OFFSET_Y #define DISPLAY_FRAMEBUFFER_OFFSET_Y 0 #endif @@ -68,4 +77,5 @@ 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 +#endif // NEW_RENDERING +#endif // TREZORHAL_DISPLAY_H diff --git a/core/embed/trezorhal/dma2d_bitblt.h b/core/embed/trezorhal/dma2d_bitblt.h new file mode 100644 index 000000000..fc327de6e --- /dev/null +++ b/core/embed/trezorhal/dma2d_bitblt.h @@ -0,0 +1,45 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TREZORHAL_DMA2D_BITBLT_H +#define TREZORHAL_DMA2D_BITBLT_H + +#include "gl_bitblt.h" + +// Waits until any pending DMA2D operation is finished +void dma2d_wait(void); + +// Following functions are hardware (DMA2D) accelerated versions +// of `gl_rgb565_xxx()` and `gl_rgba8888_xxx()` function from `gl_bitblt.h` + +// These functions may return `false`, indicating that the accelerated +// operation cannot be performed and must be implemented in software + +bool dma2d_rgb565_fill(const gl_bitblt_t* bb); +bool dma2d_rgb565_copy_mono4(const gl_bitblt_t* bb); +bool dma2d_rgb565_copy_rgb565(const gl_bitblt_t* bb); +bool dma2d_rgb565_blend_mono4(const gl_bitblt_t* bb); + +bool dma2d_rgba8888_fill(const gl_bitblt_t* bb); +bool dma2d_rgba8888_copy_mono4(const gl_bitblt_t* bb); +bool dma2d_rgba8888_copy_rgb565(const gl_bitblt_t* bb); +bool dma2d_rgba8888_copy_rgba8888(const gl_bitblt_t* bb); +bool dma2d_rgba8888_blend_mono4(const gl_bitblt_t* bb); + +#endif // TREZORHAL_DMA2D_BITBLT_H diff --git a/core/embed/trezorhal/stm32f4/common.c b/core/embed/trezorhal/stm32f4/common.c index 1c6d48f76..058407388 100644 --- a/core/embed/trezorhal/stm32f4/common.c +++ b/core/embed/trezorhal/stm32f4/common.c @@ -219,7 +219,11 @@ void collect_hw_entropy(void) { // where this setting might be unknown void ensure_compatible_settings(void) { #ifdef TREZOR_MODEL_T +#ifdef NEW_RENDERING + display_set_compatible_settings(); +#else display_set_big_endian(); +#endif display_orientation(0); set_core_clock(CLOCK_168_MHZ); backlight_pwm_set_slow(); diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/display_driver.c b/core/embed/trezorhal/stm32f4/display/st-7789/display_driver.c new file mode 100644 index 000000000..848843c2f --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/display_driver.c @@ -0,0 +1,149 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include + +#include "display_fb.h" +#include "display_io.h" +#include "display_panel.h" + +#include "backlight_pwm.h" +#include "supervise.h" + +#ifndef BOARDLOADER +#include "bg_copy.h" +#endif + +#if (DISPLAY_RESX != 240) || (DISPLAY_RESY != 240) +#error "Incompatible display resolution" +#endif + +// Display driver context. +typedef struct { + // Current display orientation (0, 90, 180, 270) + int orientation_angle; +} display_driver_t; + +// Display driver instance +static display_driver_t g_display_driver; + +void display_init(void) { + display_driver_t* drv = &g_display_driver; + memset(drv, 0, sizeof(display_driver_t)); + + display_io_init_gpio(); + display_io_init_fmc(); + display_panel_init(); + display_panel_set_little_endian(); + backlight_pwm_init(); + +#ifdef XFRAMEBUFFER + display_io_init_te_interrupt(); +#endif +} + +void display_reinit(void) { + display_driver_t* drv = &g_display_driver; + memset(drv, 0, sizeof(display_driver_t)); + + // Reinitialize FMC to set correct timing + // We have to do this in reinit because boardloader is fixed. + display_io_init_fmc(); + + // Important for model T as this is not set in boardloader + display_panel_set_little_endian(); + display_panel_init_gamma(); + backlight_pwm_reinit(); + +#ifdef XFRAMEBUFFER + display_io_init_te_interrupt(); +#endif +} + +void display_finish_actions(void) { +#ifdef XFRAMEBUFFER +#ifndef BOARDLOADER + bg_copy_wait(); +#endif +#endif +} + +int display_set_backlight(int level) { +#ifdef XFRAMEBUFFER +#ifndef BOARDLOADER + // wait for DMA transfer to finish before changing backlight + // so that we know that panel has current data + if (backlight_pwm_get() != level && !is_mode_handler()) { + bg_copy_wait(); + } +#endif +#endif + + return backlight_pwm_set(level); +} + +int display_get_backlight(void) { return backlight_pwm_get(); } + +int display_set_orientation(int angle) { + display_driver_t* drv = &g_display_driver; + + if (angle != drv->orientation_angle) { + if (angle == 0 || angle == 90 || angle == 180 || angle == 270) { + drv->orientation_angle = angle; + +#ifdef XFRAMEBUFFER + display_physical_fb_clear(); +#endif + + display_panel_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1); + for (uint32_t i = 0; i < DISPLAY_RESX * DISPLAY_RESY; i++) { + // 2 bytes per pixel because we're using RGB 5-6-5 format + ISSUE_PIXEL_DATA(0x0000); + } + + display_panel_rotate(angle); + } + } + + return drv->orientation_angle; +} + +int display_get_orientation(void) { + display_driver_t* drv = &g_display_driver; + + return drv->orientation_angle; +} + +void display_wait_for_sync(void) { +#ifdef DISPLAY_TE_PIN + uint32_t id = display_panel_identify(); + if (id && (id != DISPLAY_ID_GC9307)) { + // synchronize with the panel synchronization signal + // in order to avoid visual tearing effects + while (GPIO_PIN_SET == HAL_GPIO_ReadPin(DISPLAY_TE_PORT, DISPLAY_TE_PIN)) + ; + while (GPIO_PIN_RESET == HAL_GPIO_ReadPin(DISPLAY_TE_PORT, DISPLAY_TE_PIN)) + ; + } +#endif +} + +void display_set_compatible_settings(void) { display_panel_set_big_endian(); } diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/display_fb.c b/core/embed/trezorhal/stm32f4/display/st-7789/display_fb.c new file mode 100644 index 000000000..551189db6 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/display_fb.c @@ -0,0 +1,218 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include TREZOR_BOARD +#include STM32_HAL_H + +#include "display_fb.h" +#include "display_io.h" +#include "display_panel.h" +#include "xdisplay.h" + +#include "gl_bitblt.h" +#include "irq.h" +#include "supervise.h" + +#ifndef BOARDLOADER +#include "bg_copy.h" +#endif + +#ifndef STM32U5 +#error Framebuffer only supported on STM32U5 for now +#endif + +// Size of the physical frame buffer in bytes +#define PHYSICAL_FRAME_BUFFER_SIZE (DISPLAY_RESX * DISPLAY_RESY * 2) + +// Physical frame buffers in internal SRAM memory. +// Both frame buffers layes in the fixed addresses that +// are shared between bootloaders and the firmware. +__attribute__((section(".fb1"))) +ALIGN_32BYTES(uint8_t physical_frame_buffer_0[PHYSICAL_FRAME_BUFFER_SIZE]); +__attribute__((section(".fb2"))) +ALIGN_32BYTES(uint8_t physical_frame_buffer_1[PHYSICAL_FRAME_BUFFER_SIZE]); + +// The current frame buffer selector at fixed memory address +// It's shared between bootloaders and the firmware +__attribute__((section(".framebuffer_select"))) uint32_t current_frame_buffer = + 0; + +void display_physical_fb_clear(void) { + memset(physical_frame_buffer_0, 0, sizeof(physical_frame_buffer_0)); + memset(physical_frame_buffer_1, 0, sizeof(physical_frame_buffer_1)); +} + +#ifndef BOARDLOADER +static bool pending_fb_switch = false; +#endif + +#ifndef BOARDLOADER +void DISPLAY_TE_INTERRUPT_HANDLER(void) { + HAL_NVIC_DisableIRQ(DISPLAY_TE_INTERRUPT_NUM); + + if (current_frame_buffer == 1) { + bg_copy_start_const_out_8((uint8_t *)physical_frame_buffer_1, + (uint8_t *)DISPLAY_DATA_ADDRESS, + DISPLAY_RESX * DISPLAY_RESY * 2); + + } else { + bg_copy_start_const_out_8((uint8_t *)physical_frame_buffer_0, + (uint8_t *)DISPLAY_DATA_ADDRESS, + DISPLAY_RESX * DISPLAY_RESY * 2); + } + + pending_fb_switch = false; + __HAL_GPIO_EXTI_CLEAR_FLAG(DISPLAY_TE_PIN); +} + +static void wait_for_fb_switch(void) { + while (pending_fb_switch) { + __WFI(); + } + bg_copy_wait(); +} +#endif + +static void copy_fb_to_display(uint16_t *fb) { + for (int i = 0; i < DISPLAY_RESX * DISPLAY_RESY; i++) { + // 2 bytes per pixel because we're using RGB 5-6-5 format + ISSUE_PIXEL_DATA(fb[i]); + } +} + +static void switch_fb_manually(void) { + // sync with the panel refresh + while (GPIO_PIN_SET == HAL_GPIO_ReadPin(DISPLAY_TE_PORT, DISPLAY_TE_PIN)) { + } + while (GPIO_PIN_RESET == HAL_GPIO_ReadPin(DISPLAY_TE_PORT, DISPLAY_TE_PIN)) { + } + + if (current_frame_buffer == 0) { + current_frame_buffer = 1; + copy_fb_to_display((uint16_t *)physical_frame_buffer_1); + memcpy(physical_frame_buffer_0, physical_frame_buffer_1, + sizeof(physical_frame_buffer_0)); + + } else { + current_frame_buffer = 0; + copy_fb_to_display((uint16_t *)physical_frame_buffer_0); + memcpy(physical_frame_buffer_1, physical_frame_buffer_0, + sizeof(physical_frame_buffer_1)); + } +} + +#ifndef BOARDLOADER +static void switch_fb_in_backround(void) { + if (current_frame_buffer == 0) { + current_frame_buffer = 1; + + memcpy(physical_frame_buffer_0, physical_frame_buffer_1, + sizeof(physical_frame_buffer_0)); + + pending_fb_switch = true; + __HAL_GPIO_EXTI_CLEAR_FLAG(DISPLAY_TE_PIN); + svc_enableIRQ(DISPLAY_TE_INTERRUPT_NUM); + } else { + current_frame_buffer = 0; + memcpy(physical_frame_buffer_1, physical_frame_buffer_0, + sizeof(physical_frame_buffer_1)); + + pending_fb_switch = true; + __HAL_GPIO_EXTI_CLEAR_FLAG(DISPLAY_TE_PIN); + svc_enableIRQ(DISPLAY_TE_INTERRUPT_NUM); + } +} +#endif + +display_fb_info_t display_get_frame_buffer(void) { + void *addr; + + if (current_frame_buffer == 0) { + addr = (void *)physical_frame_buffer_1; + } else { + addr = (void *)physical_frame_buffer_0; + } + + display_fb_info_t fb = { + .ptr = addr, + .stride = DISPLAY_RESX * sizeof(uint16_t), + }; + + return fb; +} + +void display_refresh(void) { +#ifndef BOARDLOADER + wait_for_fb_switch(); + display_panel_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1); + + if (is_mode_handler()) { + switch_fb_manually(); + } else { + switch_fb_in_backround(); + } +#else + display_panel_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1); + switch_fb_manually(); +#endif +} + +void display_fill(const gl_bitblt_t *bb) { + display_fb_info_t fb = display_get_frame_buffer(); + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = (uint16_t *)((uintptr_t)fb.ptr + fb.stride * bb_new.dst_y); + bb_new.dst_stride = fb.stride; + + gl_rgb565_fill(&bb_new); +} + +void display_copy_rgb565(const gl_bitblt_t *bb) { + display_fb_info_t fb = display_get_frame_buffer(); + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = (uint16_t *)((uintptr_t)fb.ptr + fb.stride * bb_new.dst_y); + bb_new.dst_stride = fb.stride; + + gl_rgb565_copy_rgb565(&bb_new); +} + +void display_copy_mono1p(const gl_bitblt_t *bb) { + display_fb_info_t fb = display_get_frame_buffer(); + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = (uint16_t *)((uintptr_t)fb.ptr + fb.stride * bb_new.dst_y); + bb_new.dst_stride = fb.stride; + + gl_rgb565_copy_mono1p(&bb_new); +} + +void display_copy_mono4(const gl_bitblt_t *bb) { + display_fb_info_t fb = display_get_frame_buffer(); + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = (uint16_t *)((uintptr_t)fb.ptr + fb.stride * bb_new.dst_y); + bb_new.dst_stride = fb.stride; + + gl_rgb565_copy_mono4(&bb_new); +} diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/display_fb.h b/core/embed/trezorhal/stm32f4/display/st-7789/display_fb.h new file mode 100644 index 000000000..d216be9c4 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/display_fb.h @@ -0,0 +1,32 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TREZORHAL_DISPLAY_FB_H +#define TREZORHAL_DISPLAY_FB_H + +#include + +#ifdef XFRAMEBUFFER + +// Clears both physical frame buffers +void display_physical_fb_clear(void); + +#endif // XFRAMEBUFFER + +#endif // TREZORHAL_DISPLAY_FB_H diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/display_io.c b/core/embed/trezorhal/stm32f4/display/st-7789/display_io.c new file mode 100644 index 000000000..fe02ca207 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/display_io.c @@ -0,0 +1,145 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include TREZOR_BOARD +#include STM32_HAL_H + +#include "display_io.h" +#include "irq.h" + +__IO DISP_MEM_TYPE *const DISPLAY_CMD_ADDRESS = + (__IO DISP_MEM_TYPE *const)((uint32_t)DISPLAY_MEMORY_BASE); +__IO DISP_MEM_TYPE *const DISPLAY_DATA_ADDRESS = + (__IO DISP_MEM_TYPE *const)((uint32_t)DISPLAY_MEMORY_BASE | + (DISPLAY_ADDR_SHIFT << DISPLAY_MEMORY_PIN)); + +void display_io_init_gpio(void) { + // init peripherals + __HAL_RCC_GPIOE_CLK_ENABLE(); + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_GPIOD_CLK_ENABLE(); + __HAL_RCC_FMC_CLK_ENABLE(); + + GPIO_InitTypeDef GPIO_InitStructure; + + // LCD_RST/PC14 + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Alternate = 0; + GPIO_InitStructure.Pin = GPIO_PIN_14; + // default to keeping display in reset + HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET); + HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); + +#ifdef DISPLAY_TE_PIN + // LCD_FMARK (tearing effect) + GPIO_InitStructure.Mode = GPIO_MODE_INPUT; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStructure.Alternate = 0; + GPIO_InitStructure.Pin = DISPLAY_TE_PIN; + HAL_GPIO_Init(DISPLAY_TE_PORT, &GPIO_InitStructure); +#endif + + GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStructure.Alternate = GPIO_AF12_FMC; + // LCD_CS/PD7 LCD_RS/PD11 LCD_RD/PD4 LCD_WR/PD5 + GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_11 | GPIO_PIN_4 | GPIO_PIN_5; + HAL_GPIO_Init(GPIOD, &GPIO_InitStructure); + // LCD_D0/PD14 LCD_D1/PD15 LCD_D2/PD0 LCD_D3/PD1 + GPIO_InitStructure.Pin = GPIO_PIN_14 | GPIO_PIN_15 | GPIO_PIN_0 | GPIO_PIN_1; + HAL_GPIO_Init(GPIOD, &GPIO_InitStructure); + // LCD_D4/PE7 LCD_D5/PE8 LCD_D6/PE9 LCD_D7/PE10 + GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10; + HAL_GPIO_Init(GPIOE, &GPIO_InitStructure); +#ifdef USE_DISP_I8080_16BIT_DW + // LCD_D8/PE11 LCD_D9/PE12 LCD_D10/PE13 LCD_D11/PE14 + GPIO_InitStructure.Pin = + GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14; + HAL_GPIO_Init(GPIOE, &GPIO_InitStructure); + // LCD_D12/PE15 + GPIO_InitStructure.Pin = GPIO_PIN_15; + HAL_GPIO_Init(GPIOE, &GPIO_InitStructure); + // LCD_D13/PD8 LCD_D14/PD9 LCD_D15/PD10 + GPIO_InitStructure.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10; + HAL_GPIO_Init(GPIOD, &GPIO_InitStructure); +#endif +} + +void display_io_init_fmc(void) { + // Reference UM1725 "Description of STM32F4 HAL and LL drivers", + // section 64.2.1 "How to use this driver" + SRAM_HandleTypeDef external_display_data_sram = {0}; + external_display_data_sram.Instance = FMC_NORSRAM_DEVICE; + external_display_data_sram.Extended = FMC_NORSRAM_EXTENDED_DEVICE; + external_display_data_sram.Init.NSBank = FMC_NORSRAM_BANK1; + external_display_data_sram.Init.DataAddressMux = FMC_DATA_ADDRESS_MUX_DISABLE; + external_display_data_sram.Init.MemoryType = FMC_MEMORY_TYPE_SRAM; +#ifdef USE_DISP_I8080_16BIT_DW + external_display_data_sram.Init.MemoryDataWidth = + FMC_NORSRAM_MEM_BUS_WIDTH_16; +#elif USE_DISP_I8080_8BIT_DW + external_display_data_sram.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_8; +#endif + external_display_data_sram.Init.BurstAccessMode = + FMC_BURST_ACCESS_MODE_DISABLE; + external_display_data_sram.Init.WaitSignalPolarity = + FMC_WAIT_SIGNAL_POLARITY_LOW; + external_display_data_sram.Init.WaitSignalActive = FMC_WAIT_TIMING_BEFORE_WS; + external_display_data_sram.Init.WriteOperation = FMC_WRITE_OPERATION_ENABLE; + external_display_data_sram.Init.WaitSignal = FMC_WAIT_SIGNAL_DISABLE; + external_display_data_sram.Init.ExtendedMode = FMC_EXTENDED_MODE_DISABLE; + external_display_data_sram.Init.AsynchronousWait = + FMC_ASYNCHRONOUS_WAIT_DISABLE; + external_display_data_sram.Init.WriteBurst = FMC_WRITE_BURST_DISABLE; + external_display_data_sram.Init.ContinuousClock = + FMC_CONTINUOUS_CLOCK_SYNC_ONLY; + external_display_data_sram.Init.PageSize = FMC_PAGE_SIZE_NONE; + + // reference RM0090 section 37.5 Table 259, 37.5.4, Mode 1 SRAM, and 37.5.6 + FMC_NORSRAM_TimingTypeDef normal_mode_timing = {0}; + normal_mode_timing.AddressSetupTime = 5; + normal_mode_timing.AddressHoldTime = 1; // don't care + normal_mode_timing.DataSetupTime = 6; + normal_mode_timing.BusTurnAroundDuration = 0; // don't care + normal_mode_timing.CLKDivision = 2; // don't care + normal_mode_timing.DataLatency = 2; // don't care + normal_mode_timing.AccessMode = FMC_ACCESS_MODE_A; + + HAL_SRAM_Init(&external_display_data_sram, &normal_mode_timing, NULL); +} + +#ifdef DISPLAY_TE_INTERRUPT_HANDLER +void display_io_init_te_interrupt(void) { + EXTI_HandleTypeDef EXTI_Handle = {0}; + EXTI_ConfigTypeDef EXTI_Config = {0}; + EXTI_Config.GPIOSel = DISPLAY_TE_INTERRUPT_GPIOSEL; + EXTI_Config.Line = DISPLAY_TE_INTERRUPT_EXTI_LINE; + EXTI_Config.Mode = EXTI_MODE_INTERRUPT; + EXTI_Config.Trigger = EXTI_TRIGGER_RISING; + HAL_EXTI_SetConfigLine(&EXTI_Handle, &EXTI_Config); + + // setup interrupt for tearing effect pin + HAL_NVIC_SetPriority(DISPLAY_TE_INTERRUPT_NUM, IRQ_PRI_DMA, 0); +} +#endif diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/display_io.h b/core/embed/trezorhal/stm32f4/display/st-7789/display_io.h new file mode 100644 index 000000000..603157c67 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/display_io.h @@ -0,0 +1,67 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TREZORHAL_DISPLAY_IO_H +#define TREZORHAL_DISPLAY_IO_H + +#include STM32_HAL_H +#include TREZOR_BOARD + +void display_io_init_gpio(void); +void display_io_init_fmc(void); +void display_io_init_te_interrupt(void); + +#ifndef FMC_BANK1 +#define FMC_BANK1 0x60000000U +#endif + +#define DISPLAY_MEMORY_BASE FMC_BANK1 +#define DISPLAY_MEMORY_PIN 16 + +#ifdef USE_DISP_I8080_16BIT_DW +#define DISPLAY_ADDR_SHIFT 2 +#define DISP_MEM_TYPE uint16_t +#elif USE_DISP_I8080_8BIT_DW +#define DISPLAY_ADDR_SHIFT 1 +#define DISP_MEM_TYPE uint8_t +#else +#error "Unsupported display interface" +#endif + +/*#define DISPLAY_CMD_ADDRESS ((__IO DISP_MEM_TYPE *)(DISPLAY_MEMORY_BASE)) +#define DISPLAY_DATA_ADDRESS \ + ((__IO DISP_MEM_TYPE *)(DISPLAY_MEMORY_BASE | \ + (DISPLAY_ADDR_SHIFT << DISPLAY_MEMORY_PIN))) +*/ + +extern __IO DISP_MEM_TYPE *const DISPLAY_CMD_ADDRESS; +extern __IO DISP_MEM_TYPE *const DISPLAY_DATA_ADDRESS; + +#define ISSUE_CMD_BYTE(X) (*(DISPLAY_CMD_ADDRESS) = (X)) +#define ISSUE_DATA_BYTE(X) (*(DISPLAY_DATA_ADDRESS) = (X)) + +#ifdef USE_DISP_I8080_16BIT_DW +#define ISSUE_PIXEL_DATA(X) DATA(X) +#elif USE_DISP_I8080_8BIT_DW +#define ISSUE_PIXEL_DATA(X) \ + ISSUE_DATA_BYTE((X)&0xFF); \ + ISSUE_DATA_BYTE((X) >> 8) +#endif + +#endif // TREZORHAL_DISPLAY_IO_H diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/display_nofb.c b/core/embed/trezorhal/stm32f4/display/st-7789/display_nofb.c new file mode 100644 index 000000000..b4d76f3a3 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/display_nofb.c @@ -0,0 +1,103 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include TREZOR_BOARD + +#include + +#include "display_io.h" +#include "display_panel.h" + +void display_refresh(void) { + // if the framebuffer is not used the implementation is empty +} + +static inline void set_window(const gl_bitblt_t* bb) { + display_panel_set_window(bb->dst_x, bb->dst_y, bb->dst_x + bb->width - 1, + bb->dst_y + bb->height + 1); +} + +// For future notice, if we ever want to do a new model using progressive +// rendering. +// +// Following functions can be optimized by using DMA (regular is likely enough) +// to copy the data, along with the fill function. If even more performance is +// needed, we could use double-slice similarly to double-framebuffer and render +// to one with DMA2D while copying the other to the display with DMA. + +void display_fill(const gl_bitblt_t* bb) { + set_window(bb); + + uint16_t height = bb->height; + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + ISSUE_PIXEL_DATA(bb->src_fg); + } + } +} + +void display_copy_rgb565(const gl_bitblt_t* bb) { + set_window(bb); + + uint16_t* src_ptr = (uint16_t*)bb->src_row + bb->src_x; + uint16_t height = bb->height; + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + ISSUE_PIXEL_DATA(src_ptr[x]); + } + src_ptr += bb->src_stride / sizeof(*src_ptr); + } +} + +void display_copy_mono1p(const gl_bitblt_t* bb) { + set_window(bb); + + uint8_t* src = (uint8_t*)bb->src_row; + uint16_t src_ofs = bb->src_stride * bb->src_y + bb->src_x; + uint16_t height = bb->height; + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + uint8_t mask = 1 << (7 - ((src_ofs + x) & 7)); + uint8_t data = src[(src_ofs + x) / 8]; + ISSUE_PIXEL_DATA((data & mask) ? bb->src_fg : bb->src_bg); + } + src_ofs += bb->src_stride; + } +} + +void display_copy_mono4(const gl_bitblt_t* bb) { + set_window(bb); + + const gl_color16_t* gradient = gl_color16_gradient_a4(bb->src_fg, bb->src_bg); + + uint8_t* src_row = (uint8_t*)bb->src_row; + uint16_t height = bb->height; + + while (height-- > 0) { + for (int x = 0; x < bb->width; x++) { + uint8_t fg_data = src_row[(x + bb->src_x) / 2]; + uint8_t fg_lum = (x + bb->src_x) & 1 ? fg_data >> 4 : fg_data & 0xF; + ISSUE_PIXEL_DATA(gradient[fg_lum]); + } + src_row += bb->src_stride / sizeof(*src_row); + } +} diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/display_panel.c b/core/embed/trezorhal/stm32f4/display/st-7789/display_panel.c new file mode 100644 index 000000000..aaf86d8e8 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/display_panel.c @@ -0,0 +1,244 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +// using const volatile instead of #define results in binaries that change +// only in 1-byte when the flag changes. +// using #define leads compiler to over-optimize the code leading to bigger +// differencies in the resulting binaries. + +#include "display_panel.h" +#include "display_io.h" + +#ifdef TREZOR_MODEL_T +#include "panels/154a.h" +#include "panels/lx154a2411.h" +#include "panels/lx154a2422.h" +#include "panels/tf15411a.h" +#else +#include "panels/lx154a2422.h" +#endif + +// using const volatile instead of #define results in binaries that change +// only in 1-byte when the flag changes. +// using #define leads compiler to over-optimize the code leading to bigger +// differencies in the resulting binaries. +const volatile uint8_t DISPLAY_ST7789V_INVERT_COLORS2 = 1; + +// Window padding (correction) when using 90dg or 270dg orientation +// (internally the display is 240x320 but we use only 240x240) +static display_padding_t g_window_padding; + +#ifdef DISPLAY_IDENTIFY +static uint32_t read_display_id(uint8_t command) { + volatile uint8_t c = 0; + uint32_t id = 0; + ISSUE_CMD_BYTE(command); + c = *DISPLAY_DATA_ADDRESS; // first returned value is a dummy value and + // should be discarded + c = *DISPLAY_DATA_ADDRESS; + id |= (c << 16); + c = *DISPLAY_DATA_ADDRESS; + id |= (c << 8); + c = *DISPLAY_DATA_ADDRESS; + id |= c; + return id; +} + +uint32_t display_panel_identify(void) { + static uint32_t id = 0x000000U; + static bool id_initialized = false; + + // Return immediately if id has been already initialized + if (id_initialized) return id; + + // RDDID: Read Display ID + id = read_display_id(0x04); + // the default RDDID for ILI9341 should be 0x8000. + // some display modules return 0x0. + // the ILI9341 has an extra id, let's check it here. + if ((id != DISPLAY_ID_ST7789V) && (id != DISPLAY_ID_GC9307)) { + // Read ID4 + uint32_t id4 = read_display_id(0xD3); + if (id4 == DISPLAY_ID_ILI9341V) { // definitely found a ILI9341 + id = id4; + } + } + id_initialized = true; + return id; +} +#else +uint32_t display_panel_identify(void) { return DISPLAY_ID_ST7789V; } +#endif + +bool display_panel_is_inverted() { + bool inv_on = false; + uint32_t id = display_panel_identify(); + if (id == DISPLAY_ID_ST7789V) { + volatile uint8_t c = 0; + ISSUE_CMD_BYTE(0x09); // read display status + c = *DISPLAY_DATA_ADDRESS; // don't care + c = *DISPLAY_DATA_ADDRESS; // don't care + c = *DISPLAY_DATA_ADDRESS; // don't care + c = *DISPLAY_DATA_ADDRESS; + if (c & 0x20) { + inv_on = true; + } + c = *DISPLAY_DATA_ADDRESS; // don't care + } + + return inv_on; +} + +void display_panel_sleep(void) { + uint32_t id = display_panel_identify(); + if ((id == DISPLAY_ID_ILI9341V) || (id == DISPLAY_ID_GC9307) || + (id == DISPLAY_ID_ST7789V)) { + ISSUE_CMD_BYTE(0x28); // DISPOFF: Display Off + ISSUE_CMD_BYTE(0x10); // SLPIN: Sleep in + HAL_Delay(5); // need to wait 5 milliseconds after "sleep in" before + // sending any new commands + } +} + +void display_panel_unsleep(void) { + uint32_t id = display_panel_identify(); + if ((id == DISPLAY_ID_ILI9341V) || (id == DISPLAY_ID_GC9307) || + (id == DISPLAY_ID_ST7789V)) { + ISSUE_CMD_BYTE(0x11); // SLPOUT: Sleep Out + HAL_Delay(5); // need to wait 5 milliseconds after "sleep out" before + // sending any new commands + ISSUE_CMD_BYTE(0x29); // DISPON: Display On + } +} + +void display_panel_set_window(uint16_t x0, uint16_t y0, uint16_t x1, + uint16_t y1) { + x0 += g_window_padding.x; + x1 += g_window_padding.x; + y0 += g_window_padding.y; + y1 += g_window_padding.y; + + uint32_t id = display_panel_identify(); + if ((id == DISPLAY_ID_ILI9341V) || (id == DISPLAY_ID_GC9307) || + (id == DISPLAY_ID_ST7789V)) { + ISSUE_CMD_BYTE(0x2A); + ISSUE_DATA_BYTE(x0 >> 8); + ISSUE_DATA_BYTE(x0 & 0xFF); + ISSUE_DATA_BYTE(x1 >> 8); + ISSUE_DATA_BYTE(x1 & 0xFF); // column addr set + ISSUE_CMD_BYTE(0x2B); + ISSUE_DATA_BYTE(y0 >> 8); + ISSUE_DATA_BYTE(y0 & 0xFF); + ISSUE_DATA_BYTE(y1 >> 8); + ISSUE_DATA_BYTE(y1 & 0xFF); // row addr set + ISSUE_CMD_BYTE(0x2C); + } +} + +void display_panel_set_little_endian(void) { + uint32_t id = display_panel_identify(); + if (id == DISPLAY_ID_GC9307) { + // CANNOT SET ENDIAN FOR GC9307 + } else if (id == DISPLAY_ID_ST7789V) { + ISSUE_CMD_BYTE(0xB0); + ISSUE_DATA_BYTE(0x00); + ISSUE_DATA_BYTE(0xF8); + } else if (id == DISPLAY_ID_ILI9341V) { + // Interface Control: XOR BGR as ST7789V does + ISSUE_CMD_BYTE(0xF6); + ISSUE_DATA_BYTE(0x09); + ISSUE_DATA_BYTE(0x30); + ISSUE_DATA_BYTE(0x20); + } +} + +void display_panel_set_big_endian(void) { + uint32_t id = display_panel_identify(); + if (id == DISPLAY_ID_GC9307) { + // CANNOT SET ENDIAN FOR GC9307 + } else if (id == DISPLAY_ID_ST7789V) { + ISSUE_CMD_BYTE(0xB0); + ISSUE_DATA_BYTE(0x00); + ISSUE_DATA_BYTE(0xF0); + } else if (id == DISPLAY_ID_ILI9341V) { + // Interface Control: XOR BGR as ST7789V does + ISSUE_CMD_BYTE(0xF6); + ISSUE_DATA_BYTE(0x09); + ISSUE_DATA_BYTE(0x30); + ISSUE_DATA_BYTE(0x00); + } +} + +void display_panel_init(void) { + HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET); // LCD_RST/PC14 + // wait 10 milliseconds. only needs to be low for 10 microseconds. + // my dev display module ties display reset and touch panel reset together. + // keeping this low for max(display_reset_time, ctpm_reset_time) aids + // development and does not hurt. + HAL_Delay(10); + HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_SET); // LCD_RST/PC14 + // max wait time for hardware reset is 120 milliseconds + // (experienced display flakiness using only 5ms wait before sending commands) + HAL_Delay(120); + + // identify the controller we will communicate with +#ifdef TREZOR_MODEL_T + uint32_t id = display_panel_identify(); + if (id == DISPLAY_ID_GC9307) { + tf15411a_init_seq(); + } else if (id == DISPLAY_ID_ST7789V) { + if (DISPLAY_ST7789V_INVERT_COLORS2) { + lx154a2422_init_seq(); + } else { + lx154a2411_init_seq(); + } + } else if (id == DISPLAY_ID_ILI9341V) { + _154a_init_seq(); + } +#else + lx154a2422_init_seq(); +#endif + + display_panel_unsleep(); +} + +void display_panel_init_gamma(void) { +#ifdef TREZOR_MODEL_T + uint32_t id = display_panel_identify(); + if (id == DISPLAY_ID_ST7789V && display_panel_is_inverted()) { + // newest TT display - set proper gamma + lx154a2422_gamma(); + } else if (id == DISPLAY_ID_ST7789V) { + lx154a2411_gamma(); + } +#endif +} + +void display_panel_rotate(int angle) { +#ifdef TREZOR_MODEL_T + uint32_t id = display_panel_identify(); + if (id == DISPLAY_ID_GC9307) { + tf15411a_rotate(angle, &g_window_padding); + } else { + lx154a2422_rotate(angle, &g_window_padding); + } +#else + lx154a2422_rotate(angle, &g_window_padding); +#endif +} diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/display_panel.h b/core/embed/trezorhal/stm32f4/display/st-7789/display_panel.h new file mode 100644 index 000000000..99935a7ba --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/display_panel.h @@ -0,0 +1,57 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TREZORHAL_ST7789_PANEL_H +#define TREZORHAL_ST7789_PANEL_H + +#include +#include + +// section "9.1.3 RDDID (04h): Read Display ID" +// of ST7789V datasheet +#define DISPLAY_ID_ST7789V 0x858552U +// section "6.2.1. Read display identification information (04h)" +// of GC9307 datasheet +#define DISPLAY_ID_GC9307 0x009307U +// section "8.3.23 Read ID4 (D3h)" +// of ILI9341V datasheet +#define DISPLAY_ID_ILI9341V 0x009341U + +typedef struct { + uint16_t x; + uint16_t y; +} display_padding_t; + +// Identifies the connected display panel and +// returns one of DISPLAY_ID_xxx constant +uint32_t display_panel_identify(void); +bool display_panel_is_inverted(); + +void display_panel_init(void); +void display_panel_init_gamma(void); +void display_panel_set_little_endian(void); +void display_panel_set_big_endian(void); + +void display_panel_sleep(void); +void display_panel_unsleep(void); +void display_panel_set_window(uint16_t x0, uint16_t y0, uint16_t x1, + uint16_t y1); +void display_panel_rotate(int angle); + +#endif // TREZORHAL_ST7789_PANEL_H diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/panels/154a.c b/core/embed/trezorhal/stm32f4/display/st-7789/panels/154a.c new file mode 100644 index 000000000..8d7dc0fe9 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/panels/154a.c @@ -0,0 +1,132 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "../display_io.h" + +void _154a_init_seq(void) { + // most recent manual: https://www.newhavendisplay.com/app_notes/ILI9341.pdf + // TEON: Tearing Effect Line On; V-blanking only + ISSUE_CMD_BYTE(0x35); + ISSUE_DATA_BYTE(0x00); + + // COLMOD: Interface Pixel format; 65K color: 16-bit/pixel (RGB 5-6-5 bits + // input) + ISSUE_CMD_BYTE(0x3A); + ISSUE_DATA_BYTE(0x55); + + // Display Function Control: gate scan direction 319 -> 0 + ISSUE_CMD_BYTE(0xB6); + ISSUE_DATA_BYTE(0x0A); + ISSUE_DATA_BYTE(0xC2); + ISSUE_DATA_BYTE(0x27); + ISSUE_DATA_BYTE(0x00); + + // Interface Control: XOR BGR as ST7789V does + ISSUE_CMD_BYTE(0xF6); + ISSUE_DATA_BYTE(0x09); + ISSUE_DATA_BYTE(0x30); + ISSUE_DATA_BYTE(0x00); + + // the above config is the most important and definitely necessary + + ISSUE_CMD_BYTE(0xCF); + ISSUE_DATA_BYTE(0x00); + ISSUE_DATA_BYTE(0xC1); + ISSUE_DATA_BYTE(0x30); + + ISSUE_CMD_BYTE(0xED); + ISSUE_DATA_BYTE(0x64); + ISSUE_DATA_BYTE(0x03); + ISSUE_DATA_BYTE(0x12); + ISSUE_DATA_BYTE(0x81); + + ISSUE_CMD_BYTE(0xE8); + ISSUE_DATA_BYTE(0x85); + ISSUE_DATA_BYTE(0x10); + ISSUE_DATA_BYTE(0x7A); + + ISSUE_CMD_BYTE(0xF7); + ISSUE_DATA_BYTE(0x20); + + ISSUE_CMD_BYTE(0xEA); + ISSUE_DATA_BYTE(0x00); + ISSUE_DATA_BYTE(0x00); + + // power control VRH[5:0] + ISSUE_CMD_BYTE(0xC0); + ISSUE_DATA_BYTE(0x23); + + // power control SAP[2:0] BT[3:0] + ISSUE_CMD_BYTE(0xC1); + ISSUE_DATA_BYTE(0x12); + + // vcm control 1 + ISSUE_CMD_BYTE(0xC5); + ISSUE_DATA_BYTE(0x60); + ISSUE_DATA_BYTE(0x44); + + // vcm control 2 + ISSUE_CMD_BYTE(0xC7); + ISSUE_DATA_BYTE(0x8A); + + // framerate + ISSUE_CMD_BYTE(0xB1); + ISSUE_DATA_BYTE(0x00); + ISSUE_DATA_BYTE(0x18); + + // 3 gamma func disable + ISSUE_CMD_BYTE(0xF2); + ISSUE_DATA_BYTE(0x00); + + // gamma curve 1 + ISSUE_CMD_BYTE(0xE0); + ISSUE_DATA_BYTE(0x0F); + ISSUE_DATA_BYTE(0x2F); + ISSUE_DATA_BYTE(0x2C); + ISSUE_DATA_BYTE(0x0B); + ISSUE_DATA_BYTE(0x0F); + ISSUE_DATA_BYTE(0x09); + ISSUE_DATA_BYTE(0x56); + ISSUE_DATA_BYTE(0xD9); + ISSUE_DATA_BYTE(0x4A); + ISSUE_DATA_BYTE(0x0B); + ISSUE_DATA_BYTE(0x14); + ISSUE_DATA_BYTE(0x05); + ISSUE_DATA_BYTE(0x0C); + ISSUE_DATA_BYTE(0x06); + ISSUE_DATA_BYTE(0x00); + + // gamma curve 2 + ISSUE_CMD_BYTE(0xE1); + ISSUE_DATA_BYTE(0x00); + ISSUE_DATA_BYTE(0x10); + ISSUE_DATA_BYTE(0x13); + ISSUE_DATA_BYTE(0x04); + ISSUE_DATA_BYTE(0x10); + ISSUE_DATA_BYTE(0x06); + ISSUE_DATA_BYTE(0x25); + ISSUE_DATA_BYTE(0x26); + ISSUE_DATA_BYTE(0x3B); + ISSUE_DATA_BYTE(0x04); + ISSUE_DATA_BYTE(0x0B); + ISSUE_DATA_BYTE(0x0A); + ISSUE_DATA_BYTE(0x33); + ISSUE_DATA_BYTE(0x39); + ISSUE_DATA_BYTE(0x0F); +} diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/panels/154a.h b/core/embed/trezorhal/stm32f4/display/st-7789/panels/154a.h new file mode 100644 index 000000000..410455abb --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/panels/154a.h @@ -0,0 +1,27 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _154A_H_ +#define _154A_H_ + +// ILI9341 IC controller + +void _154a_init_seq(void); + +#endif diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2411.c b/core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2411.c new file mode 100644 index 000000000..b81ee75bb --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2411.c @@ -0,0 +1,82 @@ + +#include "../display_io.h" + +void lx154a2411_gamma(void) { + // positive voltage correction + ISSUE_CMD_BYTE(0xE0); + ISSUE_DATA_BYTE(0xD0); + ISSUE_DATA_BYTE(0x03); + ISSUE_DATA_BYTE(0x08); + ISSUE_DATA_BYTE(0x0E); + ISSUE_DATA_BYTE(0x11); + ISSUE_DATA_BYTE(0x2B); + ISSUE_DATA_BYTE(0x3B); + ISSUE_DATA_BYTE(0x44); + ISSUE_DATA_BYTE(0x4C); + ISSUE_DATA_BYTE(0x2B); + ISSUE_DATA_BYTE(0x16); + ISSUE_DATA_BYTE(0x15); + ISSUE_DATA_BYTE(0x1E); + ISSUE_DATA_BYTE(0x21); + + // negative voltage correction + ISSUE_CMD_BYTE(0xE1); + ISSUE_DATA_BYTE(0xD0); + ISSUE_DATA_BYTE(0x03); + ISSUE_DATA_BYTE(0x08); + ISSUE_DATA_BYTE(0x0E); + ISSUE_DATA_BYTE(0x11); + ISSUE_DATA_BYTE(0x2B); + ISSUE_DATA_BYTE(0x3B); + ISSUE_DATA_BYTE(0x54); + ISSUE_DATA_BYTE(0x4C); + ISSUE_DATA_BYTE(0x2B); + ISSUE_DATA_BYTE(0x16); + ISSUE_DATA_BYTE(0x15); + ISSUE_DATA_BYTE(0x1E); + ISSUE_DATA_BYTE(0x21); +} + +void lx154a2411_init_seq(void) { + // most recent manual: + // https://www.newhavendisplay.com/appnotes/datasheets/LCDs/ST7789V.pdf + // TEON: Tearing Effect Line On; V-blanking only + ISSUE_CMD_BYTE(0x35); + ISSUE_DATA_BYTE(0x00); + + // COLMOD: Interface Pixel format; 65K color: 16-bit/pixel (RGB 5-6-5 bits + // input) + ISSUE_CMD_BYTE(0x3A); + ISSUE_DATA_BYTE(0x55); + + // CMD2EN: Commands in command table 2 can be executed when EXTC level is Low + ISSUE_CMD_BYTE(0xDF); + ISSUE_DATA_BYTE(0x5A); + ISSUE_DATA_BYTE(0x69); + ISSUE_DATA_BYTE(0x02); + ISSUE_DATA_BYTE(0x01); + + // LCMCTRL: LCM Control: XOR RGB setting + ISSUE_CMD_BYTE(0xC0); + ISSUE_DATA_BYTE(0x20); + + // GATECTRL: Gate Control; NL = 240 gate lines, first scan line is gate 80.; + // gate scan direction 319 -> 0 + ISSUE_CMD_BYTE(0xE4); + ISSUE_DATA_BYTE(0x1D); + ISSUE_DATA_BYTE(0x0A); + ISSUE_DATA_BYTE(0x11); + + // INVOFF (20h): Display Inversion Off + // INVON (21h): Display Inversion On + ISSUE_CMD_BYTE(0x20); + + // the above config is the most important and definitely necessary + + // PWCTRL1: Power Control 1 + ISSUE_CMD_BYTE(0xD0); + ISSUE_DATA_BYTE(0xA4); + ISSUE_DATA_BYTE(0xA1); + + lx154a2411_gamma(); +} diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2411.h b/core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2411.h new file mode 100644 index 000000000..347aa3b58 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2411.h @@ -0,0 +1,27 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LX154A2411_H_ +#define LX154A2411_H_ + +// ST7789_V IC controller +void lx154a2411_gamma(void); +void lx154a2411_init_seq(void); + +#endif diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2422.c b/core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2422.c new file mode 100644 index 000000000..d793e0952 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2422.c @@ -0,0 +1,159 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "lx154a2422.h" + +#include "../display_io.h" + +void lx154a2422_gamma(void) { + // positive voltage correction + ISSUE_CMD_BYTE(0xE0); + ISSUE_DATA_BYTE(0xD0); + ISSUE_DATA_BYTE(0x0A); + ISSUE_DATA_BYTE(0x10); + ISSUE_DATA_BYTE(0x0A); + ISSUE_DATA_BYTE(0x0A); + ISSUE_DATA_BYTE(0x26); + ISSUE_DATA_BYTE(0x36); + ISSUE_DATA_BYTE(0x34); + ISSUE_DATA_BYTE(0x4D); + ISSUE_DATA_BYTE(0x18); + ISSUE_DATA_BYTE(0x13); + ISSUE_DATA_BYTE(0x14); + ISSUE_DATA_BYTE(0x2F); + ISSUE_DATA_BYTE(0x34); + + // negative voltage correction + ISSUE_CMD_BYTE(0xE1); + ISSUE_DATA_BYTE(0xD0); + ISSUE_DATA_BYTE(0x0A); + ISSUE_DATA_BYTE(0x10); + ISSUE_DATA_BYTE(0x0A); + ISSUE_DATA_BYTE(0x09); + ISSUE_DATA_BYTE(0x26); + ISSUE_DATA_BYTE(0x36); + ISSUE_DATA_BYTE(0x53); + ISSUE_DATA_BYTE(0x4C); + ISSUE_DATA_BYTE(0x18); + ISSUE_DATA_BYTE(0x14); + ISSUE_DATA_BYTE(0x14); + ISSUE_DATA_BYTE(0x2F); + ISSUE_DATA_BYTE(0x34); +} + +void lx154a2422_init_seq(void) { + // most recent manual: + // https://www.newhavendisplay.com/appnotes/datasheets/LCDs/ST7789V.pdf + // TEON: Tearing Effect Line On; V-blanking only + ISSUE_CMD_BYTE(0x35); + ISSUE_DATA_BYTE(0x00); + + // COLMOD: Interface Pixel format; 65K color: 16-bit/pixel (RGB 5-6-5 bits + // input) + ISSUE_CMD_BYTE(0x3A); + ISSUE_DATA_BYTE(0x55); + + // CMD2EN: Commands in command table 2 can be executed when EXTC level is Low + ISSUE_CMD_BYTE(0xDF); + ISSUE_DATA_BYTE(0x5A); + ISSUE_DATA_BYTE(0x69); + ISSUE_DATA_BYTE(0x02); + ISSUE_DATA_BYTE(0x01); + + // LCMCTRL: LCM Control: XOR RGB setting + ISSUE_CMD_BYTE(0xC0); + ISSUE_DATA_BYTE(0x20); + + // GATECTRL: Gate Control; NL = 240 gate lines, first scan line is gate 80.; + // gate scan direction 319 -> 0 + ISSUE_CMD_BYTE(0xE4); + ISSUE_DATA_BYTE(0x1D); + ISSUE_DATA_BYTE(0x0A); + ISSUE_DATA_BYTE(0x11); + + // INVOFF (20h): Display Inversion Off + // INVON (21h): Display Inversion On + ISSUE_CMD_BYTE(0x21); + + // the above config is the most important and definitely necessary + + // PWCTRL1: Power Control 1 + ISSUE_CMD_BYTE(0xD0); + ISSUE_DATA_BYTE(0xA4); + ISSUE_DATA_BYTE(0xA1); + + lx154a2422_gamma(); +} + +void lx154a2422_rotate(int degrees, display_padding_t* padding) { + uint16_t shift = 0; + char BX = 0, BY = 0; + +#define RGB (1 << 3) +#define ML (1 << 4) // vertical refresh order +#define MH (1 << 2) // horizontal refresh order +#define MV (1 << 5) +#define MX (1 << 6) +#define MY (1 << 7) + // MADCTL: Memory Data Access Control - reference: + // section 8.12 in the ST7789V manual + uint8_t display_command_parameter = 0; + switch (degrees) { + case 0: + display_command_parameter = 0; + BY = 0; + break; + case 90: + display_command_parameter = MV | MX | MH | ML; + BX = 1; + shift = 1; + break; + case 180: + display_command_parameter = MX | MY | MH | ML; + BY = 0; + shift = 1; + break; + case 270: + display_command_parameter = MV | MY; + BX = 1; + break; + } + + ISSUE_CMD_BYTE(0x36); + ISSUE_DATA_BYTE(display_command_parameter); + + if (shift) { + // GATECTRL: Gate Control; NL = 240 gate lines, first scan line is + // gate 80.; gate scan direction 319 -> 0 + ISSUE_CMD_BYTE(0xE4); + ISSUE_DATA_BYTE(0x1D); + ISSUE_DATA_BYTE(0x00); + ISSUE_DATA_BYTE(0x11); + } else { + // GATECTRL: Gate Control; NL = 240 gate lines, first scan line is + // gate 80.; gate scan direction 319 -> 0 + ISSUE_CMD_BYTE(0xE4); + ISSUE_DATA_BYTE(0x1D); + ISSUE_DATA_BYTE(0x0A); + ISSUE_DATA_BYTE(0x11); + } + + padding->x = BX ? (320 - DISPLAY_RESY) : 0; + padding->y = BY ? (320 - DISPLAY_RESY) : 0; +} diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2422.h b/core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2422.h new file mode 100644 index 000000000..ba726ba1f --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2422.h @@ -0,0 +1,29 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LX154A2422_H_ +#define LX154A2422_H_ + +#include "../display_panel.h" + +void lx154a2422_init_seq(void); +void lx154a2422_gamma(void); +void lx154a2422_rotate(int degrees, display_padding_t* padding); + +#endif diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/panels/tf15411a.c b/core/embed/trezorhal/stm32f4/display/st-7789/panels/tf15411a.c new file mode 100644 index 000000000..155bf017a --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/panels/tf15411a.c @@ -0,0 +1,174 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "tf15411a.h" +#include "../display_io.h" + +void tf15411a_init_seq(void) { + // Inter Register Enable1 + ISSUE_CMD_BYTE(0xFE); + + // Inter Register Enable2 + ISSUE_CMD_BYTE(0xEF); + + // TEON: Tearing Effect Line On; V-blanking only + ISSUE_CMD_BYTE(0x35); + ISSUE_DATA_BYTE(0x00); + + // COLMOD: Interface Pixel format; 65K color: 16-bit/pixel (RGB 5-6-5 bits + // input) + ISSUE_CMD_BYTE(0x3A); + ISSUE_DATA_BYTE(0x55); + + // Frame Rate + // ISSUE_CMD_BYTE(0xE8); ISSUE_DATA_BYTE(0x12); ISSUE_DATA_BYTE(0x00); + + // Power Control 2 + ISSUE_CMD_BYTE(0xC3); + ISSUE_DATA_BYTE(0x27); + + // Power Control 3 + ISSUE_CMD_BYTE(0xC4); + ISSUE_DATA_BYTE(0x18); + + // Power Control 4 + ISSUE_CMD_BYTE(0xC9); + ISSUE_DATA_BYTE(0x1F); + + ISSUE_CMD_BYTE(0xC5); + ISSUE_DATA_BYTE(0x0F); + + ISSUE_CMD_BYTE(0xC6); + ISSUE_DATA_BYTE(0x00); + + ISSUE_CMD_BYTE(0xC7); + ISSUE_DATA_BYTE(0x10); + + ISSUE_CMD_BYTE(0xC8); + ISSUE_DATA_BYTE(0x01); + + ISSUE_CMD_BYTE(0xFF); + ISSUE_DATA_BYTE(0x62); + + ISSUE_CMD_BYTE(0x99); + ISSUE_DATA_BYTE(0x3E); + + ISSUE_CMD_BYTE(0x9D); + ISSUE_DATA_BYTE(0x4B); + + ISSUE_CMD_BYTE(0x8E); + ISSUE_DATA_BYTE(0x0F); + + // SET_GAMMA1 + ISSUE_CMD_BYTE(0xF0); + ISSUE_DATA_BYTE(0x8F); + ISSUE_DATA_BYTE(0x1B); + ISSUE_DATA_BYTE(0x05); + ISSUE_DATA_BYTE(0x06); + ISSUE_DATA_BYTE(0x07); + ISSUE_DATA_BYTE(0x42); + + // SET_GAMMA3 + ISSUE_CMD_BYTE(0xF2); + ISSUE_DATA_BYTE(0x5C); + ISSUE_DATA_BYTE(0x1F); + ISSUE_DATA_BYTE(0x12); + ISSUE_DATA_BYTE(0x10); + ISSUE_DATA_BYTE(0x07); + ISSUE_DATA_BYTE(0x43); + + // SET_GAMMA2 + ISSUE_CMD_BYTE(0xF1); + ISSUE_DATA_BYTE(0x59); + ISSUE_DATA_BYTE(0xCF); + ISSUE_DATA_BYTE(0xCF); + ISSUE_DATA_BYTE(0x35); + ISSUE_DATA_BYTE(0x37); + ISSUE_DATA_BYTE(0x8F); + + // SET_GAMMA4 + ISSUE_CMD_BYTE(0xF3); + ISSUE_DATA_BYTE(0x58); + ISSUE_DATA_BYTE(0xCF); + ISSUE_DATA_BYTE(0xCF); + ISSUE_DATA_BYTE(0x35); + ISSUE_DATA_BYTE(0x37); + ISSUE_DATA_BYTE(0x8F); +} + +void tf15411a_rotate(int degrees, display_padding_t* padding) { + uint16_t shift = 0; + char BX = 0, BY = 0; + +#define RGB (1 << 3) +#define ML (1 << 4) // vertical refresh order +#define MH (1 << 2) // horizontal refresh order +#define MV (1 << 5) +#define MX (1 << 6) +#define MY (1 << 7) + // MADCTL: Memory Data Access Control - reference: + // section 9.3 in the ILI9341 manual + // section 6.2.18 in the GC9307 manual + // section 8.12 in the ST7789V manual + uint8_t display_command_parameter = 0; + switch (degrees) { + case 0: + display_command_parameter = 0; + BY = 1; + break; + case 90: + display_command_parameter = MV | MX | MH | ML; + BX = 0; + shift = 1; + break; + case 180: + display_command_parameter = MX | MY | MH | ML; + BY = 1; + shift = 1; + break; + case 270: + display_command_parameter = MV | MY; + BX = 0; + break; + } + + display_command_parameter ^= RGB | MY; // XOR RGB and MY settings + + ISSUE_CMD_BYTE(0x36); + ISSUE_DATA_BYTE(display_command_parameter); + + if (shift) { + // GATECTRL: Gate Control; NL = 240 gate lines, first scan line is + // gate 80.; gate scan direction 319 -> 0 + ISSUE_CMD_BYTE(0xE4); + ISSUE_DATA_BYTE(0x1D); + ISSUE_DATA_BYTE(0x00); + ISSUE_DATA_BYTE(0x11); + } else { + // GATECTRL: Gate Control; NL = 240 gate lines, first scan line is + // gate 80.; gate scan direction 319 -> 0 + ISSUE_CMD_BYTE(0xE4); + ISSUE_DATA_BYTE(0x1D); + ISSUE_DATA_BYTE(0x0A); + ISSUE_DATA_BYTE(0x11); + } + + padding->x = BX ? (320 - DISPLAY_RESY) : 0; + padding->y = BY ? (320 - DISPLAY_RESY) : 0; +} diff --git a/core/embed/trezorhal/stm32f4/display/st-7789/panels/tf15411a.h b/core/embed/trezorhal/stm32f4/display/st-7789/panels/tf15411a.h new file mode 100644 index 000000000..33988e0a7 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/st-7789/panels/tf15411a.h @@ -0,0 +1,30 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TF15411A_H_ +#define TF15411A_H_ + +#include "../display_panel.h" + +// GC9307 IC controller + +void tf15411a_init_seq(void); +void tf15411a_rotate(int degrees, display_padding_t* padding); + +#endif diff --git a/core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/display_driver.c b/core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/display_driver.c new file mode 100644 index 000000000..a62ea9239 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/display_driver.c @@ -0,0 +1,154 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include TREZOR_BOARD +#include STM32_HAL_H + +#include "display_internal.h" +#include "ili9341_spi.h" +#include "xdisplay.h" + +#if (DISPLAY_RESX != 240) || (DISPLAY_RESY != 320) +#error "Incompatible display resolution" +#endif + +// Display driver context. +typedef struct { + // Pointer to the frame buffer + uint16_t *framebuf; + // Current display orientation (0, 90, 180, 270) + int orientation_angle; + // Current backlight level ranging from 0 to 255 + int backlight_level; +} display_driver_t; + +// Display driver instance +static display_driver_t g_display_driver; + +void display_init(void) { + display_driver_t *drv = &g_display_driver; + memset(drv, 0, sizeof(display_driver_t)); + drv->framebuf = (uint16_t *)FRAME_BUFFER_ADDR; + + // Initialize LTDC controller + BSP_LCD_Init(); + // Initialize external display controller + ili9341_init(); +} + +void display_reinit(void) { + display_driver_t *drv = &g_display_driver; + memset(drv, 0, sizeof(display_driver_t)); + drv->framebuf = (uint16_t *)FRAME_BUFFER_ADDR; +} + +void display_finish_actions(void) { + // Not used and intentionally left empty +} + +int display_set_backlight(int level) { + display_driver_t *drv = &g_display_driver; + + // Just emulation, not doing anything + drv->backlight_level = level; + return level; +} + +int display_get_backlight(void) { + display_driver_t *drv = &g_display_driver; + + return drv->backlight_level; +} + +int display_set_orientation(int angle) { + display_driver_t *drv = &g_display_driver; + + if (angle == 0 || angle == 90 || angle == 180 || angle == 270) { + // Just emulation, not doing anything + drv->orientation_angle = angle; + } + + return drv->orientation_angle; +} + +int display_get_orientation(void) { + display_driver_t *drv = &g_display_driver; + + return drv->orientation_angle; +} + +display_fb_info_t display_get_frame_buffer(void) { + display_driver_t *drv = &g_display_driver; + + display_fb_info_t fb = { + .ptr = (void *)drv->framebuf, + .stride = DISPLAY_RESX * sizeof(uint16_t), + }; + + return fb; +} + +void display_refresh(void) { + // Do nothing as using just a single frame buffer +} + +void display_set_compatible_settings() {} + +void display_fill(const gl_bitblt_t *bb) { + display_driver_t *drv = &g_display_driver; + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = drv->framebuf + (DISPLAY_RESX * bb_new.dst_y); + bb_new.dst_stride = DISPLAY_RESX * sizeof(uint16_t); + + gl_rgb565_fill(&bb_new); +} + +void display_copy_rgb565(const gl_bitblt_t *bb) { + display_driver_t *drv = &g_display_driver; + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = drv->framebuf + (DISPLAY_RESX * bb_new.dst_y); + bb_new.dst_stride = DISPLAY_RESX * sizeof(uint16_t); + + gl_rgb565_copy_rgb565(&bb_new); +} + +void display_copy_mono1p(const gl_bitblt_t *bb) { + display_driver_t *drv = &g_display_driver; + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = drv->framebuf + (DISPLAY_RESX * bb_new.dst_y); + bb_new.dst_stride = DISPLAY_RESX * sizeof(uint16_t); + + gl_rgb565_copy_mono1p(&bb_new); +} + +void display_copy_mono4(const gl_bitblt_t *bb) { + display_driver_t *drv = &g_display_driver; + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = drv->framebuf + (DISPLAY_RESX * bb_new.dst_y); + bb_new.dst_stride = DISPLAY_RESX * sizeof(uint16_t); + + gl_rgb565_copy_mono4(&bb_new); +} diff --git a/core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/display_internal.h b/core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/display_internal.h new file mode 100644 index 000000000..c19c63a65 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/display_internal.h @@ -0,0 +1,36 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TREZORHAL_DISPLAY_INTERNAL_H +#define TREZORHAL_DISPLAY_INTERNAL_H + +#include TREZOR_BOARD +#include STM32_HAL_H + +#include "sdram.h" + +// Frame buffer address in external SDRAM +#define FRAME_BUFFER_ADDR ((uint32_t)SDRAM_DEVICE_ADDR) +// Frame buffer size (16-bit per pixel RGB565) +#define FRAME_BUFFER_SIZE (DISPLAY_RESX * DISPLAY_RESY * 2) + +// Initializes LTDC controller and I/O pins +void BSP_LCD_Init(void); + +#endif // TREZORHAL_DISPLAY_INTERNAL_H diff --git a/core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/display_ltdc.c b/core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/display_ltdc.c new file mode 100644 index 000000000..df3a752c3 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/display_ltdc.c @@ -0,0 +1,291 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include TREZOR_BOARD +#include STM32_HAL_H + +#include "display_internal.h" +#include "ili9341_spi.h" +#include "xdisplay.h" + +#define MAX_LAYER_NUMBER 2 + +LTDC_HandleTypeDef LtdcHandler; +static RCC_PeriphCLKInitTypeDef PeriphClkInitStruct; + +/* Default LCD configuration with LCD Layer 1 */ +uint32_t ActiveLayer = 0; + +/** + * @brief Initializes the LCD layers. + * @param LayerIndex: the layer foreground or background. + * @param FB_Address: the layer frame buffer. + */ +void BSP_LCD_LayerDefaultInit(uint16_t LayerIndex, uint32_t FB_Address) { + LTDC_LayerCfgTypeDef Layercfg; + + /* Layer Init */ + Layercfg.WindowX0 = 0; + Layercfg.WindowX1 = DISPLAY_RESX; + Layercfg.WindowY0 = 0; + Layercfg.WindowY1 = DISPLAY_RESY; + Layercfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565; + Layercfg.FBStartAdress = FB_Address; + Layercfg.Alpha = 255; + Layercfg.Alpha0 = 0; + Layercfg.Backcolor.Blue = 0; + Layercfg.Backcolor.Green = 0; + Layercfg.Backcolor.Red = 0; + Layercfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA; + Layercfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA; + Layercfg.ImageWidth = DISPLAY_RESX; + Layercfg.ImageHeight = DISPLAY_RESY; + + HAL_LTDC_ConfigLayer(&LtdcHandler, &Layercfg, LayerIndex); + + // DrawProp[LayerIndex].BackColor = LCD_COLOR_WHITE; + // DrawProp[LayerIndex].pFont = &Font24; + // DrawProp[LayerIndex].TextColor = LCD_COLOR_BLACK; + + /* Dithering activation */ + HAL_LTDC_EnableDither(&LtdcHandler); +} + +/** + * @brief Selects the LCD Layer. + * @param LayerIndex: the Layer foreground or background. + */ +void BSP_LCD_SelectLayer(uint32_t LayerIndex) { ActiveLayer = LayerIndex; } + +/** + * @brief Sets a LCD Layer visible. + * @param LayerIndex: the visible Layer. + * @param state: new state of the specified layer. + * This parameter can be: ENABLE or DISABLE. + */ +void BSP_LCD_SetLayerVisible(uint32_t LayerIndex, FunctionalState state) { + if (state == ENABLE) { + __HAL_LTDC_LAYER_ENABLE(&LtdcHandler, LayerIndex); + } else { + __HAL_LTDC_LAYER_DISABLE(&LtdcHandler, LayerIndex); + } + __HAL_LTDC_RELOAD_CONFIG(&LtdcHandler); +} + +/** + * @brief Sets an LCD Layer visible without reloading. + * @param LayerIndex: Visible Layer + * @param State: New state of the specified layer + * This parameter can be one of the following values: + * @arg ENABLE + * @arg DISABLE + * @retval None + */ +void BSP_LCD_SetLayerVisible_NoReload(uint32_t LayerIndex, + FunctionalState State) { + if (State == ENABLE) { + __HAL_LTDC_LAYER_ENABLE(&LtdcHandler, LayerIndex); + } else { + __HAL_LTDC_LAYER_DISABLE(&LtdcHandler, LayerIndex); + } + /* Do not Sets the Reload */ +} + +/** + * @brief Configures the Transparency. + * @param LayerIndex: the Layer foreground or background. + * @param Transparency: the Transparency, + * This parameter must range from 0x00 to 0xFF. + */ +void BSP_LCD_SetTransparency(uint32_t LayerIndex, uint8_t Transparency) { + HAL_LTDC_SetAlpha(&LtdcHandler, Transparency, LayerIndex); +} + +/** + * @brief Configures the transparency without reloading. + * @param LayerIndex: Layer foreground or background. + * @param Transparency: Transparency + * This parameter must be a number between Min_Data = 0x00 and + * Max_Data = 0xFF + * @retval None + */ +void BSP_LCD_SetTransparency_NoReload(uint32_t LayerIndex, + uint8_t Transparency) { + HAL_LTDC_SetAlpha_NoReload(&LtdcHandler, Transparency, LayerIndex); +} + +/** + * @brief Sets a LCD layer frame buffer address. + * @param LayerIndex: specifies the Layer foreground or background + * @param Address: new LCD frame buffer value + */ +void BSP_LCD_SetLayerAddress(uint32_t LayerIndex, uint32_t Address) { + HAL_LTDC_SetAddress(&LtdcHandler, Address, LayerIndex); +} + +/** + * @brief Sets an LCD layer frame buffer address without reloading. + * @param LayerIndex: Layer foreground or background + * @param Address: New LCD frame buffer value + * @retval None + */ +void BSP_LCD_SetLayerAddress_NoReload(uint32_t LayerIndex, uint32_t Address) { + HAL_LTDC_SetAddress_NoReload(&LtdcHandler, Address, LayerIndex); +} + +void BSP_LCD_Init(void) { + GPIO_InitTypeDef GPIO_InitStructure = {0}; + + /* Enable the LTDC and DMA2D Clock */ + __HAL_RCC_LTDC_CLK_ENABLE(); + __HAL_RCC_DMA2D_CLK_ENABLE(); + + /* Enable GPIOs clock */ + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_GPIOD_CLK_ENABLE(); + __HAL_RCC_GPIOF_CLK_ENABLE(); + __HAL_RCC_GPIOG_CLK_ENABLE(); + + /* GPIOs Configuration */ + /* + +------------------------+-----------------------+----------------------------+ + + LCD pins assignment + + +------------------------+-----------------------+----------------------------+ + | LCD_TFT R2 <-> PC.10 | LCD_TFT G2 <-> PA.06 | LCD_TFT B2 <-> PD.06 | | + LCD_TFT R3 <-> PB.00 | LCD_TFT G3 <-> PG.10 | LCD_TFT B3 <-> PG.11 | + | LCD_TFT R4 <-> PA.11 | LCD_TFT G4 <-> PB.10 | LCD_TFT B4 <-> PG.12 | | + LCD_TFT R5 <-> PA.12 | LCD_TFT G5 <-> PB.11 | LCD_TFT B5 <-> PA.03 | + | LCD_TFT R6 <-> PB.01 | LCD_TFT G6 <-> PC.07 | LCD_TFT B6 <-> PB.08 | | + LCD_TFT R7 <-> PG.06 | LCD_TFT G7 <-> PD.03 | LCD_TFT B7 <-> PB.09 | + ------------------------------------------------------------------------------- + | LCD_TFT HSYNC <-> PC.06 | LCDTFT VSYNC <-> PA.04 | + | LCD_TFT CLK <-> PG.07 | LCD_TFT DE <-> PF.10 | + ----------------------------------------------------- + */ + + /* GPIOA configuration */ + GPIO_InitStructure.Pin = + GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_6 | GPIO_PIN_11 | GPIO_PIN_12; + GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FAST; + GPIO_InitStructure.Alternate = GPIO_AF14_LTDC; + HAL_GPIO_Init(GPIOA, &GPIO_InitStructure); + + /* GPIOB configuration */ + GPIO_InitStructure.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11; + HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); + + /* GPIOC configuration */ + GPIO_InitStructure.Pin = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_10; + HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); + + /* GPIOD configuration */ + GPIO_InitStructure.Pin = GPIO_PIN_3 | GPIO_PIN_6; + HAL_GPIO_Init(GPIOD, &GPIO_InitStructure); + + /* GPIOF configuration */ + GPIO_InitStructure.Pin = GPIO_PIN_10; + HAL_GPIO_Init(GPIOF, &GPIO_InitStructure); + + /* GPIOG configuration */ + GPIO_InitStructure.Pin = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_11; + HAL_GPIO_Init(GPIOG, &GPIO_InitStructure); + + /* GPIOB configuration */ + GPIO_InitStructure.Pin = GPIO_PIN_0 | GPIO_PIN_1; + GPIO_InitStructure.Alternate = GPIO_AF9_LTDC; + HAL_GPIO_Init(GPIOB, &GPIO_InitStructure); + + /* GPIOG configuration */ + GPIO_InitStructure.Pin = GPIO_PIN_10 | GPIO_PIN_12; + HAL_GPIO_Init(GPIOG, &GPIO_InitStructure); + + /* On STM32F429I-DISCO, it is not possible to read ILI9341 ID because */ + /* PIN EXTC is not connected to VDD and then LCD_READ_ID4 is not accessible. + */ + /* In this case, ReadID function is bypassed.*/ + /*if(ili9341_drv.ReadID() == ILI9341_ID)*/ + + /* LTDC Configuration ----------------------------------------------------*/ + LtdcHandler.Instance = LTDC; + + /* Timing configuration (Typical configuration from ILI9341 datasheet) + HSYNC=10 (9+1) + HBP=20 (29-10+1) + ActiveW=240 (269-20-10+1) + HFP=10 (279-240-20-10+1) + + VSYNC=2 (1+1) + VBP=2 (3-2+1) + ActiveH=320 (323-2-2+1) + VFP=4 (327-320-2-2+1) + */ + + /* Configure horizontal synchronization width */ + LtdcHandler.Init.HorizontalSync = ILI9341_HSYNC; + /* Configure vertical synchronization height */ + LtdcHandler.Init.VerticalSync = ILI9341_VSYNC; + /* Configure accumulated horizontal back porch */ + LtdcHandler.Init.AccumulatedHBP = ILI9341_HBP; + /* Configure accumulated vertical back porch */ + LtdcHandler.Init.AccumulatedVBP = ILI9341_VBP; + /* Configure accumulated active width */ + LtdcHandler.Init.AccumulatedActiveW = 269; + /* Configure accumulated active height */ + LtdcHandler.Init.AccumulatedActiveH = 323; + /* Configure total width */ + LtdcHandler.Init.TotalWidth = 279; + /* Configure total height */ + LtdcHandler.Init.TotalHeigh = 327; + + /* Configure R,G,B component values for LCD background color */ + LtdcHandler.Init.Backcolor.Red = 0; + LtdcHandler.Init.Backcolor.Blue = 0; + LtdcHandler.Init.Backcolor.Green = 0; + + /* LCD clock configuration */ + /* PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz */ + /* PLLSAI_VCO Output = PLLSAI_VCO Input * PLLSAIN = 192 Mhz */ + /* PLLLCDCLK = PLLSAI_VCO Output/PLLSAIR = 192/4 = 48 Mhz */ + /* LTDC clock frequency = PLLLCDCLK / LTDC_PLLSAI_DIVR_8 = 48/4 = 6Mhz */ + PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC; + PeriphClkInitStruct.PLLSAI.PLLSAIN = 192; + PeriphClkInitStruct.PLLSAI.PLLSAIR = 4; + PeriphClkInitStruct.PLLSAIDivR = RCC_PLLSAIDIVR_8; + HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct); + + /* Polarity */ + LtdcHandler.Init.HSPolarity = LTDC_HSPOLARITY_AL; + LtdcHandler.Init.VSPolarity = LTDC_VSPOLARITY_AL; + LtdcHandler.Init.DEPolarity = LTDC_DEPOLARITY_AL; + LtdcHandler.Init.PCPolarity = LTDC_PCPOLARITY_IPC; + + HAL_LTDC_Init(&LtdcHandler); + + /* Initialize the LCD Layers */ + BSP_LCD_LayerDefaultInit(1, FRAME_BUFFER_ADDR); + + memset((void *)FRAME_BUFFER_ADDR, 0, FRAME_BUFFER_SIZE); +} diff --git a/core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/ili9341_spi.c b/core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/ili9341_spi.c new file mode 100644 index 000000000..309fb3f0d --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/stm32f429i-disc1/ili9341_spi.c @@ -0,0 +1,512 @@ + + +#include +#include TREZOR_BOARD +#include "ili9341_spi.h" +#include STM32_HAL_H + +/** + * @brief ILI9341 chip IDs + */ +#define ILI9341_ID 0x9341 + +/** + * @brief ILI9341 Size + */ +#define ILI9341_LCD_PIXEL_WIDTH ((uint16_t)240) +#define ILI9341_LCD_PIXEL_HEIGHT ((uint16_t)320) + +/** + * @brief ILI9341 Timing + */ +/* Timing configuration (Typical configuration from ILI9341 datasheet) + HSYNC=10 (9+1) + HBP=20 (29-10+1) + ActiveW=240 (269-20-10+1) + HFP=10 (279-240-20-10+1) + + VSYNC=2 (1+1) + VBP=2 (3-2+1) + ActiveH=320 (323-2-2+1) + VFP=4 (327-320-2-2+1) +*/ + +/** + * @brief ILI9341 Registers + */ + +/* Level 1 Commands */ +#define LCD_SWRESET 0x01 /* Software Reset */ +#define LCD_READ_DISPLAY_ID 0x04 /* Read display identification information */ +#define LCD_RDDST 0x09 /* Read Display Status */ +#define LCD_RDDPM 0x0A /* Read Display Power Mode */ +#define LCD_RDDMADCTL 0x0B /* Read Display MADCTL */ +#define LCD_RDDCOLMOD 0x0C /* Read Display Pixel Format */ +#define LCD_RDDIM 0x0D /* Read Display Image Format */ +#define LCD_RDDSM 0x0E /* Read Display Signal Mode */ +#define LCD_RDDSDR 0x0F /* Read Display Self-Diagnostic Result */ +#define LCD_SPLIN 0x10 /* Enter Sleep Mode */ +#define LCD_SLEEP_OUT 0x11 /* Sleep out register */ +#define LCD_PTLON 0x12 /* Partial Mode ON */ +#define LCD_NORMAL_MODE_ON 0x13 /* Normal Display Mode ON */ +#define LCD_DINVOFF 0x20 /* Display Inversion OFF */ +#define LCD_DINVON 0x21 /* Display Inversion ON */ +#define LCD_GAMMA 0x26 /* Gamma register */ +#define LCD_DISPLAY_OFF 0x28 /* Display off register */ +#define LCD_DISPLAY_ON 0x29 /* Display on register */ +#define LCD_COLUMN_ADDR 0x2A /* Colomn address register */ +#define LCD_PAGE_ADDR 0x2B /* Page address register */ +#define LCD_GRAM 0x2C /* GRAM register */ +#define LCD_RGBSET 0x2D /* Color SET */ +#define LCD_RAMRD 0x2E /* Memory Read */ +#define LCD_PLTAR 0x30 /* Partial Area */ +#define LCD_VSCRDEF 0x33 /* Vertical Scrolling Definition */ +#define LCD_TEOFF 0x34 /* Tearing Effect Line OFF */ +#define LCD_TEON 0x35 /* Tearing Effect Line ON */ +#define LCD_MAC 0x36 /* Memory Access Control register*/ +#define LCD_VSCRSADD 0x37 /* Vertical Scrolling Start Address */ +#define LCD_IDMOFF 0x38 /* Idle Mode OFF */ +#define LCD_IDMON 0x39 /* Idle Mode ON */ +#define LCD_PIXEL_FORMAT 0x3A /* Pixel Format register */ +#define LCD_WRITE_MEM_CONTINUE 0x3C /* Write Memory Continue */ +#define LCD_READ_MEM_CONTINUE 0x3E /* Read Memory Continue */ +#define LCD_SET_TEAR_SCANLINE 0x44 /* Set Tear Scanline */ +#define LCD_GET_SCANLINE 0x45 /* Get Scanline */ +#define LCD_WDB 0x51 /* Write Brightness Display register */ +#define LCD_RDDISBV 0x52 /* Read Display Brightness */ +#define LCD_WCD 0x53 /* Write Control Display register*/ +#define LCD_RDCTRLD 0x54 /* Read CTRL Display */ +#define LCD_WRCABC 0x55 /* Write Content Adaptive Brightness Control */ +#define LCD_RDCABC 0x56 /* Read Content Adaptive Brightness Control */ +#define LCD_WRITE_CABC 0x5E /* Write CABC Minimum Brightness */ +#define LCD_READ_CABC 0x5F /* Read CABC Minimum Brightness */ +#define LCD_READ_ID1 0xDA /* Read ID1 */ +#define LCD_READ_ID2 0xDB /* Read ID2 */ +#define LCD_READ_ID3 0xDC /* Read ID3 */ + +/* Level 2 Commands */ +#define LCD_RGB_INTERFACE 0xB0 /* RGB Interface Signal Control */ +#define LCD_FRMCTR1 0xB1 /* Frame Rate Control (In Normal Mode) */ +#define LCD_FRMCTR2 0xB2 /* Frame Rate Control (In Idle Mode) */ +#define LCD_FRMCTR3 0xB3 /* Frame Rate Control (In Partial Mode) */ +#define LCD_INVTR 0xB4 /* Display Inversion Control */ +#define LCD_BPC 0xB5 /* Blanking Porch Control register */ +#define LCD_DFC 0xB6 /* Display Function Control register */ +#define LCD_ETMOD 0xB7 /* Entry Mode Set */ +#define LCD_BACKLIGHT1 0xB8 /* Backlight Control 1 */ +#define LCD_BACKLIGHT2 0xB9 /* Backlight Control 2 */ +#define LCD_BACKLIGHT3 0xBA /* Backlight Control 3 */ +#define LCD_BACKLIGHT4 0xBB /* Backlight Control 4 */ +#define LCD_BACKLIGHT5 0xBC /* Backlight Control 5 */ +#define LCD_BACKLIGHT7 0xBE /* Backlight Control 7 */ +#define LCD_BACKLIGHT8 0xBF /* Backlight Control 8 */ +#define LCD_POWER1 0xC0 /* Power Control 1 register */ +#define LCD_POWER2 0xC1 /* Power Control 2 register */ +#define LCD_VCOM1 0xC5 /* VCOM Control 1 register */ +#define LCD_VCOM2 0xC7 /* VCOM Control 2 register */ +#define LCD_NVMWR 0xD0 /* NV Memory Write */ +#define LCD_NVMPKEY 0xD1 /* NV Memory Protection Key */ +#define LCD_RDNVM 0xD2 /* NV Memory Status Read */ +#define LCD_READ_ID4 0xD3 /* Read ID4 */ +#define LCD_PGAMMA 0xE0 /* Positive Gamma Correction register */ +#define LCD_NGAMMA 0xE1 /* Negative Gamma Correction register */ +#define LCD_DGAMCTRL1 0xE2 /* Digital Gamma Control 1 */ +#define LCD_DGAMCTRL2 0xE3 /* Digital Gamma Control 2 */ +#define LCD_INTERFACE 0xF6 /* Interface control register */ + +/* Extend register commands */ +#define LCD_POWERA 0xCB /* Power control A register */ +#define LCD_POWERB 0xCF /* Power control B register */ +#define LCD_DTCA 0xE8 /* Driver timing control A */ +#define LCD_DTCB 0xEA /* Driver timing control B */ +#define LCD_POWER_SEQ 0xED /* Power on sequence register */ +#define LCD_3GAMMA_EN 0xF2 /* 3 Gamma enable register */ +#define LCD_PRC 0xF7 /* Pump ratio control register */ + +/* Size of read registers */ +#define LCD_READ_ID4_SIZE 3 /* Size of Read ID4 */ + +/*############################### SPIx #######################################*/ +#define DISCOVERY_SPIx SPI5 +#define DISCOVERY_SPIx_CLK_ENABLE() __HAL_RCC_SPI5_CLK_ENABLE() +#define DISCOVERY_SPIx_GPIO_PORT GPIOF /* GPIOF */ +#define DISCOVERY_SPIx_AF GPIO_AF5_SPI5 +#define DISCOVERY_SPIx_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE() +#define DISCOVERY_SPIx_GPIO_CLK_DISABLE() __HAL_RCC_GPIOF_CLK_DISABLE() +#define DISCOVERY_SPIx_SCK_PIN GPIO_PIN_7 /* PF.07 */ +#define DISCOVERY_SPIx_MISO_PIN GPIO_PIN_8 /* PF.08 */ +#define DISCOVERY_SPIx_MOSI_PIN GPIO_PIN_9 /* PF.09 */ +/* Maximum Timeout values for flags waiting loops. These timeouts are not based + on accurate values, they just guarantee that the application will not remain + stuck if the SPI communication is corrupted. + You may modify these timeout values depending on CPU frequency and + application conditions (interrupts routines ...). */ +#define SPIx_TIMEOUT_MAX ((uint32_t)0x1000) + +/*################################ LCD #######################################*/ +/* Chip Select macro definition */ +#define LCD_CS_LOW() \ + HAL_GPIO_WritePin(LCD_NCS_GPIO_PORT, LCD_NCS_PIN, GPIO_PIN_RESET) +#define LCD_CS_HIGH() \ + HAL_GPIO_WritePin(LCD_NCS_GPIO_PORT, LCD_NCS_PIN, GPIO_PIN_SET) + +/* Set WRX High to send data */ +#define LCD_WRX_LOW() \ + HAL_GPIO_WritePin(LCD_WRX_GPIO_PORT, LCD_WRX_PIN, GPIO_PIN_RESET) +#define LCD_WRX_HIGH() \ + HAL_GPIO_WritePin(LCD_WRX_GPIO_PORT, LCD_WRX_PIN, GPIO_PIN_SET) + +/* Set WRX High to send data */ +#define LCD_RDX_LOW() \ + HAL_GPIO_WritePin(LCD_RDX_GPIO_PORT, LCD_RDX_PIN, GPIO_PIN_RESET) +#define LCD_RDX_HIGH() \ + HAL_GPIO_WritePin(LCD_RDX_GPIO_PORT, LCD_RDX_PIN, GPIO_PIN_SET) + +/** + * @brief LCD Control pin + */ +#define LCD_NCS_PIN GPIO_PIN_2 +#define LCD_NCS_GPIO_PORT GPIOC +#define LCD_NCS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE() +#define LCD_NCS_GPIO_CLK_DISABLE() __HAL_RCC_GPIOC_CLK_DISABLE() +/** + * @} + */ +/** + * @brief LCD Command/data pin + */ +#define LCD_WRX_PIN GPIO_PIN_13 +#define LCD_WRX_GPIO_PORT GPIOD +#define LCD_WRX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE() +#define LCD_WRX_GPIO_CLK_DISABLE() __HAL_RCC_GPIOD_CLK_DISABLE() + +#define LCD_RDX_PIN GPIO_PIN_12 +#define LCD_RDX_GPIO_PORT GPIOD +#define LCD_RDX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE() +#define LCD_RDX_GPIO_CLK_DISABLE() __HAL_RCC_GPIOD_CLK_DISABLE() + +static SPI_HandleTypeDef SpiHandle; +uint32_t SpixTimeout = + SPIx_TIMEOUT_MAX; /*. + */ + +#include +#include + +#include TREZOR_BOARD +#include STM32_HAL_H + +#include "xdisplay.h" + +#if (DISPLAY_RESX != 128) || (DISPLAY_RESY != 128) +#error "Incompatible display resolution" +#endif + +// This file implements display driver for monochromatic display V-2864KSWEG01 +// with 128x128 resolution connected to CPU via SPI interface. +// +// This type of displayed was used on some preliminary dev kits for T3T1 (Trezor +// TS3) + +// Display driver context. +typedef struct { + // Frame buffer (8-bit Mono) + uint8_t framebuf[DISPLAY_RESX * DISPLAY_RESY]; + // Current display orientation (0 or 180) + int orientation_angle; + // Current backlight level ranging from 0 to 255 + int backlight_level; +} display_driver_t; + +// Display driver instance +static display_driver_t g_display_driver; + +// Macros to access display parallel interface + +// FSMC/FMC Bank 1 - NOR/PSRAM 1 +#define DISPLAY_MEMORY_BASE 0x60000000 +#define DISPLAY_MEMORY_PIN 16 + +#define CMD_ADDR *((__IO uint8_t *)((uint32_t)(DISPLAY_MEMORY_BASE))) +#define DATA_ADDR \ + (*((__IO uint8_t *)((uint32_t)(DISPLAY_MEMORY_BASE | \ + (1 << DISPLAY_MEMORY_PIN))))) + +#define ISSUE_CMD_BYTE(X) \ + do { \ + (CMD_ADDR) = (X); \ + } while (0) +#define ISSUE_DATA_BYTE(X) \ + do { \ + (DATA_ADDR) = (X); \ + } while (0) + +// --------------------------------------------------------------------------- +// Display controller registers +// --------------------------------------------------------------------------- + +#define OLED_SETCONTRAST 0x81 +#define OLED_DISPLAYALLON_RESUME 0xA4 +#define OLED_DISPLAYALLON 0xA5 +#define OLED_NORMALDISPLAY 0xA6 +#define OLED_INVERTDISPLAY 0xA7 +#define OLED_DISPLAYOFF 0xAE +#define OLED_DISPLAYON 0xAF +#define OLED_SETDISPLAYOFFSET 0xD3 +#define OLED_SETCOMPINS 0xDA +#define OLED_SETVCOMDETECT 0xDB +#define OLED_SETDISPLAYCLOCKDIV 0xD5 +#define OLED_SETPRECHARGE 0xD9 +#define OLED_SETMULTIPLEX 0xA8 +#define OLED_SETLOWCOLUMN 0x00 +#define OLED_SETHIGHCOLUMN 0x10 +#define OLED_SETSTARTLINE 0x40 +#define OLED_MEMORYMODE 0x20 +#define OLED_COMSCANINC 0xC0 +#define OLED_COMSCANDEC 0xC8 +#define OLED_SEGREMAP 0xA0 +#define OLED_CHARGEPUMP 0x8D + +// Dipslay specific initialization sequence +static const uint8_t ug_2828tswig01_init_seq[] = { + OLED_DISPLAYOFF, + // Divide ratio 0, Oscillator Frequency +0% + OLED_SETDISPLAYCLOCKDIV, 0x50, + // Set Memory Addressing Mode - page addressing mode + 0x20, + // Set Contrast Control Register + OLED_SETCONTRAST, 0x8F, + // Set DC-DC Setting: (Double Bytes Command) + 0xAD, 0x8A, + // Set Segment Re-map + OLED_SEGREMAP | 0x01, + // Set COM Output Scan Direction + OLED_COMSCANDEC, + // Set Display Start Line:(Double Bytes Command) + 0xDC, 0x00, + // Set Display Offset:(Double Bytes Command) + OLED_SETDISPLAYOFFSET, 0x00, + // Set Discharge / Pre-Charge Period (Double Bytes Command) + OLED_SETPRECHARGE, 0x22, + // Set VCOM Deselect Level + OLED_SETVCOMDETECT, 0x35, + // Set Multiplex Ratio + OLED_SETMULTIPLEX, 0x7F, + // Set Page + 0xB0, + // Reset column + OLED_SETLOWCOLUMN | 0, OLED_SETHIGHCOLUMN | 0, + + // Set Entire Display Off + // to be clear, this command turns off the function + // which turns entire display on, but it does not clear + // the data in display RAM + OLED_DISPLAYALLON_RESUME, + // Set Normal Display + OLED_NORMALDISPLAY}; + +static void __attribute__((unused)) display_sleep(void) { + // Display OFF + ISSUE_CMD_BYTE(OLED_DISPLAYOFF); + HAL_Delay(5); + // Vpp disable + HAL_GPIO_WritePin(GPIOD, GPIO_PIN_8, GPIO_PIN_RESET); +} + +static void display_resume(void) { + // Vpp enable + HAL_GPIO_WritePin(GPIOD, GPIO_PIN_8, GPIO_PIN_SET); + // 100 ms mandatory wait + HAL_Delay(100); + // Display ON + ISSUE_CMD_BYTE(OLED_DISPLAYON); +} + +// Sets the display cursor to the specific row and column +static void display_set_page_and_col(uint8_t page, uint8_t col) { + if (page < (DISPLAY_RESY / 8)) { + ISSUE_CMD_BYTE(0xB0 | (page & 0xF)); + + if (col < DISPLAY_RESX) { + ISSUE_CMD_BYTE(OLED_SETHIGHCOLUMN | ((col & 0x70) >> 4)); + ISSUE_CMD_BYTE(OLED_SETLOWCOLUMN | (col & 0x0F)); + } else { + // Reset column to start + ISSUE_CMD_BYTE(OLED_SETHIGHCOLUMN); + ISSUE_CMD_BYTE(OLED_SETLOWCOLUMN); + } + } +} + +#define COLLECT_ROW_BYTE(src) \ + (0 | (*(src + (0 * DISPLAY_RESX)) >= 128 ? 128 : 0) | \ + (*(src + (1 * DISPLAY_RESX)) >= 128 ? 64 : 0) | \ + (*(src + (2 * DISPLAY_RESX)) >= 128 ? 32 : 0) | \ + (*(src + (3 * DISPLAY_RESX)) >= 128 ? 16 : 0) | \ + (*(src + (4 * DISPLAY_RESX)) >= 128 ? 8 : 0) | \ + (*(src + (5 * DISPLAY_RESX)) >= 128 ? 4 : 0) | \ + (*(src + (6 * DISPLAY_RESX)) >= 128 ? 2 : 0) | \ + (*(src + (7 * DISPLAY_RESX)) >= 128 ? 1 : 0)) + +// Copies the framebuffer to the display via SPI interface +static void display_sync_with_fb(void) { + display_driver_t *drv = &g_display_driver; + + for (int y = 0; y < DISPLAY_RESY / 8; y++) { + display_set_page_and_col(y, 0); + uint8_t *src = &drv->framebuf[y * DISPLAY_RESX * 8]; + for (int x = 0; x < DISPLAY_RESX; x++) { + ISSUE_DATA_BYTE(COLLECT_ROW_BYTE(src)); + src++; + } + } +} + +static void display_init_controller(void) { + // LCD_RST/PC14 + HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET); + // wait 10 milliseconds. only needs to be low for 10 microseconds. + // my dev display module ties display reset and touch panel reset together. + // keeping this low for max(display_reset_time, ctpm_reset_time) aids + // development and does not hurt. + HAL_Delay(10); + + // LCD_RST/PC14 + HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_SET); + // max wait time for hardware reset is 120 milliseconds + // (experienced display flakiness using only 5ms wait before sending commands) + HAL_Delay(120); + + // Apply initialization sequence specific to this display controller/panel + for (int i = 0; i < sizeof(ug_2828tswig01_init_seq); i++) { + ISSUE_CMD_BYTE(ug_2828tswig01_init_seq[i]); + } + + // Resume the suspended display + display_resume(); + // Clear display internal framebuffer + display_sync_with_fb(); +} + +static void display_init_interface(void) { + // init peripherals + __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_GPIOD_CLK_ENABLE(); + __HAL_RCC_GPIOE_CLK_ENABLE(); + __HAL_RCC_FMC_CLK_ENABLE(); + + GPIO_InitTypeDef GPIO_InitStructure = {0}; + + // LCD_RST/PC14 + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Alternate = 0; + GPIO_InitStructure.Pin = GPIO_PIN_14; + // default to keeping display in reset + HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_RESET); + HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); + + // VPP Enable + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStructure.Pull = GPIO_PULLDOWN; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStructure.Alternate = 0; + GPIO_InitStructure.Pin = GPIO_PIN_8; + HAL_GPIO_WritePin(GPIOD, GPIO_PIN_8, GPIO_PIN_RESET); + HAL_GPIO_Init(GPIOD, &GPIO_InitStructure); + + GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStructure.Alternate = GPIO_AF12_FMC; + // LCD_CS/PD7 LCD_RS/PD11 LCD_RD/PD4 LCD_WR/PD5 + GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_11 | GPIO_PIN_4 | GPIO_PIN_5; + HAL_GPIO_Init(GPIOD, &GPIO_InitStructure); + // LCD_D0/PD14 LCD_D1/PD15 LCD_D2/PD0 LCD_D3/PD1 + GPIO_InitStructure.Pin = GPIO_PIN_14 | GPIO_PIN_15 | GPIO_PIN_0 | GPIO_PIN_1; + HAL_GPIO_Init(GPIOD, &GPIO_InitStructure); + // LCD_D4/PE7 LCD_D5/PE8 LCD_D6/PE9 LCD_D7/PE10 + GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10; + HAL_GPIO_Init(GPIOE, &GPIO_InitStructure); + + // Reference UM1725 "Description of STM32F4 HAL and LL drivers", + // section 64.2.1 "How to use this driver" + SRAM_HandleTypeDef display_sram = {0}; + display_sram.Instance = FMC_NORSRAM_DEVICE; + display_sram.Extended = FMC_NORSRAM_EXTENDED_DEVICE; + display_sram.Init.NSBank = FMC_NORSRAM_BANK1; + display_sram.Init.DataAddressMux = FMC_DATA_ADDRESS_MUX_DISABLE; + display_sram.Init.MemoryType = FMC_MEMORY_TYPE_SRAM; + display_sram.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_8; + display_sram.Init.BurstAccessMode = FMC_BURST_ACCESS_MODE_DISABLE; + display_sram.Init.WaitSignalPolarity = FMC_WAIT_SIGNAL_POLARITY_LOW; + display_sram.Init.WrapMode = FMC_WRAP_MODE_DISABLE; + display_sram.Init.WaitSignalActive = FMC_WAIT_TIMING_BEFORE_WS; + display_sram.Init.WriteOperation = FMC_WRITE_OPERATION_ENABLE; + display_sram.Init.WaitSignal = FMC_WAIT_SIGNAL_DISABLE; + display_sram.Init.ExtendedMode = FMC_EXTENDED_MODE_DISABLE; + display_sram.Init.AsynchronousWait = FMC_ASYNCHRONOUS_WAIT_DISABLE; + display_sram.Init.WriteBurst = FMC_WRITE_BURST_DISABLE; + display_sram.Init.ContinuousClock = FMC_CONTINUOUS_CLOCK_SYNC_ONLY; + display_sram.Init.PageSize = FMC_PAGE_SIZE_NONE; + + // reference RM0090 section 37.5 Table 259, 37.5.4, Mode 1 SRAM, and 37.5.6 + FMC_NORSRAM_TimingTypeDef normal_mode_timing = {0}; + normal_mode_timing.AddressSetupTime = 10; + normal_mode_timing.AddressHoldTime = 10; + normal_mode_timing.DataSetupTime = 10; + normal_mode_timing.BusTurnAroundDuration = 0; + normal_mode_timing.CLKDivision = 2; + normal_mode_timing.DataLatency = 2; + normal_mode_timing.AccessMode = FMC_ACCESS_MODE_A; + + HAL_SRAM_Init(&display_sram, &normal_mode_timing, NULL); +} + +void display_init(void) { + display_driver_t *drv = &g_display_driver; + memset(drv, 0, sizeof(display_driver_t)); + + // Initialize GPIO & FSMC controller + display_init_interface(); + // Initialize display controller + display_init_controller(); +} + +void display_reinit(void) { + display_driver_t *drv = &g_display_driver; + memset(drv, 0, sizeof(display_driver_t)); + + // !@# TODO backlight level?? +} + +void display_finish_actions(void) { + /// Not used and intentionally left empty +} + +int display_set_backlight(int level) { + display_driver_t *drv = &g_display_driver; + + if (level != drv->backlight_level) { + if (level >= 0 && level <= 255) { + drv->backlight_level = level; + // Set Contrast Control Register: (Double Bytes Command) + ISSUE_CMD_BYTE(OLED_SETCONTRAST); + ISSUE_CMD_BYTE(level & 0xFF); + } + } + + return drv->backlight_level; +} + +int display_get_backlight(void) { + display_driver_t *drv = &g_display_driver; + + return drv->backlight_level; +} + +int display_set_orientation(int angle) { + display_driver_t *drv = &g_display_driver; + + if (angle != drv->orientation_angle) { + if (angle == 0 || angle == 180) { + drv->orientation_angle = angle; + if (angle == 0) { + // Set Segment Re-map: (A0H - A1H) + ISSUE_CMD_BYTE(OLED_SEGREMAP | 0x01); + // Set COM Output Scan Direction + ISSUE_CMD_BYTE(OLED_COMSCANDEC); + } else { + // Set Segment Re-map: (A0H - A1H) + ISSUE_CMD_BYTE(OLED_SEGREMAP | 0x00); + // Set COM Output Scan Direction + ISSUE_CMD_BYTE(OLED_COMSCANINC); + } + } + } + + return drv->orientation_angle; +} + +int display_get_orientation(void) { + display_driver_t *drv = &g_display_driver; + + return drv->orientation_angle; +} + +display_fb_info_t display_get_frame_buffer(void) { + display_driver_t *drv = &g_display_driver; + + display_fb_info_t fb = { + .ptr = &drv->framebuf[0], + .stride = DISPLAY_RESX, + }; + + return fb; +} + +void display_refresh(void) { display_sync_with_fb(); } + +void display_set_compatible_settings() {} + +void display_fill(const gl_bitblt_t *bb) { + display_driver_t *drv = &g_display_driver; + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = &drv->framebuf[DISPLAY_RESX * bb_new.dst_y]; + bb_new.dst_stride = DISPLAY_RESX; + + mono8_fill(&bb_new); +} + +void display_copy_mono1p(const gl_bitblt_t *bb) { + display_driver_t *drv = &g_display_driver; + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = &drv->framebuf[DISPLAY_RESX * bb_new.dst_y]; + bb_new.dst_stride = DISPLAY_RESX; + + mono8_copy_mono1p(&bb_new); +} diff --git a/core/embed/trezorhal/stm32f4/display/vg-2864/display_driver.c b/core/embed/trezorhal/stm32f4/display/vg-2864/display_driver.c new file mode 100644 index 000000000..e1495a67b --- /dev/null +++ b/core/embed/trezorhal/stm32f4/display/vg-2864/display_driver.c @@ -0,0 +1,379 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include TREZOR_BOARD +#include STM32_HAL_H + +#include "xdisplay.h" + +#ifdef USE_CONSUMPTION_MASK +#include "consumption_mask.h" +#endif + +#if (DISPLAY_RESX != 128) || (DISPLAY_RESY != 64) +#error "Incompatible display resolution" +#endif + +// This file implements display driver for monochromatic display V-2864KSWEG01 +// with 128x64 resolution connected to CPU via SPI interface. +// +// This type of display is used with T3T1 model (Trezor TS3) + +// Display driver context. +typedef struct { + // SPI driver instance + SPI_HandleTypeDef spi; + // Frame buffer (8-bit Mono) + uint8_t framebuf[DISPLAY_RESX * DISPLAY_RESY]; + // Current display orientation (0 or 180) + int orientation_angle; + // Current backlight level ranging from 0 to 255 + int backlight_level; +} display_driver_t; + +// Display driver instance +static display_driver_t g_display_driver; + +// Display controller registers +#define OLED_SETCONTRAST 0x81 +#define OLED_DISPLAYALLON_RESUME 0xA4 +#define OLED_DISPLAYALLON 0xA5 +#define OLED_NORMALDISPLAY 0xA6 +#define OLED_INVERTDISPLAY 0xA7 +#define OLED_DISPLAYOFF 0xAE +#define OLED_DISPLAYON 0xAF +#define OLED_SETDISPLAYOFFSET 0xD3 +#define OLED_SETCOMPINS 0xDA +#define OLED_SETVCOMDETECT 0xDB +#define OLED_SETDISPLAYCLOCKDIV 0xD5 +#define OLED_SETPRECHARGE 0xD9 +#define OLED_SETMULTIPLEX 0xA8 +#define OLED_SETLOWCOLUMN 0x00 +#define OLED_SETHIGHCOLUMN 0x10 +#define OLED_SETSTARTLINE 0x40 +#define OLED_MEMORYMODE 0x20 +#define OLED_COMSCANINC 0xC0 +#define OLED_COMSCANDEC 0xC8 +#define OLED_SEGREMAP 0xA0 +#define OLED_CHARGEPUMP 0x8D + +// Display controller initialization sequence +static const uint8_t vg_2864ksweg01_init_seq[] = {OLED_DISPLAYOFF, + OLED_SETDISPLAYCLOCKDIV, + 0x80, + OLED_SETMULTIPLEX, + 0x3F, // 128x64 + OLED_SETDISPLAYOFFSET, + 0x00, + OLED_SETSTARTLINE | 0x00, + OLED_CHARGEPUMP, + 0x14, + OLED_MEMORYMODE, + 0x00, + OLED_SEGREMAP | 0x01, + OLED_COMSCANDEC, + OLED_SETCOMPINS, + 0x12, // 128x64 + OLED_SETCONTRAST, + 0xCF, + OLED_SETPRECHARGE, + 0xF1, + OLED_SETVCOMDETECT, + 0x40, + OLED_DISPLAYALLON_RESUME, + OLED_NORMALDISPLAY, + OLED_DISPLAYON}; + +// Configures SPI driver/controller +static bool display_init_spi(display_driver_t *drv) { + drv->spi.Instance = OLED_SPI; + drv->spi.State = HAL_SPI_STATE_RESET; + drv->spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; + drv->spi.Init.Direction = SPI_DIRECTION_2LINES; + drv->spi.Init.CLKPhase = SPI_PHASE_1EDGE; + drv->spi.Init.CLKPolarity = SPI_POLARITY_LOW; + drv->spi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; + drv->spi.Init.CRCPolynomial = 7; + drv->spi.Init.DataSize = SPI_DATASIZE_8BIT; + drv->spi.Init.FirstBit = SPI_FIRSTBIT_MSB; + drv->spi.Init.NSS = SPI_NSS_HARD_OUTPUT; + drv->spi.Init.TIMode = SPI_TIMODE_DISABLE; + drv->spi.Init.Mode = SPI_MODE_MASTER; + + return (HAL_OK == HAL_SPI_Init(&drv->spi)) ? true : false; +} + +// Sends specified number of bytes to the display via SPI interface +static void display_send_bytes(display_driver_t *drv, const uint8_t *data, + size_t len) { + volatile int32_t timeout = 1000; + for (int i = 0; i < timeout; i++) + ; + + if (HAL_OK != HAL_SPI_Transmit(&drv->spi, (uint8_t *)data, len, 1000)) { + // TODO: error + return; + } + while (HAL_SPI_STATE_READY != HAL_SPI_GetState(&drv->spi)) { + } +} + +#define COLLECT_ROW_BYTE(src) \ + (0 | (*(src + (0 * DISPLAY_RESX)) >= 128 ? 128 : 0) | \ + (*(src + (1 * DISPLAY_RESX)) >= 128 ? 64 : 0) | \ + (*(src + (2 * DISPLAY_RESX)) >= 128 ? 32 : 0) | \ + (*(src + (3 * DISPLAY_RESX)) >= 128 ? 16 : 0) | \ + (*(src + (4 * DISPLAY_RESX)) >= 128 ? 8 : 0) | \ + (*(src + (5 * DISPLAY_RESX)) >= 128 ? 4 : 0) | \ + (*(src + (6 * DISPLAY_RESX)) >= 128 ? 2 : 0) | \ + (*(src + (7 * DISPLAY_RESX)) >= 128 ? 1 : 0)) + +#define COLLECT_ROW_BYTE_REV(src) \ + (0 | (*(src + (0 * DISPLAY_RESX)) >= 128 ? 1 : 0) | \ + (*(src + (1 * DISPLAY_RESX)) >= 128 ? 2 : 0) | \ + (*(src + (2 * DISPLAY_RESX)) >= 128 ? 4 : 0) | \ + (*(src + (3 * DISPLAY_RESX)) >= 128 ? 8 : 0) | \ + (*(src + (4 * DISPLAY_RESX)) >= 128 ? 16 : 0) | \ + (*(src + (5 * DISPLAY_RESX)) >= 128 ? 32 : 0) | \ + (*(src + (6 * DISPLAY_RESX)) >= 128 ? 64 : 0) | \ + (*(src + (7 * DISPLAY_RESX)) >= 128 ? 128 : 0)) + +// Copies the framebuffer to the display via SPI interface +static void display_sync_with_fb(display_driver_t *drv) { + static const uint8_t cursor_set_seq[3] = {OLED_SETLOWCOLUMN | 0x00, + OLED_SETHIGHCOLUMN | 0x00, + OLED_SETSTARTLINE | 0x00}; + + // SPI select + HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET); + // Set the cursor to the screen top-left corner + display_send_bytes(drv, &cursor_set_seq[0], sizeof(cursor_set_seq)); + + // SPI deselect + HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET); + // Set to DATA + HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_SET); + // SPI select + HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET); + + // Send whole framebuffer to the display + + if (drv->orientation_angle == 0) { + for (int y = DISPLAY_RESY / 8 - 1; y >= 0; y--) { + uint8_t buff[DISPLAY_RESX]; + uint8_t *src = &drv->framebuf[y * DISPLAY_RESX * 8]; + + for (int x = DISPLAY_RESX - 1; x >= 0; x--) { + buff[x] = COLLECT_ROW_BYTE(src); + src++; + } + + if (HAL_OK != HAL_SPI_Transmit(&drv->spi, &buff[0], sizeof(buff), 1000)) { + // TODO: error + return; + } + } + } else { + for (int y = 0; y < DISPLAY_RESY / 8; y++) { + uint8_t buff[DISPLAY_RESX]; + uint8_t *src = &drv->framebuf[y * DISPLAY_RESX * 8]; + + for (int x = 0; x < DISPLAY_RESX; x++) { + buff[x] = COLLECT_ROW_BYTE_REV(src); + src++; + } + + if (HAL_OK != HAL_SPI_Transmit(&drv->spi, &buff[0], sizeof(buff), 1000)) { + // TODO: error + return; + } + } + } + + while (HAL_SPI_STATE_READY != HAL_SPI_GetState(&drv->spi)) { + } + + // SPI deselect + HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET); + // Set to CMD + HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_RESET); +} + +void display_init(void) { + display_driver_t *drv = &g_display_driver; + + memset(drv, 0, sizeof(display_driver_t)); + drv->backlight_level = 255; + + OLED_DC_CLK_ENA(); + OLED_CS_CLK_ENA(); + OLED_RST_CLK_ENA(); + OLED_SPI_SCK_CLK_ENA(); + OLED_SPI_MOSI_CLK_ENA(); + OLED_SPI_CLK_ENA(); + + GPIO_InitTypeDef GPIO_InitStructure; + + // Set GPIO for OLED display + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStructure.Alternate = 0; + GPIO_InitStructure.Pin = OLED_CS_PIN; + HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET); + HAL_GPIO_Init(OLED_CS_PORT, &GPIO_InitStructure); + GPIO_InitStructure.Pin = OLED_DC_PIN; + HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_RESET); + HAL_GPIO_Init(OLED_DC_PORT, &GPIO_InitStructure); + GPIO_InitStructure.Pin = OLED_RST_PIN; + HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_RESET); + HAL_GPIO_Init(OLED_RST_PORT, &GPIO_InitStructure); + + // Enable SPI 1 for OLED display + GPIO_InitStructure.Mode = GPIO_MODE_AF_PP; + GPIO_InitStructure.Pull = GPIO_NOPULL; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStructure.Alternate = OLED_SPI_AF; + GPIO_InitStructure.Pin = OLED_SPI_SCK_PIN; + HAL_GPIO_Init(OLED_SPI_SCK_PORT, &GPIO_InitStructure); + GPIO_InitStructure.Pin = OLED_SPI_MOSI_PIN; + HAL_GPIO_Init(OLED_SPI_MOSI_PORT, &GPIO_InitStructure); + + // Initialize SPI controller + display_init_spi(drv); + + // Set to CMD + HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_RESET); + // SPI deselect + HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET); + + // Reset the LCD + HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_SET); + HAL_Delay(1); + HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_RESET); + HAL_Delay(1); + HAL_GPIO_WritePin(OLED_RST_PORT, OLED_RST_PIN, GPIO_PIN_SET); + + // SPI select + HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_RESET); + // Send initialization command sequence + display_send_bytes(drv, &vg_2864ksweg01_init_seq[0], + sizeof(vg_2864ksweg01_init_seq)); + // SPI deselect + HAL_GPIO_WritePin(OLED_CS_PORT, OLED_CS_PIN, GPIO_PIN_SET); + + // Clear display internal framebuffer + display_sync_with_fb(drv); +} + +void display_reinit(void) { + display_driver_t *drv = &g_display_driver; + + memset(drv, 0, sizeof(display_driver_t)); + drv->backlight_level = 255; + + display_init_spi(drv); +} + +void display_finish_actions(void) { + /// Not used and intentionally left empty +} + +int display_set_backlight(int level) { + display_driver_t *drv = &g_display_driver; + + drv->backlight_level = 255; + return drv->backlight_level; +} + +int display_get_backlight(void) { + display_driver_t *drv = &g_display_driver; + + return drv->backlight_level; +} + +int display_set_orientation(int angle) { + display_driver_t *drv = &g_display_driver; + + if (angle != drv->orientation_angle) { + if (angle == 0 || angle == 180) { + drv->orientation_angle = angle; + display_sync_with_fb(drv); + } + } + + return drv->orientation_angle; +} + +int display_get_orientation(void) { + display_driver_t *drv = &g_display_driver; + + return drv->orientation_angle; +} + +display_fb_info_t display_get_frame_buffer(void) { + display_driver_t *drv = &g_display_driver; + + display_fb_info_t fb = { + .ptr = &drv->framebuf[0], + .stride = DISPLAY_RESX, + }; + + return fb; +} + +void display_refresh(void) { + display_driver_t *drv = &g_display_driver; + +#if defined USE_CONSUMPTION_MASK && !defined BOARDLOADER + // This is an intentional randomization of the consumption masking algorithm + // after every change on the display + consumption_mask_randomize(); +#endif + + // Sends the current frame buffer to the display + display_sync_with_fb(drv); +} + +void display_set_compatible_settings() {} + +void display_fill(const gl_bitblt_t *bb) { + display_driver_t *drv = &g_display_driver; + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = &drv->framebuf[DISPLAY_RESX * bb_new.dst_y]; + bb_new.dst_stride = DISPLAY_RESX; + + gl_mono8_fill(&bb_new); +} + +void display_copy_mono1p(const gl_bitblt_t *bb) { + display_driver_t *drv = &g_display_driver; + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = &drv->framebuf[DISPLAY_RESX * bb_new.dst_y]; + bb_new.dst_stride = DISPLAY_RESX; + + gl_mono8_copy_mono1p(&bb_new); +} diff --git a/core/embed/trezorhal/stm32f4/displays/ltdc.c b/core/embed/trezorhal/stm32f4/displays/ltdc.c index 8d5bd3eca..93ee9659e 100644 --- a/core/embed/trezorhal/stm32f4/displays/ltdc.c +++ b/core/embed/trezorhal/stm32f4/displays/ltdc.c @@ -19,7 +19,7 @@ #include #include TREZOR_BOARD -#include "display_interface.h" +#include "display.h" #include "memzero.h" #include STM32_HAL_H @@ -46,12 +46,12 @@ uint8_t *const DISPLAY_DATA_ADDRESS = 0; 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_y0 = DISPLAY_RESX - 1; uint16_t window_x1 = 0; -uint16_t window_y1 = MAX_DISPLAY_RESY - 1; +uint16_t window_y1 = DISPLAY_RESY - 1; void display_pixeldata(uint16_t c) { - ((uint16_t *)LCD_FRAME_BUFFER)[(cursor_y * MAX_DISPLAY_RESX) + cursor_x] = c; + ((uint16_t *)LCD_FRAME_BUFFER)[(cursor_y * DISPLAY_RESX) + cursor_x] = c; cursor_x++; @@ -83,9 +83,9 @@ void BSP_LCD_LayerDefaultInit(uint16_t LayerIndex, uint32_t FB_Address) { /* Layer Init */ Layercfg.WindowX0 = 0; - Layercfg.WindowX1 = MAX_DISPLAY_RESX; + Layercfg.WindowX1 = DISPLAY_RESX; Layercfg.WindowY0 = 0; - Layercfg.WindowY1 = MAX_DISPLAY_RESY; + Layercfg.WindowY1 = DISPLAY_RESY; Layercfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565; Layercfg.FBStartAdress = FB_Address; Layercfg.Alpha = 255; @@ -95,8 +95,8 @@ void BSP_LCD_LayerDefaultInit(uint16_t LayerIndex, uint32_t FB_Address) { Layercfg.Backcolor.Red = 0; Layercfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA; Layercfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA; - Layercfg.ImageWidth = MAX_DISPLAY_RESX; - Layercfg.ImageHeight = MAX_DISPLAY_RESY; + Layercfg.ImageWidth = DISPLAY_RESX; + Layercfg.ImageHeight = DISPLAY_RESY; HAL_LTDC_ConfigLayer(&LtdcHandler, &Layercfg, LayerIndex); @@ -380,7 +380,7 @@ void display_efficient_clear(void) { 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)))); + address = (address + (2 * ((cursor_y)*DISPLAY_RESX + (cursor_x)))); return (uint8_t *)address; } @@ -413,7 +413,7 @@ void display_shift_window(uint16_t pixels) { } uint16_t display_get_window_offset(void) { - return MAX_DISPLAY_RESX - display_get_window_width(); + return DISPLAY_RESX - display_get_window_width(); } void display_finish_actions(void) {} diff --git a/core/embed/trezorhal/stm32f4/displays/ltdc.h b/core/embed/trezorhal/stm32f4/displays/ltdc.h index 2efb2be96..94d83a026 100644 --- a/core/embed/trezorhal/stm32f4/displays/ltdc.h +++ b/core/embed/trezorhal/stm32f4/displays/ltdc.h @@ -5,8 +5,8 @@ #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_WIDTH DISPLAY_RESX +#define DISPLAY_FRAMEBUFFER_HEIGHT DISPLAY_RESY #define DISPLAY_FRAMEBUFFER_OFFSET_X 0 #define DISPLAY_FRAMEBUFFER_OFFSET_Y 0 #define DISPLAY_COLOR_MODE DMA2D_OUTPUT_RGB565 diff --git a/core/embed/trezorhal/stm32f4/displays/panels/lx154a2422.c b/core/embed/trezorhal/stm32f4/displays/panels/lx154a2422.c index acc0efe3f..9a632fd68 100644 --- a/core/embed/trezorhal/stm32f4/displays/panels/lx154a2422.c +++ b/core/embed/trezorhal/stm32f4/displays/panels/lx154a2422.c @@ -1,5 +1,5 @@ -#include "display_interface.h" +#include "display.h" #include "displays/st7789v.h" #include "touch.h" diff --git a/core/embed/trezorhal/stm32f4/displays/panels/tf15411a.c b/core/embed/trezorhal/stm32f4/displays/panels/tf15411a.c index 4c186bc74..179e3018f 100644 --- a/core/embed/trezorhal/stm32f4/displays/panels/tf15411a.c +++ b/core/embed/trezorhal/stm32f4/displays/panels/tf15411a.c @@ -1,4 +1,4 @@ -#include "display_interface.h" +#include "display.h" #include "displays/st7789v.h" void tf15411a_init_seq(void) { diff --git a/core/embed/trezorhal/stm32f4/displays/st7789v.c b/core/embed/trezorhal/stm32f4/displays/st7789v.c index 7e9f1392c..5b9197c62 100644 --- a/core/embed/trezorhal/stm32f4/displays/st7789v.c +++ b/core/embed/trezorhal/stm32f4/displays/st7789v.c @@ -22,7 +22,7 @@ #include #include TREZOR_BOARD #include "backlight_pwm.h" -#include "display_interface.h" +#include "display.h" #include "irq.h" #include "memzero.h" #include "st7789v.h" @@ -34,6 +34,8 @@ #include "displays/panels/lx154a2411.h" #include "displays/panels/lx154a2422.h" #include "displays/panels/tf15411a.h" +#else +#include "displays/panels/lx154a2422.h" #endif // using const volatile instead of #define results in binaries that change @@ -241,7 +243,7 @@ int display_orientation(int degrees) { lx154a2422_rotate(degrees, &DISPLAY_PADDING); } #else - DISPLAY_PANEL_ROTATE(degrees, &DISPLAY_PADDING); + lx154a2422_rotate(degrees, &DISPLAY_PADDING); #endif panel_set_window(0, 0, DISPLAY_RESX - 1, DISPLAY_RESY - 1); } @@ -292,7 +294,7 @@ void display_init_seq(void) { _154a_init_seq(); } #else - DISPLAY_PANEL_INIT_SEQ(); + lx154a2422_init_seq(); #endif display_unsleep(); diff --git a/core/embed/trezorhal/stm32f4/displays/ug-2828tswig01.c b/core/embed/trezorhal/stm32f4/displays/ug-2828tswig01.c index 2d165bbaa..4e09309a6 100644 --- a/core/embed/trezorhal/stm32f4/displays/ug-2828tswig01.c +++ b/core/embed/trezorhal/stm32f4/displays/ug-2828tswig01.c @@ -19,7 +19,7 @@ #include #include TREZOR_BOARD -#include "display_interface.h" +#include "display.h" #include "memzero.h" #include STM32_HAL_H diff --git a/core/embed/trezorhal/stm32f4/displays/vg-2864ksweg01.c b/core/embed/trezorhal/stm32f4/displays/vg-2864ksweg01.c index d8568923d..ad3ea481b 100644 --- a/core/embed/trezorhal/stm32f4/displays/vg-2864ksweg01.c +++ b/core/embed/trezorhal/stm32f4/displays/vg-2864ksweg01.c @@ -20,7 +20,7 @@ #include #include #include TREZOR_BOARD -#include "display_interface.h" +#include "display.h" #include STM32_HAL_H #ifdef USE_CONSUMPTION_MASK diff --git a/core/embed/trezorhal/stm32f4/dma2d.c b/core/embed/trezorhal/stm32f4/dma2d.c index c27880cd5..c479d4b4c 100644 --- a/core/embed/trezorhal/stm32f4/dma2d.c +++ b/core/embed/trezorhal/stm32f4/dma2d.c @@ -20,7 +20,7 @@ #include "dma2d.h" #include "colors.h" #include STM32_HAL_H -#include "display_interface.h" +#include "display.h" typedef enum { DMA2D_LAYER_FG = 1, diff --git a/core/embed/trezorhal/stm32f4/dma2d_bitblt.c b/core/embed/trezorhal/stm32f4/dma2d_bitblt.c new file mode 100644 index 000000000..f478d7d07 --- /dev/null +++ b/core/embed/trezorhal/stm32f4/dma2d_bitblt.c @@ -0,0 +1,599 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include STM32_HAL_H + +#include + +#include "dma2d_bitblt.h" +#include "gl_color.h" + +static DMA2D_HandleTypeDef dma2d_handle = { + .Instance = (DMA2D_TypeDef*)DMA2D_BASE, +}; + +// Returns `true` if the specified address is accessible by DMA2D +// and can be used by any of the following functions +static inline bool dma2d_accessible(const void* ptr) { +#ifdef STM32F4 + const void* ccm_start = (const void*)0x10000000; + const void* ccm_end = (const void*)0x1000FFFF; + return !(ptr >= ccm_start && ptr <= ccm_end); +#else + return true; +#endif +} + +void dma2d_wait(void) { + while (HAL_DMA2D_PollForTransfer(&dma2d_handle, 10) != HAL_OK) + ; +} + +bool dma2d_rgb565_fill(const gl_bitblt_t* bb) { + dma2d_wait(); + + if (!dma2d_accessible(bb->dst_row)) { + return false; + } + + if (bb->src_alpha == 255) { + dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_RGB565; + dma2d_handle.Init.Mode = DMA2D_R2M; + dma2d_handle.Init.OutputOffset = + bb->dst_stride / sizeof(uint16_t) - bb->width; + HAL_DMA2D_Init(&dma2d_handle); + + HAL_DMA2D_Start(&dma2d_handle, gl_color_to_color32(bb->src_fg), + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t), + bb->width, bb->height); + } else { +#ifdef STM32U5 + dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_RGB565; + dma2d_handle.Init.Mode = DMA2D_M2M_BLEND_FG; + dma2d_handle.Init.OutputOffset = + bb->dst_stride / sizeof(uint16_t) - bb->width; + HAL_DMA2D_Init(&dma2d_handle); + + dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565; + dma2d_handle.LayerCfg[1].InputOffset = 0; + dma2d_handle.LayerCfg[1].AlphaMode = DMA2D_REPLACE_ALPHA; + dma2d_handle.LayerCfg[1].InputAlpha = bb->src_alpha; + HAL_DMA2D_ConfigLayer(&dma2d_handle, 1); + + dma2d_handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565; + dma2d_handle.LayerCfg[0].InputOffset = + bb->dst_stride / sizeof(uint16_t) - bb->width; + dma2d_handle.LayerCfg[0].AlphaMode = 0; + dma2d_handle.LayerCfg[0].InputAlpha = 0; + HAL_DMA2D_ConfigLayer(&dma2d_handle, 0); + + HAL_DMA2D_BlendingStart( + &dma2d_handle, gl_color_to_color32(bb->src_fg), + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t), + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t), bb->width, + bb->height); +#else + // STM32F4 can not accelerate blending with the fixed color + return false; +#endif + } + + return true; +} + +static void dma2d_config_clut(uint32_t layer, gl_color_t fg, gl_color_t bg) { +#define LAYER_COUNT 2 +#define GRADIENT_STEPS 16 + + static struct { + gl_color_t c_fg; + gl_color_t c_bg; + } cache[LAYER_COUNT] = {0}; + + if (layer >= LAYER_COUNT) { + return; + } + + volatile uint32_t* clut = + layer ? dma2d_handle.Instance->FGCLUT : dma2d_handle.Instance->BGCLUT; + + if (fg != cache[layer].c_fg || bg != cache[layer].c_bg) { + cache[layer].c_fg = fg; + cache[layer].c_bg = bg; + + for (int step = 0; step < GRADIENT_STEPS; step++) { + clut[step] = gl_color32_blend_a4(fg, bg, step); + } + + DMA2D_CLUTCfgTypeDef clut; + clut.CLUTColorMode = DMA2D_CCM_ARGB8888; + clut.Size = GRADIENT_STEPS - 1; + clut.pCLUT = 0; // ??? + + HAL_DMA2D_ConfigCLUT(&dma2d_handle, clut, layer); + } +} + +static void dma2d_rgb565_copy_mono4_first_col(gl_bitblt_t* bb, + const gl_color16_t* gradient) { + uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x; + uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x / 2; + + int height = bb->height; + + while (height-- > 0) { + uint8_t fg_lum = src_ptr[0] >> 4; + dst_ptr[0] = gradient[fg_lum]; + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ptr += bb->src_stride / sizeof(*src_ptr); + } +} + +static void dma2d_rgb565_copy_mono4_last_col(gl_bitblt_t* bb, + const gl_color16_t* gradient) { + uint16_t* dst_ptr = (uint16_t*)bb->dst_row + (bb->dst_x + bb->width - 1); + uint8_t* src_ptr = (uint8_t*)bb->src_row + (bb->src_x + bb->width - 1) / 2; + + int height = bb->height; + + while (height-- > 0) { + uint8_t fg_lum = src_ptr[0] & 0x0F; + dst_ptr[0] = gradient[fg_lum]; + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ptr += bb->src_stride / sizeof(*src_ptr); + } +} + +bool dma2d_rgb565_copy_mono4(const gl_bitblt_t* params) { + const gl_color16_t* src_gradient = NULL; + + gl_bitblt_t bb_copy = *params; + gl_bitblt_t* bb = &bb_copy; + + dma2d_wait(); + + if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) { + return false; + } + + if (bb->src_x & 1) { + // First column of mono4 bitmap is odd + // Use the CPU to draw the first column + src_gradient = gl_color16_gradient_a4(bb->src_fg, bb->src_bg); + dma2d_rgb565_copy_mono4_first_col(bb, src_gradient); + bb->dst_x += 1; + bb->src_x += 1; + bb->width -= 1; + } + + if (bb->width > 0 && bb->width & 1) { + // The width is odd + // Use the CPU to draw the last column + if (src_gradient == NULL) { + src_gradient = gl_color16_gradient_a4(bb->src_fg, bb->src_bg); + } + dma2d_rgb565_copy_mono4_last_col(bb, src_gradient); + bb->width -= 1; + } + + dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_RGB565; + dma2d_handle.Init.Mode = DMA2D_M2M_PFC; + dma2d_handle.Init.OutputOffset = + bb->dst_stride / sizeof(uint16_t) - bb->width; + HAL_DMA2D_Init(&dma2d_handle); + + dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4; + dma2d_handle.LayerCfg[1].InputOffset = bb->src_stride * 2 - bb->width; + dma2d_handle.LayerCfg[1].AlphaMode = 0; + dma2d_handle.LayerCfg[1].InputAlpha = 0; + HAL_DMA2D_ConfigLayer(&dma2d_handle, 1); + + dma2d_config_clut(1, bb->src_fg, bb->src_bg); + + HAL_DMA2D_Start(&dma2d_handle, (uint32_t)bb->src_row + bb->src_x / 2, + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t), + bb->width, bb->height); + return true; +} + +bool dma2d_rgb565_copy_rgb565(const gl_bitblt_t* bb) { + dma2d_wait(); + + if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) { + return false; + } + + dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_RGB565; + dma2d_handle.Init.Mode = DMA2D_M2M_PFC; + dma2d_handle.Init.OutputOffset = + bb->dst_stride / sizeof(uint16_t) - bb->width; + HAL_DMA2D_Init(&dma2d_handle); + + dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565; + dma2d_handle.LayerCfg[1].InputOffset = + bb->src_stride / sizeof(uint16_t) - bb->width; + dma2d_handle.LayerCfg[1].AlphaMode = 0; + dma2d_handle.LayerCfg[1].InputAlpha = 0; + HAL_DMA2D_ConfigLayer(&dma2d_handle, 1); + + HAL_DMA2D_Start(&dma2d_handle, + (uint32_t)bb->src_row + bb->src_x * sizeof(uint16_t), + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t), + bb->width, bb->height); + return true; +} + +static void dma2d_rgb565_blend_mono4_first_col(const gl_bitblt_t* bb) { + uint16_t* dst_ptr = (uint16_t*)bb->dst_row + bb->dst_x; + uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x / 2; + + int height = bb->height; + + while (height-- > 0) { + uint8_t fg_alpha = src_ptr[0] >> 4; + dst_ptr[0] = gl_color16_blend_a4(bb->src_fg, + gl_color16_to_color(dst_ptr[0]), fg_alpha); + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ptr += bb->src_stride / sizeof(*src_ptr); + } +} + +static void dma2d_rgb565_blend_mono4_last_col(const gl_bitblt_t* bb) { + uint16_t* dst_ptr = (uint16_t*)bb->dst_row + (bb->dst_x + bb->width - 1); + uint8_t* src_ptr = (uint8_t*)bb->src_row + (bb->src_x + bb->width - 1) / 2; + + int height = bb->height; + + while (height-- > 0) { + uint8_t fg_alpha = src_ptr[0] & 0x0F; + dst_ptr[0] = gl_color16_blend_a4(bb->src_fg, + gl_color16_to_color(dst_ptr[0]), fg_alpha); + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ptr += bb->src_stride / sizeof(*src_ptr); + } +} + +bool dma2d_rgb565_blend_mono4(const gl_bitblt_t* params) { + dma2d_wait(); + + gl_bitblt_t bb_copy = *params; + gl_bitblt_t* bb = &bb_copy; + + if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) { + return false; + } + + if (bb->src_x & 1) { + // First column of mono4 bitmap is odd + // Use the CPU to draw the first column + dma2d_rgb565_blend_mono4_first_col(bb); + bb->dst_x += 1; + bb->src_x += 1; + bb->width -= 1; + } + + if (bb->width > 0 && bb->width & 1) { + // The width is odd + // Use the CPU to draw the last column + dma2d_rgb565_blend_mono4_last_col(bb); + bb->width -= 1; + } + + if (bb->width > 0) { + dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_RGB565; + dma2d_handle.Init.Mode = DMA2D_M2M_BLEND; + dma2d_handle.Init.OutputOffset = + bb->dst_stride / sizeof(uint16_t) - bb->width; + HAL_DMA2D_Init(&dma2d_handle); + + dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A4; + dma2d_handle.LayerCfg[1].InputOffset = bb->src_stride * 2 - bb->width; + dma2d_handle.LayerCfg[1].AlphaMode = 0; + dma2d_handle.LayerCfg[1].InputAlpha = gl_color_to_color32(bb->src_fg); + HAL_DMA2D_ConfigLayer(&dma2d_handle, 1); + + dma2d_handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565; + dma2d_handle.LayerCfg[0].InputOffset = + bb->dst_stride / sizeof(uint16_t) - bb->width; + dma2d_handle.LayerCfg[0].AlphaMode = 0; + dma2d_handle.LayerCfg[0].InputAlpha = 0; + HAL_DMA2D_ConfigLayer(&dma2d_handle, 0); + + HAL_DMA2D_BlendingStart( + &dma2d_handle, (uint32_t)bb->src_row + bb->src_x / 2, + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t), + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint16_t), bb->width, + bb->height); + } + + return true; +} + +bool dma2d_rgba8888_fill(const gl_bitblt_t* bb) { + dma2d_wait(); + + if (!dma2d_accessible(bb->dst_row)) { + return false; + } + + if (bb->src_alpha == 255) { + dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888; + dma2d_handle.Init.Mode = DMA2D_R2M; + dma2d_handle.Init.OutputOffset = + bb->dst_stride / sizeof(uint32_t) - bb->width; + HAL_DMA2D_Init(&dma2d_handle); + + HAL_DMA2D_Start(&dma2d_handle, gl_color_to_color32(bb->src_fg), + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t), + bb->width, bb->height); + } else { +#ifdef STM32U5 + dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888; + dma2d_handle.Init.Mode = DMA2D_M2M_BLEND_FG; + dma2d_handle.Init.OutputOffset = + bb->dst_stride / sizeof(uint32_t) - bb->width; + HAL_DMA2D_Init(&dma2d_handle); + + dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_ARGB8888; + dma2d_handle.LayerCfg[1].InputOffset = 0; + dma2d_handle.LayerCfg[1].AlphaMode = DMA2D_REPLACE_ALPHA; + dma2d_handle.LayerCfg[1].InputAlpha = bb->src_alpha; + HAL_DMA2D_ConfigLayer(&dma2d_handle, 1); + + dma2d_handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_ARGB8888; + dma2d_handle.LayerCfg[0].InputOffset = + bb->dst_stride / sizeof(uint32_t) - bb->width; + dma2d_handle.LayerCfg[0].AlphaMode = 0; + dma2d_handle.LayerCfg[0].InputAlpha = 0; + HAL_DMA2D_ConfigLayer(&dma2d_handle, 0); + + HAL_DMA2D_BlendingStart( + &dma2d_handle, gl_color_to_color32(bb->src_fg), + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t), + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t), bb->width, + bb->height); +#else + // STM32F4 can not accelerate blending with the fixed color + return false; +#endif + } + return true; +} + +static void dma2d_rgba8888_copy_mono4_first_col(gl_bitblt_t* bb, + const gl_color32_t* gradient) { + uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x; + uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x / 2; + + int height = bb->height; + + while (height-- > 0) { + uint8_t fg_lum = src_ptr[0] >> 4; + dst_ptr[0] = gradient[fg_lum]; + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ptr += bb->src_stride / sizeof(*src_ptr); + } +} + +static void dma2d_rgba8888_copy_mono4_last_col(gl_bitblt_t* bb, + const gl_color32_t* gradient) { + uint32_t* dst_ptr = (uint32_t*)bb->dst_row + (bb->dst_x + bb->width - 1); + uint8_t* src_ptr = (uint8_t*)bb->src_row + (bb->src_x + bb->width - 1) / 2; + + int height = bb->height; + + while (height-- > 0) { + uint8_t fg_lum = src_ptr[0] & 0x0F; + dst_ptr[0] = gradient[fg_lum]; + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ptr += bb->src_stride / sizeof(*src_ptr); + } +} + +bool dma2d_rgba8888_copy_mono4(const gl_bitblt_t* params) { + const gl_color32_t* src_gradient = NULL; + + gl_bitblt_t bb_copy = *params; + gl_bitblt_t* bb = &bb_copy; + + dma2d_wait(); + + if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) { + return false; + } + + if (bb->src_x & 1) { + // First column of mono4 bitmap is odd + // Use the CPU to draw the first column + src_gradient = gl_color32_gradient_a4(bb->src_fg, bb->src_bg); + dma2d_rgba8888_copy_mono4_first_col(bb, src_gradient); + bb->dst_x += 1; + bb->src_x += 1; + bb->width -= 1; + } + + if (bb->width > 0 && bb->width & 1) { + // The width is odd + // Use the CPU to draw the last column + if (src_gradient == NULL) { + src_gradient = gl_color32_gradient_a4(bb->src_fg, bb->src_bg); + } + dma2d_rgba8888_copy_mono4_last_col(bb, src_gradient); + bb->width -= 1; + } + + dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888; + dma2d_handle.Init.Mode = DMA2D_M2M_PFC; + dma2d_handle.Init.OutputOffset = + bb->dst_stride / sizeof(uint32_t) - bb->width; + HAL_DMA2D_Init(&dma2d_handle); + + dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4; + dma2d_handle.LayerCfg[1].InputOffset = bb->src_stride * 2 - bb->width; + dma2d_handle.LayerCfg[1].AlphaMode = 0; + dma2d_handle.LayerCfg[1].InputAlpha = 0; + HAL_DMA2D_ConfigLayer(&dma2d_handle, 1); + + dma2d_config_clut(1, bb->src_fg, bb->src_bg); + + HAL_DMA2D_Start(&dma2d_handle, (uint32_t)bb->src_row + bb->src_x / 2, + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t), + bb->width, bb->height); + return true; +} + +bool dma2d_rgba8888_copy_rgb565(const gl_bitblt_t* bb) { + dma2d_wait(); + + if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) { + return false; + } + + dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888; + dma2d_handle.Init.Mode = DMA2D_M2M_PFC; + dma2d_handle.Init.OutputOffset = + bb->dst_stride / sizeof(uint32_t) - bb->width; + HAL_DMA2D_Init(&dma2d_handle); + + dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565; + dma2d_handle.LayerCfg[1].InputOffset = + bb->src_stride / sizeof(uint16_t) - bb->width; + dma2d_handle.LayerCfg[1].AlphaMode = 0; + dma2d_handle.LayerCfg[1].InputAlpha = 0; + HAL_DMA2D_ConfigLayer(&dma2d_handle, 1); + + HAL_DMA2D_Start(&dma2d_handle, + (uint32_t)bb->src_row + bb->src_x * sizeof(uint16_t), + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t), + bb->width, bb->height); + return true; +} + +static void dma2d_rgba8888_blend_mono4_first_col(const gl_bitblt_t* bb) { + uint32_t* dst_ptr = (uint32_t*)bb->dst_row + bb->dst_x; + uint8_t* src_ptr = (uint8_t*)bb->src_row + bb->src_x / 2; + + int height = bb->height; + + while (height-- > 0) { + uint8_t fg_alpha = src_ptr[0] >> 4; + dst_ptr[0] = gl_color32_blend_a4(bb->src_fg, + gl_color32_to_color(dst_ptr[0]), fg_alpha); + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ptr += bb->src_stride / sizeof(*src_ptr); + } +} + +static void dma2d_rgba8888_blend_mono4_last_col(const gl_bitblt_t* bb) { + uint32_t* dst_ptr = (uint32_t*)bb->dst_row + (bb->dst_x + bb->width - 1); + uint8_t* src_ptr = (uint8_t*)bb->src_row + (bb->src_x + bb->width - 1) / 2; + + int height = bb->height; + + while (height-- > 0) { + uint8_t fg_alpha = src_ptr[0] & 0x0F; + dst_ptr[0] = gl_color32_blend_a4(bb->src_fg, + gl_color32_to_color(dst_ptr[0]), fg_alpha); + dst_ptr += bb->dst_stride / sizeof(*dst_ptr); + src_ptr += bb->src_stride / sizeof(*src_ptr); + } +} + +bool dma2d_rgba8888_blend_mono4(const gl_bitblt_t* params) { + dma2d_wait(); + + gl_bitblt_t bb_copy = *params; + gl_bitblt_t* bb = &bb_copy; + + if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) { + return false; + } + + if (bb->src_x & 1) { + // First column of mono4 bitmap is odd + // Use the CPU to draw the first column + dma2d_rgba8888_blend_mono4_first_col(bb); + bb->dst_x += 1; + bb->src_x += 1; + bb->width -= 1; + } + + if (bb->width > 0 && bb->width & 1) { + // The width is odd + // Use the CPU to draw the last column + dma2d_rgba8888_blend_mono4_last_col(bb); + bb->width -= 1; + } + + if (bb->width > 0) { + dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888; + dma2d_handle.Init.Mode = DMA2D_M2M_BLEND; + dma2d_handle.Init.OutputOffset = + bb->dst_stride / sizeof(uint32_t) - bb->width; + HAL_DMA2D_Init(&dma2d_handle); + + dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A4; + dma2d_handle.LayerCfg[1].InputOffset = bb->src_stride * 2 - bb->width; + dma2d_handle.LayerCfg[1].AlphaMode = 0; + dma2d_handle.LayerCfg[1].InputAlpha = gl_color_to_color32(bb->src_fg); + HAL_DMA2D_ConfigLayer(&dma2d_handle, 1); + + dma2d_handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_ARGB8888; + dma2d_handle.LayerCfg[0].InputOffset = + bb->dst_stride / sizeof(uint32_t) - bb->width; + dma2d_handle.LayerCfg[0].AlphaMode = 0; + dma2d_handle.LayerCfg[0].InputAlpha = 0; + HAL_DMA2D_ConfigLayer(&dma2d_handle, 0); + + HAL_DMA2D_BlendingStart( + &dma2d_handle, (uint32_t)bb->src_row + bb->src_x / 2, + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t), + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t), bb->width, + bb->height); + } + + return true; +} + +bool dma2d_rgba8888_copy_rgba8888(const gl_bitblt_t* bb) { + dma2d_wait(); + + if (!dma2d_accessible(bb->dst_row) || !dma2d_accessible(bb->src_row)) { + return false; + } + + dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_ARGB8888; + dma2d_handle.Init.Mode = DMA2D_M2M_PFC; + dma2d_handle.Init.OutputOffset = + bb->dst_stride / sizeof(uint32_t) - bb->width; + HAL_DMA2D_Init(&dma2d_handle); + + dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_ARGB8888; + dma2d_handle.LayerCfg[1].InputOffset = + bb->src_stride / sizeof(uint32_t) - bb->width; + dma2d_handle.LayerCfg[1].AlphaMode = 0; + dma2d_handle.LayerCfg[1].InputAlpha = 0; + HAL_DMA2D_ConfigLayer(&dma2d_handle, 1); + + HAL_DMA2D_Start(&dma2d_handle, + (uint32_t)bb->src_row + bb->src_x * sizeof(uint32_t), + (uint32_t)bb->dst_row + bb->dst_x * sizeof(uint32_t), + bb->width, bb->height); + return true; +} diff --git a/core/embed/trezorhal/stm32f4/secret.c b/core/embed/trezorhal/stm32f4/secret.c index d299f2068..9a90511fd 100644 --- a/core/embed/trezorhal/stm32f4/secret.c +++ b/core/embed/trezorhal/stm32f4/secret.c @@ -1,7 +1,7 @@ #include "secret.h" #include #include "common.h" -#include "display.h" +#include "display_draw.h" #include "flash.h" #include "model.h" diff --git a/core/embed/trezorhal/stm32f4/touch/ft6x36.c b/core/embed/trezorhal/stm32f4/touch/ft6x36.c index 439dd16aa..f2a7d96bc 100644 --- a/core/embed/trezorhal/stm32f4/touch/ft6x36.c +++ b/core/embed/trezorhal/stm32f4/touch/ft6x36.c @@ -260,8 +260,8 @@ uint32_t touch_read(void) { // first touch) (tested with FT6206) const uint32_t event_flag = touch_data[3] & 0xC0; if (touch_data[1] == GESTURE_NO_GESTURE) { - xy = TRANSFORM_TOUCH_COORDS((X_POS_MSB << 8) | X_POS_LSB, - (Y_POS_MSB << 8) | Y_POS_LSB); + xy = touch_pack_xy((X_POS_MSB << 8) | X_POS_LSB, + (Y_POS_MSB << 8) | Y_POS_LSB); if ((number_of_touch_points == 1) && (event_flag == EVENT_PRESS_DOWN)) { touching = 1; return TOUCH_START | xy; diff --git a/core/embed/trezorhal/stm32u5/display/st-7789 b/core/embed/trezorhal/stm32u5/display/st-7789 new file mode 120000 index 000000000..3ad01c673 --- /dev/null +++ b/core/embed/trezorhal/stm32u5/display/st-7789 @@ -0,0 +1 @@ +../../stm32f4/display/st-7789 \ No newline at end of file diff --git a/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_driver.c b/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_driver.c new file mode 100644 index 000000000..39b4be02b --- /dev/null +++ b/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_driver.c @@ -0,0 +1,155 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include TREZOR_BOARD +#include STM32_HAL_H + +#include "display_internal.h" +#include "xdisplay.h" + +#if (DISPLAY_RESX != 240) || (DISPLAY_RESY != 240) +#error "Incompatible display resolution" +#endif + +// Display driver context. +typedef struct { + // Current display orientation (0, 90, 180, 270) + int orientation_angle; + // Current backlight level ranging from 0 to 255 + int backlight_level; +} display_driver_t; + +// Display driver instance +static display_driver_t g_display_driver; + +void display_init(void) { + RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; + + // Initializes the common periph clock + PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_LTDC | RCC_PERIPHCLK_DSI; + PeriphClkInit.DsiClockSelection = RCC_DSICLKSOURCE_PLL3; + PeriphClkInit.LtdcClockSelection = RCC_LTDCCLKSOURCE_PLL3; + PeriphClkInit.PLL3.PLL3Source = RCC_PLLSOURCE_HSE; + PeriphClkInit.PLL3.PLL3M = 4; + PeriphClkInit.PLL3.PLL3N = 125; + PeriphClkInit.PLL3.PLL3P = 8; + PeriphClkInit.PLL3.PLL3Q = 2; + PeriphClkInit.PLL3.PLL3R = 24; + PeriphClkInit.PLL3.PLL3RGE = RCC_PLLVCIRANGE_0; + PeriphClkInit.PLL3.PLL3FRACN = 0; + PeriphClkInit.PLL3.PLL3ClockOut = RCC_PLL3_DIVP | RCC_PLL3_DIVR; + HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit); + + // Clear framebuffers + memset(physical_frame_buffer_0, 0x00, PHYSICAL_FRAME_BUFFER_SIZE); + memset(physical_frame_buffer_1, 0x00, PHYSICAL_FRAME_BUFFER_SIZE); + + BSP_LCD_Init(0, LCD_ORIENTATION_PORTRAIT); + BSP_LCD_SetBrightness(0, 100); + BSP_LCD_DisplayOn(0); +} + +void display_reinit(void) { + BSP_LCD_Reinit(0); + if (current_frame_buffer == 0) { + BSP_LCD_SetFrameBuffer(0, GFXMMU_VIRTUAL_BUFFER1_BASE_S); + } else { + BSP_LCD_SetFrameBuffer(0, GFXMMU_VIRTUAL_BUFFER0_BASE_S); + } +} + +void display_finish_actions(void) { + // Not used and intentionally left empty +} + +int display_set_backlight(int level) { + display_driver_t *drv = &g_display_driver; + + // Just emulation, not doing anything + drv->backlight_level = level; + return level; +} + +int display_get_backlight(void) { + display_driver_t *drv = &g_display_driver; + + return drv->orientation_angle; +} + +int display_set_orientation(int angle) { + display_driver_t *drv = &g_display_driver; + + if (angle == 0 || angle == 90 || angle == 180 || angle == 270) { + // Just emulation, not doing anything + drv->orientation_angle = angle; + } + + return drv->orientation_angle; +} + +int display_get_orientation(void) { + display_driver_t *drv = &g_display_driver; + + return drv->orientation_angle; +} + +void display_set_compatible_settings() {} + +void display_fill(const gl_bitblt_t *bb) { + display_fb_info_t fb = display_get_frame_buffer(); + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = (uint8_t *)fb.ptr + (fb.stride * bb_new.dst_y); + bb_new.dst_stride = fb.stride; + + gl_rgba8888_fill(&bb_new); +} + +void display_copy_rgb565(const gl_bitblt_t *bb) { + display_fb_info_t fb = display_get_frame_buffer(); + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = (uint8_t *)fb.ptr + (fb.stride * bb_new.dst_y); + bb_new.dst_stride = fb.stride; + + gl_rgba8888_copy_rgb565(&bb_new); +} + +void display_copy_mono1p(const gl_bitblt_t *bb) { + display_fb_info_t fb = display_get_frame_buffer(); + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = (uint8_t *)fb.ptr + (fb.stride * bb_new.dst_y); + bb_new.dst_stride = fb.stride; + + gl_rgba8888_copy_mono1p(&bb_new); +} + +void display_copy_mono4(const gl_bitblt_t *bb) { + display_fb_info_t fb = display_get_frame_buffer(); + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = (uint8_t *)fb.ptr + (fb.stride * bb_new.dst_y); + bb_new.dst_stride = fb.stride; + + gl_rgba8888_copy_mono4(&bb_new); +} diff --git a/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_fb.c b/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_fb.c new file mode 100644 index 000000000..5b425176c --- /dev/null +++ b/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_fb.c @@ -0,0 +1,77 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include TREZOR_BOARD +#include STM32_HAL_H + +#include +#include "display_internal.h" + +// Physical frame buffers in internal SRAM memory +__attribute__((section(".fb1"))) +ALIGN_32BYTES(uint32_t physical_frame_buffer_0[PHYSICAL_FRAME_BUFFER_SIZE]); + +__attribute__((section(".fb2"))) +ALIGN_32BYTES(uint32_t physical_frame_buffer_1[PHYSICAL_FRAME_BUFFER_SIZE]); + +// The current frame buffer selector at fixed memory address +// It's shared between bootloaders and the firmware +__attribute__((section(".framebuffer_select"))) uint32_t current_frame_buffer = + 0; + +display_fb_info_t display_get_frame_buffer(void) { + uintptr_t addr; + + if (current_frame_buffer == 0) { + addr = GFXMMU_VIRTUAL_BUFFER1_BASE_S; + } else { + addr = GFXMMU_VIRTUAL_BUFFER0_BASE_S; + } + + uint32_t fb_stride = FRAME_BUFFER_PIXELS_PER_LINE * sizeof(uint32_t); + + // We do not utilize whole area of the display + // (discovery kit display is 480x480 and we need just 240x240) + addr += (480 - DISPLAY_RESY) / 2 * sizeof(uint32_t); + addr += (480 - DISPLAY_RESX) / 2 * fb_stride; + + display_fb_info_t fb = { + .ptr = (void *)addr, + .stride = fb_stride, + }; + + return fb; +} + +void display_refresh(void) { + if (current_frame_buffer == 0) { + current_frame_buffer = 1; + BSP_LCD_SetFrameBuffer(0, GFXMMU_VIRTUAL_BUFFER1_BASE_S); + memcpy(physical_frame_buffer_0, physical_frame_buffer_1, + sizeof(physical_frame_buffer_0)); + } else { + current_frame_buffer = 0; + BSP_LCD_SetFrameBuffer(0, GFXMMU_VIRTUAL_BUFFER0_BASE_S); + memcpy(physical_frame_buffer_1, physical_frame_buffer_0, + sizeof(physical_frame_buffer_1)); + } +} diff --git a/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_gfxmmu_lut.h b/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_gfxmmu_lut.h new file mode 100644 index 000000000..86528b9d9 --- /dev/null +++ b/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_gfxmmu_lut.h @@ -0,0 +1,1006 @@ +/* USER CODE BEGIN Header */ +/** + ****************************************************************************** + * File Name : gfxmmu_lut.h + * Description : header file for GFX MMU Configuration Table + ****************************************************************************** + * @attention + * + * Copyright (c) 2022 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ +/* USER CODE END Header */ +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __gfxmmu_lut_H +#define __gfxmmu_lut_H +#ifdef __cplusplus +extern "C" { +#endif +// GFX MMU Configuration Table + +#define GFXMMU_FB_SIZE 733936 +#define GFXMMU_LUT_FIRST 0 +#define GFXMMU_LUT_LAST 479 +#define GFXMMU_LUT_SIZE 480 + +uint32_t gfxmmu_lut[2 * GFXMMU_LUT_SIZE] = { + 0x00413601, // GFXMMU_LUT0L + 0x003FFCA0, // GFXMMU_LUT0H + 0x00433401, // GFXMMU_LUT1L + 0x003FFD80, // GFXMMU_LUT1H + 0x00453201, // GFXMMU_LUT2L + 0x003FFEA0, // GFXMMU_LUT2H + 0x00463101, // GFXMMU_LUT3L + 0x003FFFF0, // GFXMMU_LUT3H + 0x00482F01, // GFXMMU_LUT4L + 0x00000170, // GFXMMU_LUT4H + 0x00492E01, // GFXMMU_LUT5L + 0x00000320, // GFXMMU_LUT5H + 0x004A2D01, // GFXMMU_LUT6L + 0x000004F0, // GFXMMU_LUT6H + 0x004B2C01, // GFXMMU_LUT7L + 0x000006E0, // GFXMMU_LUT7H + 0x004C2B01, // GFXMMU_LUT8L + 0x000008F0, // GFXMMU_LUT8H + 0x004D2A01, // GFXMMU_LUT9L + 0x00000B20, // GFXMMU_LUT9H + 0x004D2A01, // GFXMMU_LUT10L + 0x00000D60, // GFXMMU_LUT10H + 0x004E2901, // GFXMMU_LUT11L + 0x00000FB0, // GFXMMU_LUT11H + 0x004F2801, // GFXMMU_LUT12L + 0x00001220, // GFXMMU_LUT12H + 0x00502701, // GFXMMU_LUT13L + 0x000014B0, // GFXMMU_LUT13H + 0x00502701, // GFXMMU_LUT14L + 0x00001750, // GFXMMU_LUT14H + 0x00512601, // GFXMMU_LUT15L + 0x00001A00, // GFXMMU_LUT15H + 0x00522501, // GFXMMU_LUT16L + 0x00001CD0, // GFXMMU_LUT16H + 0x00522501, // GFXMMU_LUT17L + 0x00001FB0, // GFXMMU_LUT17H + 0x00532401, // GFXMMU_LUT18L + 0x000022A0, // GFXMMU_LUT18H + 0x00532401, // GFXMMU_LUT19L + 0x000025A0, // GFXMMU_LUT19H + 0x00542301, // GFXMMU_LUT20L + 0x000028B0, // GFXMMU_LUT20H + 0x00552201, // GFXMMU_LUT21L + 0x00002BE0, // GFXMMU_LUT21H + 0x00552201, // GFXMMU_LUT22L + 0x00002F20, // GFXMMU_LUT22H + 0x00562101, // GFXMMU_LUT23L + 0x00003270, // GFXMMU_LUT23H + 0x00562101, // GFXMMU_LUT24L + 0x000035D0, // GFXMMU_LUT24H + 0x00572001, // GFXMMU_LUT25L + 0x00003940, // GFXMMU_LUT25H + 0x00572001, // GFXMMU_LUT26L + 0x00003CC0, // GFXMMU_LUT26H + 0x00581F01, // GFXMMU_LUT27L + 0x00004050, // GFXMMU_LUT27H + 0x00581F01, // GFXMMU_LUT28L + 0x000043F0, // GFXMMU_LUT28H + 0x00591E01, // GFXMMU_LUT29L + 0x000047A0, // GFXMMU_LUT29H + 0x00591E01, // GFXMMU_LUT30L + 0x00004B60, // GFXMMU_LUT30H + 0x00591E01, // GFXMMU_LUT31L + 0x00004F20, // GFXMMU_LUT31H + 0x005A1D01, // GFXMMU_LUT32L + 0x000052F0, // GFXMMU_LUT32H + 0x005A1D01, // GFXMMU_LUT33L + 0x000056D0, // GFXMMU_LUT33H + 0x005B1C01, // GFXMMU_LUT34L + 0x00005AC0, // GFXMMU_LUT34H + 0x005B1C01, // GFXMMU_LUT35L + 0x00005EC0, // GFXMMU_LUT35H + 0x005C1B01, // GFXMMU_LUT36L + 0x000062D0, // GFXMMU_LUT36H + 0x005C1B01, // GFXMMU_LUT37L + 0x000066F0, // GFXMMU_LUT37H + 0x005C1B01, // GFXMMU_LUT38L + 0x00006B10, // GFXMMU_LUT38H + 0x005D1A01, // GFXMMU_LUT39L + 0x00006F40, // GFXMMU_LUT39H + 0x005D1A01, // GFXMMU_LUT40L + 0x00007380, // GFXMMU_LUT40H + 0x005D1A01, // GFXMMU_LUT41L + 0x000077C0, // GFXMMU_LUT41H + 0x005E1901, // GFXMMU_LUT42L + 0x00007C10, // GFXMMU_LUT42H + 0x005E1901, // GFXMMU_LUT43L + 0x00008070, // GFXMMU_LUT43H + 0x005E1901, // GFXMMU_LUT44L + 0x000084D0, // GFXMMU_LUT44H + 0x005F1801, // GFXMMU_LUT45L + 0x00008940, // GFXMMU_LUT45H + 0x005F1801, // GFXMMU_LUT46L + 0x00008DC0, // GFXMMU_LUT46H + 0x00601801, // GFXMMU_LUT47L + 0x00009240, // GFXMMU_LUT47H + 0x00601701, // GFXMMU_LUT48L + 0x000096E0, // GFXMMU_LUT48H + 0x00601701, // GFXMMU_LUT49L + 0x00009B80, // GFXMMU_LUT49H + 0x00601701, // GFXMMU_LUT50L + 0x0000A020, // GFXMMU_LUT50H + 0x00611601, // GFXMMU_LUT51L + 0x0000A4D0, // GFXMMU_LUT51H + 0x00611601, // GFXMMU_LUT52L + 0x0000A990, // GFXMMU_LUT52H + 0x00611601, // GFXMMU_LUT53L + 0x0000AE50, // GFXMMU_LUT53H + 0x00621501, // GFXMMU_LUT54L + 0x0000B320, // GFXMMU_LUT54H + 0x00621501, // GFXMMU_LUT55L + 0x0000B800, // GFXMMU_LUT55H + 0x00621501, // GFXMMU_LUT56L + 0x0000BCE0, // GFXMMU_LUT56H + 0x00631401, // GFXMMU_LUT57L + 0x0000C1D0, // GFXMMU_LUT57H + 0x00631401, // GFXMMU_LUT58L + 0x0000C6D0, // GFXMMU_LUT58H + 0x00631401, // GFXMMU_LUT59L + 0x0000CBD0, // GFXMMU_LUT59H + 0x00631401, // GFXMMU_LUT60L + 0x0000D0D0, // GFXMMU_LUT60H + 0x00641301, // GFXMMU_LUT61L + 0x0000D5E0, // GFXMMU_LUT61H + 0x00641301, // GFXMMU_LUT62L + 0x0000DB00, // GFXMMU_LUT62H + 0x00641301, // GFXMMU_LUT63L + 0x0000E020, // GFXMMU_LUT63H + 0x00651201, // GFXMMU_LUT64L + 0x0000E550, // GFXMMU_LUT64H + 0x00651201, // GFXMMU_LUT65L + 0x0000EA90, // GFXMMU_LUT65H + 0x00651201, // GFXMMU_LUT66L + 0x0000EFD0, // GFXMMU_LUT66H + 0x00651201, // GFXMMU_LUT67L + 0x0000F510, // GFXMMU_LUT67H + 0x00661101, // GFXMMU_LUT68L + 0x0000FA60, // GFXMMU_LUT68H + 0x00661101, // GFXMMU_LUT69L + 0x0000FFC0, // GFXMMU_LUT69H + 0x00661101, // GFXMMU_LUT70L + 0x00010520, // GFXMMU_LUT70H + 0x00661101, // GFXMMU_LUT71L + 0x00010A80, // GFXMMU_LUT71H + 0x00671001, // GFXMMU_LUT72L + 0x00010FF0, // GFXMMU_LUT72H + 0x00671001, // GFXMMU_LUT73L + 0x00011570, // GFXMMU_LUT73H + 0x00671001, // GFXMMU_LUT74L + 0x00011AF0, // GFXMMU_LUT74H + 0x00671001, // GFXMMU_LUT75L + 0x00012070, // GFXMMU_LUT75H + 0x00680F01, // GFXMMU_LUT76L + 0x00012600, // GFXMMU_LUT76H + 0x00680F01, // GFXMMU_LUT77L + 0x00012BA0, // GFXMMU_LUT77H + 0x00680F01, // GFXMMU_LUT78L + 0x00013140, // GFXMMU_LUT78H + 0x00680F01, // GFXMMU_LUT79L + 0x000136E0, // GFXMMU_LUT79H + 0x00680F01, // GFXMMU_LUT80L + 0x00013C80, // GFXMMU_LUT80H + 0x00690E01, // GFXMMU_LUT81L + 0x00014230, // GFXMMU_LUT81H + 0x00690E01, // GFXMMU_LUT82L + 0x000147F0, // GFXMMU_LUT82H + 0x00690E01, // GFXMMU_LUT83L + 0x00014DB0, // GFXMMU_LUT83H + 0x00690E01, // GFXMMU_LUT84L + 0x00015370, // GFXMMU_LUT84H + 0x006A0D01, // GFXMMU_LUT85L + 0x00015940, // GFXMMU_LUT85H + 0x006A0D01, // GFXMMU_LUT86L + 0x00015F20, // GFXMMU_LUT86H + 0x006A0D01, // GFXMMU_LUT87L + 0x00016500, // GFXMMU_LUT87H + 0x006A0D01, // GFXMMU_LUT88L + 0x00016AE0, // GFXMMU_LUT88H + 0x006A0D01, // GFXMMU_LUT89L + 0x000170C0, // GFXMMU_LUT89H + 0x006B0C01, // GFXMMU_LUT90L + 0x000176B0, // GFXMMU_LUT90H + 0x006B0C01, // GFXMMU_LUT91L + 0x00017CB0, // GFXMMU_LUT91H + 0x006B0C01, // GFXMMU_LUT92L + 0x000182B0, // GFXMMU_LUT92H + 0x006B0C01, // GFXMMU_LUT93L + 0x000188B0, // GFXMMU_LUT93H + 0x006B0C01, // GFXMMU_LUT94L + 0x00018EB0, // GFXMMU_LUT94H + 0x006C0C01, // GFXMMU_LUT95L + 0x000194B0, // GFXMMU_LUT95H + 0x006C0B01, // GFXMMU_LUT96L + 0x00019AD0, // GFXMMU_LUT96H + 0x006C0B01, // GFXMMU_LUT97L + 0x0001A0F0, // GFXMMU_LUT97H + 0x006C0B01, // GFXMMU_LUT98L + 0x0001A710, // GFXMMU_LUT98H + 0x006C0B01, // GFXMMU_LUT99L + 0x0001AD30, // GFXMMU_LUT99H + 0x006C0B01, // GFXMMU_LUT100L + 0x0001B350, // GFXMMU_LUT100H + 0x006D0A01, // GFXMMU_LUT101L + 0x0001B980, // GFXMMU_LUT101H + 0x006D0A01, // GFXMMU_LUT102L + 0x0001BFC0, // GFXMMU_LUT102H + 0x006D0A01, // GFXMMU_LUT103L + 0x0001C600, // GFXMMU_LUT103H + 0x006D0A01, // GFXMMU_LUT104L + 0x0001CC40, // GFXMMU_LUT104H + 0x006D0A01, // GFXMMU_LUT105L + 0x0001D280, // GFXMMU_LUT105H + 0x006D0A01, // GFXMMU_LUT106L + 0x0001D8C0, // GFXMMU_LUT106H + 0x006E0901, // GFXMMU_LUT107L + 0x0001DF10, // GFXMMU_LUT107H + 0x006E0901, // GFXMMU_LUT108L + 0x0001E570, // GFXMMU_LUT108H + 0x006E0901, // GFXMMU_LUT109L + 0x0001EBD0, // GFXMMU_LUT109H + 0x006E0901, // GFXMMU_LUT110L + 0x0001F230, // GFXMMU_LUT110H + 0x006E0901, // GFXMMU_LUT111L + 0x0001F890, // GFXMMU_LUT111H + 0x006E0901, // GFXMMU_LUT112L + 0x0001FEF0, // GFXMMU_LUT112H + 0x006F0801, // GFXMMU_LUT113L + 0x00020560, // GFXMMU_LUT113H + 0x006F0801, // GFXMMU_LUT114L + 0x00020BE0, // GFXMMU_LUT114H + 0x006F0801, // GFXMMU_LUT115L + 0x00021260, // GFXMMU_LUT115H + 0x006F0801, // GFXMMU_LUT116L + 0x000218E0, // GFXMMU_LUT116H + 0x006F0801, // GFXMMU_LUT117L + 0x00021F60, // GFXMMU_LUT117H + 0x006F0801, // GFXMMU_LUT118L + 0x000225E0, // GFXMMU_LUT118H + 0x006F0801, // GFXMMU_LUT119L + 0x00022C60, // GFXMMU_LUT119H + 0x00700701, // GFXMMU_LUT120L + 0x000232F0, // GFXMMU_LUT120H + 0x00700701, // GFXMMU_LUT121L + 0x00023990, // GFXMMU_LUT121H + 0x00700701, // GFXMMU_LUT122L + 0x00024030, // GFXMMU_LUT122H + 0x00700701, // GFXMMU_LUT123L + 0x000246D0, // GFXMMU_LUT123H + 0x00700701, // GFXMMU_LUT124L + 0x00024D70, // GFXMMU_LUT124H + 0x00700701, // GFXMMU_LUT125L + 0x00025410, // GFXMMU_LUT125H + 0x00700701, // GFXMMU_LUT126L + 0x00025AB0, // GFXMMU_LUT126H + 0x00710601, // GFXMMU_LUT127L + 0x00026160, // GFXMMU_LUT127H + 0x00710601, // GFXMMU_LUT128L + 0x00026820, // GFXMMU_LUT128H + 0x00710601, // GFXMMU_LUT129L + 0x00026EE0, // GFXMMU_LUT129H + 0x00710601, // GFXMMU_LUT130L + 0x000275A0, // GFXMMU_LUT130H + 0x00710601, // GFXMMU_LUT131L + 0x00027C60, // GFXMMU_LUT131H + 0x00710601, // GFXMMU_LUT132L + 0x00028320, // GFXMMU_LUT132H + 0x00710601, // GFXMMU_LUT133L + 0x000289E0, // GFXMMU_LUT133H + 0x00710601, // GFXMMU_LUT134L + 0x000290A0, // GFXMMU_LUT134H + 0x00720501, // GFXMMU_LUT135L + 0x00029770, // GFXMMU_LUT135H + 0x00720501, // GFXMMU_LUT136L + 0x00029E50, // GFXMMU_LUT136H + 0x00720501, // GFXMMU_LUT137L + 0x0002A530, // GFXMMU_LUT137H + 0x00720501, // GFXMMU_LUT138L + 0x0002AC10, // GFXMMU_LUT138H + 0x00720501, // GFXMMU_LUT139L + 0x0002B2F0, // GFXMMU_LUT139H + 0x00720501, // GFXMMU_LUT140L + 0x0002B9D0, // GFXMMU_LUT140H + 0x00720501, // GFXMMU_LUT141L + 0x0002C0B0, // GFXMMU_LUT141H + 0x00720501, // GFXMMU_LUT142L + 0x0002C790, // GFXMMU_LUT142H + 0x00720501, // GFXMMU_LUT143L + 0x0002CE70, // GFXMMU_LUT143H + 0x00730401, // GFXMMU_LUT144L + 0x0002D560, // GFXMMU_LUT144H + 0x00730401, // GFXMMU_LUT145L + 0x0002DC60, // GFXMMU_LUT145H + 0x00730401, // GFXMMU_LUT146L + 0x0002E360, // GFXMMU_LUT146H + 0x00730401, // GFXMMU_LUT147L + 0x0002EA60, // GFXMMU_LUT147H + 0x00730401, // GFXMMU_LUT148L + 0x0002F160, // GFXMMU_LUT148H + 0x00730401, // GFXMMU_LUT149L + 0x0002F860, // GFXMMU_LUT149H + 0x00730401, // GFXMMU_LUT150L + 0x0002FF60, // GFXMMU_LUT150H + 0x00730401, // GFXMMU_LUT151L + 0x00030660, // GFXMMU_LUT151H + 0x00730401, // GFXMMU_LUT152L + 0x00030D60, // GFXMMU_LUT152H + 0x00740301, // GFXMMU_LUT153L + 0x00031470, // GFXMMU_LUT153H + 0x00740301, // GFXMMU_LUT154L + 0x00031B90, // GFXMMU_LUT154H + 0x00740301, // GFXMMU_LUT155L + 0x000322B0, // GFXMMU_LUT155H + 0x00740301, // GFXMMU_LUT156L + 0x000329D0, // GFXMMU_LUT156H + 0x00740301, // GFXMMU_LUT157L + 0x000330F0, // GFXMMU_LUT157H + 0x00740301, // GFXMMU_LUT158L + 0x00033810, // GFXMMU_LUT158H + 0x00740301, // GFXMMU_LUT159L + 0x00033F30, // GFXMMU_LUT159H + 0x00740301, // GFXMMU_LUT160L + 0x00034650, // GFXMMU_LUT160H + 0x00740301, // GFXMMU_LUT161L + 0x00034D70, // GFXMMU_LUT161H + 0x00740301, // GFXMMU_LUT162L + 0x00035490, // GFXMMU_LUT162H + 0x00740301, // GFXMMU_LUT163L + 0x00035BB0, // GFXMMU_LUT163H + 0x00740301, // GFXMMU_LUT164L + 0x000362D0, // GFXMMU_LUT164H + 0x00750201, // GFXMMU_LUT165L + 0x00036A00, // GFXMMU_LUT165H + 0x00750201, // GFXMMU_LUT166L + 0x00037140, // GFXMMU_LUT166H + 0x00750201, // GFXMMU_LUT167L + 0x00037880, // GFXMMU_LUT167H + 0x00750201, // GFXMMU_LUT168L + 0x00037FC0, // GFXMMU_LUT168H + 0x00750201, // GFXMMU_LUT169L + 0x00038700, // GFXMMU_LUT169H + 0x00750201, // GFXMMU_LUT170L + 0x00038E40, // GFXMMU_LUT170H + 0x00750201, // GFXMMU_LUT171L + 0x00039580, // GFXMMU_LUT171H + 0x00750201, // GFXMMU_LUT172L + 0x00039CC0, // GFXMMU_LUT172H + 0x00750201, // GFXMMU_LUT173L + 0x0003A400, // GFXMMU_LUT173H + 0x00750201, // GFXMMU_LUT174L + 0x0003AB40, // GFXMMU_LUT174H + 0x00750201, // GFXMMU_LUT175L + 0x0003B280, // GFXMMU_LUT175H + 0x00750201, // GFXMMU_LUT176L + 0x0003B9C0, // GFXMMU_LUT176H + 0x00750201, // GFXMMU_LUT177L + 0x0003C100, // GFXMMU_LUT177H + 0x00760101, // GFXMMU_LUT178L + 0x0003C850, // GFXMMU_LUT178H + 0x00760101, // GFXMMU_LUT179L + 0x0003CFB0, // GFXMMU_LUT179H + 0x00760101, // GFXMMU_LUT180L + 0x0003D710, // GFXMMU_LUT180H + 0x00760101, // GFXMMU_LUT181L + 0x0003DE70, // GFXMMU_LUT181H + 0x00760101, // GFXMMU_LUT182L + 0x0003E5D0, // GFXMMU_LUT182H + 0x00760101, // GFXMMU_LUT183L + 0x0003ED30, // GFXMMU_LUT183H + 0x00760101, // GFXMMU_LUT184L + 0x0003F490, // GFXMMU_LUT184H + 0x00760101, // GFXMMU_LUT185L + 0x0003FBF0, // GFXMMU_LUT185H + 0x00760101, // GFXMMU_LUT186L + 0x00040350, // GFXMMU_LUT186H + 0x00760101, // GFXMMU_LUT187L + 0x00040AB0, // GFXMMU_LUT187H + 0x00760101, // GFXMMU_LUT188L + 0x00041210, // GFXMMU_LUT188H + 0x00760101, // GFXMMU_LUT189L + 0x00041970, // GFXMMU_LUT189H + 0x00760101, // GFXMMU_LUT190L + 0x000420D0, // GFXMMU_LUT190H + 0x00760101, // GFXMMU_LUT191L + 0x00042830, // GFXMMU_LUT191H + 0x00760101, // GFXMMU_LUT192L + 0x00042F90, // GFXMMU_LUT192H + 0x00760101, // GFXMMU_LUT193L + 0x000436F0, // GFXMMU_LUT193H + 0x00760101, // GFXMMU_LUT194L + 0x00043E50, // GFXMMU_LUT194H + 0x00760101, // GFXMMU_LUT195L + 0x000445B0, // GFXMMU_LUT195H + 0x00770001, // GFXMMU_LUT196L + 0x00044D20, // GFXMMU_LUT196H + 0x00770001, // GFXMMU_LUT197L + 0x000454A0, // GFXMMU_LUT197H + 0x00770001, // GFXMMU_LUT198L + 0x00045C20, // GFXMMU_LUT198H + 0x00770001, // GFXMMU_LUT199L + 0x000463A0, // GFXMMU_LUT199H + 0x00770001, // GFXMMU_LUT200L + 0x00046B20, // GFXMMU_LUT200H + 0x00770001, // GFXMMU_LUT201L + 0x000472A0, // GFXMMU_LUT201H + 0x00770001, // GFXMMU_LUT202L + 0x00047A20, // GFXMMU_LUT202H + 0x00770001, // GFXMMU_LUT203L + 0x000481A0, // GFXMMU_LUT203H + 0x00770001, // GFXMMU_LUT204L + 0x00048920, // GFXMMU_LUT204H + 0x00770001, // GFXMMU_LUT205L + 0x000490A0, // GFXMMU_LUT205H + 0x00770001, // GFXMMU_LUT206L + 0x00049820, // GFXMMU_LUT206H + 0x00770001, // GFXMMU_LUT207L + 0x00049FA0, // GFXMMU_LUT207H + 0x00770001, // GFXMMU_LUT208L + 0x0004A720, // GFXMMU_LUT208H + 0x00770001, // GFXMMU_LUT209L + 0x0004AEA0, // GFXMMU_LUT209H + 0x00770001, // GFXMMU_LUT210L + 0x0004B620, // GFXMMU_LUT210H + 0x00770001, // GFXMMU_LUT211L + 0x0004BDA0, // GFXMMU_LUT211H + 0x00770001, // GFXMMU_LUT212L + 0x0004C520, // GFXMMU_LUT212H + 0x00770001, // GFXMMU_LUT213L + 0x0004CCA0, // GFXMMU_LUT213H + 0x00770001, // GFXMMU_LUT214L + 0x0004D420, // GFXMMU_LUT214H + 0x00770001, // GFXMMU_LUT215L + 0x0004DBA0, // GFXMMU_LUT215H + 0x00770001, // GFXMMU_LUT216L + 0x0004E320, // GFXMMU_LUT216H + 0x00770001, // GFXMMU_LUT217L + 0x0004EAA0, // GFXMMU_LUT217H + 0x00770001, // GFXMMU_LUT218L + 0x0004F220, // GFXMMU_LUT218H + 0x00770001, // GFXMMU_LUT219L + 0x0004F9A0, // GFXMMU_LUT219H + 0x00770001, // GFXMMU_LUT220L + 0x00050120, // GFXMMU_LUT220H + 0x00770001, // GFXMMU_LUT221L + 0x000508A0, // GFXMMU_LUT221H + 0x00770001, // GFXMMU_LUT222L + 0x00051020, // GFXMMU_LUT222H + 0x00770001, // GFXMMU_LUT223L + 0x000517A0, // GFXMMU_LUT223H + 0x00770001, // GFXMMU_LUT224L + 0x00051F20, // GFXMMU_LUT224H + 0x00770001, // GFXMMU_LUT225L + 0x000526A0, // GFXMMU_LUT225H + 0x00770001, // GFXMMU_LUT226L + 0x00052E20, // GFXMMU_LUT226H + 0x00770001, // GFXMMU_LUT227L + 0x000535A0, // GFXMMU_LUT227H + 0x00770001, // GFXMMU_LUT228L + 0x00053D20, // GFXMMU_LUT228H + 0x00770001, // GFXMMU_LUT229L + 0x000544A0, // GFXMMU_LUT229H + 0x00770001, // GFXMMU_LUT230L + 0x00054C20, // GFXMMU_LUT230H + 0x00770001, // GFXMMU_LUT231L + 0x000553A0, // GFXMMU_LUT231H + 0x00770001, // GFXMMU_LUT232L + 0x00055B20, // GFXMMU_LUT232H + 0x00770001, // GFXMMU_LUT233L + 0x000562A0, // GFXMMU_LUT233H + 0x00770001, // GFXMMU_LUT234L + 0x00056A20, // GFXMMU_LUT234H + 0x00770001, // GFXMMU_LUT235L + 0x000571A0, // GFXMMU_LUT235H + 0x00770001, // GFXMMU_LUT236L + 0x00057920, // GFXMMU_LUT236H + 0x00770001, // GFXMMU_LUT237L + 0x000580A0, // GFXMMU_LUT237H + 0x00770001, // GFXMMU_LUT238L + 0x00058820, // GFXMMU_LUT238H + 0x00770001, // GFXMMU_LUT239L + 0x00058FA0, // GFXMMU_LUT239H + 0x00780001, // GFXMMU_LUT240L + 0x00059720, // GFXMMU_LUT240H + 0x00770001, // GFXMMU_LUT241L + 0x00059EB0, // GFXMMU_LUT241H + 0x00770001, // GFXMMU_LUT242L + 0x0005A630, // GFXMMU_LUT242H + 0x00770001, // GFXMMU_LUT243L + 0x0005ADB0, // GFXMMU_LUT243H + 0x00770001, // GFXMMU_LUT244L + 0x0005B530, // GFXMMU_LUT244H + 0x00770001, // GFXMMU_LUT245L + 0x0005BCB0, // GFXMMU_LUT245H + 0x00770001, // GFXMMU_LUT246L + 0x0005C430, // GFXMMU_LUT246H + 0x00770001, // GFXMMU_LUT247L + 0x0005CBB0, // GFXMMU_LUT247H + 0x00770001, // GFXMMU_LUT248L + 0x0005D330, // GFXMMU_LUT248H + 0x00770001, // GFXMMU_LUT249L + 0x0005DAB0, // GFXMMU_LUT249H + 0x00770001, // GFXMMU_LUT250L + 0x0005E230, // GFXMMU_LUT250H + 0x00770001, // GFXMMU_LUT251L + 0x0005E9B0, // GFXMMU_LUT251H + 0x00770001, // GFXMMU_LUT252L + 0x0005F130, // GFXMMU_LUT252H + 0x00770001, // GFXMMU_LUT253L + 0x0005F8B0, // GFXMMU_LUT253H + 0x00770001, // GFXMMU_LUT254L + 0x00060030, // GFXMMU_LUT254H + 0x00770001, // GFXMMU_LUT255L + 0x000607B0, // GFXMMU_LUT255H + 0x00770001, // GFXMMU_LUT256L + 0x00060F30, // GFXMMU_LUT256H + 0x00770001, // GFXMMU_LUT257L + 0x000616B0, // GFXMMU_LUT257H + 0x00770001, // GFXMMU_LUT258L + 0x00061E30, // GFXMMU_LUT258H + 0x00770001, // GFXMMU_LUT259L + 0x000625B0, // GFXMMU_LUT259H + 0x00770001, // GFXMMU_LUT260L + 0x00062D30, // GFXMMU_LUT260H + 0x00770001, // GFXMMU_LUT261L + 0x000634B0, // GFXMMU_LUT261H + 0x00770001, // GFXMMU_LUT262L + 0x00063C30, // GFXMMU_LUT262H + 0x00770001, // GFXMMU_LUT263L + 0x000643B0, // GFXMMU_LUT263H + 0x00770001, // GFXMMU_LUT264L + 0x00064B30, // GFXMMU_LUT264H + 0x00770001, // GFXMMU_LUT265L + 0x000652B0, // GFXMMU_LUT265H + 0x00770001, // GFXMMU_LUT266L + 0x00065A30, // GFXMMU_LUT266H + 0x00770001, // GFXMMU_LUT267L + 0x000661B0, // GFXMMU_LUT267H + 0x00770001, // GFXMMU_LUT268L + 0x00066930, // GFXMMU_LUT268H + 0x00770001, // GFXMMU_LUT269L + 0x000670B0, // GFXMMU_LUT269H + 0x00770001, // GFXMMU_LUT270L + 0x00067830, // GFXMMU_LUT270H + 0x00770001, // GFXMMU_LUT271L + 0x00067FB0, // GFXMMU_LUT271H + 0x00770001, // GFXMMU_LUT272L + 0x00068730, // GFXMMU_LUT272H + 0x00770001, // GFXMMU_LUT273L + 0x00068EB0, // GFXMMU_LUT273H + 0x00770001, // GFXMMU_LUT274L + 0x00069630, // GFXMMU_LUT274H + 0x00770001, // GFXMMU_LUT275L + 0x00069DB0, // GFXMMU_LUT275H + 0x00770001, // GFXMMU_LUT276L + 0x0006A530, // GFXMMU_LUT276H + 0x00770001, // GFXMMU_LUT277L + 0x0006ACB0, // GFXMMU_LUT277H + 0x00770001, // GFXMMU_LUT278L + 0x0006B430, // GFXMMU_LUT278H + 0x00770001, // GFXMMU_LUT279L + 0x0006BBB0, // GFXMMU_LUT279H + 0x00770001, // GFXMMU_LUT280L + 0x0006C330, // GFXMMU_LUT280H + 0x00770001, // GFXMMU_LUT281L + 0x0006CAB0, // GFXMMU_LUT281H + 0x00770001, // GFXMMU_LUT282L + 0x0006D230, // GFXMMU_LUT282H + 0x00770001, // GFXMMU_LUT283L + 0x0006D9B0, // GFXMMU_LUT283H + 0x00770001, // GFXMMU_LUT284L + 0x0006E130, // GFXMMU_LUT284H + 0x00760101, // GFXMMU_LUT285L + 0x0006E8A0, // GFXMMU_LUT285H + 0x00760101, // GFXMMU_LUT286L + 0x0006F000, // GFXMMU_LUT286H + 0x00760101, // GFXMMU_LUT287L + 0x0006F760, // GFXMMU_LUT287H + 0x00760101, // GFXMMU_LUT288L + 0x0006FEC0, // GFXMMU_LUT288H + 0x00760101, // GFXMMU_LUT289L + 0x00070620, // GFXMMU_LUT289H + 0x00760101, // GFXMMU_LUT290L + 0x00070D80, // GFXMMU_LUT290H + 0x00760101, // GFXMMU_LUT291L + 0x000714E0, // GFXMMU_LUT291H + 0x00760101, // GFXMMU_LUT292L + 0x00071C40, // GFXMMU_LUT292H + 0x00760101, // GFXMMU_LUT293L + 0x000723A0, // GFXMMU_LUT293H + 0x00760101, // GFXMMU_LUT294L + 0x00072B00, // GFXMMU_LUT294H + 0x00760101, // GFXMMU_LUT295L + 0x00073260, // GFXMMU_LUT295H + 0x00760101, // GFXMMU_LUT296L + 0x000739C0, // GFXMMU_LUT296H + 0x00760101, // GFXMMU_LUT297L + 0x00074120, // GFXMMU_LUT297H + 0x00760101, // GFXMMU_LUT298L + 0x00074880, // GFXMMU_LUT298H + 0x00760101, // GFXMMU_LUT299L + 0x00074FE0, // GFXMMU_LUT299H + 0x00760101, // GFXMMU_LUT300L + 0x00075740, // GFXMMU_LUT300H + 0x00760101, // GFXMMU_LUT301L + 0x00075EA0, // GFXMMU_LUT301H + 0x00760101, // GFXMMU_LUT302L + 0x00076600, // GFXMMU_LUT302H + 0x00750201, // GFXMMU_LUT303L + 0x00076D50, // GFXMMU_LUT303H + 0x00750201, // GFXMMU_LUT304L + 0x00077490, // GFXMMU_LUT304H + 0x00750201, // GFXMMU_LUT305L + 0x00077BD0, // GFXMMU_LUT305H + 0x00750201, // GFXMMU_LUT306L + 0x00078310, // GFXMMU_LUT306H + 0x00750201, // GFXMMU_LUT307L + 0x00078A50, // GFXMMU_LUT307H + 0x00750201, // GFXMMU_LUT308L + 0x00079190, // GFXMMU_LUT308H + 0x00750201, // GFXMMU_LUT309L + 0x000798D0, // GFXMMU_LUT309H + 0x00750201, // GFXMMU_LUT310L + 0x0007A010, // GFXMMU_LUT310H + 0x00750201, // GFXMMU_LUT311L + 0x0007A750, // GFXMMU_LUT311H + 0x00750201, // GFXMMU_LUT312L + 0x0007AE90, // GFXMMU_LUT312H + 0x00750201, // GFXMMU_LUT313L + 0x0007B5D0, // GFXMMU_LUT313H + 0x00750201, // GFXMMU_LUT314L + 0x0007BD10, // GFXMMU_LUT314H + 0x00750201, // GFXMMU_LUT315L + 0x0007C450, // GFXMMU_LUT315H + 0x00740301, // GFXMMU_LUT316L + 0x0007CB80, // GFXMMU_LUT316H + 0x00740301, // GFXMMU_LUT317L + 0x0007D2A0, // GFXMMU_LUT317H + 0x00740301, // GFXMMU_LUT318L + 0x0007D9C0, // GFXMMU_LUT318H + 0x00740301, // GFXMMU_LUT319L + 0x0007E0E0, // GFXMMU_LUT319H + 0x00740301, // GFXMMU_LUT320L + 0x0007E800, // GFXMMU_LUT320H + 0x00740301, // GFXMMU_LUT321L + 0x0007EF20, // GFXMMU_LUT321H + 0x00740301, // GFXMMU_LUT322L + 0x0007F640, // GFXMMU_LUT322H + 0x00740301, // GFXMMU_LUT323L + 0x0007FD60, // GFXMMU_LUT323H + 0x00740301, // GFXMMU_LUT324L + 0x00080480, // GFXMMU_LUT324H + 0x00740301, // GFXMMU_LUT325L + 0x00080BA0, // GFXMMU_LUT325H + 0x00740301, // GFXMMU_LUT326L + 0x000812C0, // GFXMMU_LUT326H + 0x00740301, // GFXMMU_LUT327L + 0x000819E0, // GFXMMU_LUT327H + 0x00730401, // GFXMMU_LUT328L + 0x000820F0, // GFXMMU_LUT328H + 0x00730401, // GFXMMU_LUT329L + 0x000827F0, // GFXMMU_LUT329H + 0x00730401, // GFXMMU_LUT330L + 0x00082EF0, // GFXMMU_LUT330H + 0x00730401, // GFXMMU_LUT331L + 0x000835F0, // GFXMMU_LUT331H + 0x00730401, // GFXMMU_LUT332L + 0x00083CF0, // GFXMMU_LUT332H + 0x00730401, // GFXMMU_LUT333L + 0x000843F0, // GFXMMU_LUT333H + 0x00730401, // GFXMMU_LUT334L + 0x00084AF0, // GFXMMU_LUT334H + 0x00730401, // GFXMMU_LUT335L + 0x000851F0, // GFXMMU_LUT335H + 0x00730401, // GFXMMU_LUT336L + 0x000858F0, // GFXMMU_LUT336H + 0x00730401, // GFXMMU_LUT337L + 0x00085FF0, // GFXMMU_LUT337H + 0x00720501, // GFXMMU_LUT338L + 0x000866E0, // GFXMMU_LUT338H + 0x00720501, // GFXMMU_LUT339L + 0x00086DC0, // GFXMMU_LUT339H + 0x00720501, // GFXMMU_LUT340L + 0x000874A0, // GFXMMU_LUT340H + 0x00720501, // GFXMMU_LUT341L + 0x00087B80, // GFXMMU_LUT341H + 0x00720501, // GFXMMU_LUT342L + 0x00088260, // GFXMMU_LUT342H + 0x00720501, // GFXMMU_LUT343L + 0x00088940, // GFXMMU_LUT343H + 0x00720501, // GFXMMU_LUT344L + 0x00089020, // GFXMMU_LUT344H + 0x00720501, // GFXMMU_LUT345L + 0x00089700, // GFXMMU_LUT345H + 0x00710601, // GFXMMU_LUT346L + 0x00089DD0, // GFXMMU_LUT346H + 0x00710601, // GFXMMU_LUT347L + 0x0008A490, // GFXMMU_LUT347H + 0x00710601, // GFXMMU_LUT348L + 0x0008AB50, // GFXMMU_LUT348H + 0x00710601, // GFXMMU_LUT349L + 0x0008B210, // GFXMMU_LUT349H + 0x00710601, // GFXMMU_LUT350L + 0x0008B8D0, // GFXMMU_LUT350H + 0x00710601, // GFXMMU_LUT351L + 0x0008BF90, // GFXMMU_LUT351H + 0x00710601, // GFXMMU_LUT352L + 0x0008C650, // GFXMMU_LUT352H + 0x00710601, // GFXMMU_LUT353L + 0x0008CD10, // GFXMMU_LUT353H + 0x00700701, // GFXMMU_LUT354L + 0x0008D3C0, // GFXMMU_LUT354H + 0x00700701, // GFXMMU_LUT355L + 0x0008DA60, // GFXMMU_LUT355H + 0x00700701, // GFXMMU_LUT356L + 0x0008E100, // GFXMMU_LUT356H + 0x00700701, // GFXMMU_LUT357L + 0x0008E7A0, // GFXMMU_LUT357H + 0x00700701, // GFXMMU_LUT358L + 0x0008EE40, // GFXMMU_LUT358H + 0x00700701, // GFXMMU_LUT359L + 0x0008F4E0, // GFXMMU_LUT359H + 0x00700701, // GFXMMU_LUT360L + 0x0008FB80, // GFXMMU_LUT360H + 0x006F0801, // GFXMMU_LUT361L + 0x00090210, // GFXMMU_LUT361H + 0x006F0801, // GFXMMU_LUT362L + 0x00090890, // GFXMMU_LUT362H + 0x006F0801, // GFXMMU_LUT363L + 0x00090F10, // GFXMMU_LUT363H + 0x006F0801, // GFXMMU_LUT364L + 0x00091590, // GFXMMU_LUT364H + 0x006F0801, // GFXMMU_LUT365L + 0x00091C10, // GFXMMU_LUT365H + 0x006F0801, // GFXMMU_LUT366L + 0x00092290, // GFXMMU_LUT366H + 0x006F0801, // GFXMMU_LUT367L + 0x00092910, // GFXMMU_LUT367H + 0x006E0901, // GFXMMU_LUT368L + 0x00092F80, // GFXMMU_LUT368H + 0x006E0901, // GFXMMU_LUT369L + 0x000935E0, // GFXMMU_LUT369H + 0x006E0901, // GFXMMU_LUT370L + 0x00093C40, // GFXMMU_LUT370H + 0x006E0901, // GFXMMU_LUT371L + 0x000942A0, // GFXMMU_LUT371H + 0x006E0901, // GFXMMU_LUT372L + 0x00094900, // GFXMMU_LUT372H + 0x006E0901, // GFXMMU_LUT373L + 0x00094F60, // GFXMMU_LUT373H + 0x006D0A01, // GFXMMU_LUT374L + 0x000955B0, // GFXMMU_LUT374H + 0x006D0A01, // GFXMMU_LUT375L + 0x00095BF0, // GFXMMU_LUT375H + 0x006D0A01, // GFXMMU_LUT376L + 0x00096230, // GFXMMU_LUT376H + 0x006D0A01, // GFXMMU_LUT377L + 0x00096870, // GFXMMU_LUT377H + 0x006D0A01, // GFXMMU_LUT378L + 0x00096EB0, // GFXMMU_LUT378H + 0x006C0B01, // GFXMMU_LUT379L + 0x000974E0, // GFXMMU_LUT379H + 0x006C0B01, // GFXMMU_LUT380L + 0x00097B00, // GFXMMU_LUT380H + 0x006C0B01, // GFXMMU_LUT381L + 0x00098120, // GFXMMU_LUT381H + 0x006C0B01, // GFXMMU_LUT382L + 0x00098740, // GFXMMU_LUT382H + 0x006C0B01, // GFXMMU_LUT383L + 0x00098D60, // GFXMMU_LUT383H + 0x006C0B01, // GFXMMU_LUT384L + 0x00099380, // GFXMMU_LUT384H + 0x006B0C01, // GFXMMU_LUT385L + 0x00099990, // GFXMMU_LUT385H + 0x006B0C01, // GFXMMU_LUT386L + 0x00099F90, // GFXMMU_LUT386H + 0x006B0C01, // GFXMMU_LUT387L + 0x0009A590, // GFXMMU_LUT387H + 0x006B0C01, // GFXMMU_LUT388L + 0x0009AB90, // GFXMMU_LUT388H + 0x006B0C01, // GFXMMU_LUT389L + 0x0009B190, // GFXMMU_LUT389H + 0x006A0D01, // GFXMMU_LUT390L + 0x0009B780, // GFXMMU_LUT390H + 0x006A0D01, // GFXMMU_LUT391L + 0x0009BD60, // GFXMMU_LUT391H + 0x006A0D01, // GFXMMU_LUT392L + 0x0009C340, // GFXMMU_LUT392H + 0x006A0D01, // GFXMMU_LUT393L + 0x0009C920, // GFXMMU_LUT393H + 0x006A0D01, // GFXMMU_LUT394L + 0x0009CF00, // GFXMMU_LUT394H + 0x00690E01, // GFXMMU_LUT395L + 0x0009D4D0, // GFXMMU_LUT395H + 0x00690E01, // GFXMMU_LUT396L + 0x0009DA90, // GFXMMU_LUT396H + 0x00690E01, // GFXMMU_LUT397L + 0x0009E050, // GFXMMU_LUT397H + 0x00690E01, // GFXMMU_LUT398L + 0x0009E610, // GFXMMU_LUT398H + 0x00690E01, // GFXMMU_LUT399L + 0x0009EBD0, // GFXMMU_LUT399H + 0x00690E01, // GFXMMU_LUT400L + 0x0009F190, // GFXMMU_LUT400H + 0x00690E01, // GFXMMU_LUT401L + 0x0009F750, // GFXMMU_LUT401H + 0x00680F01, // GFXMMU_LUT402L + 0x0009FD00, // GFXMMU_LUT402H + 0x00680F01, // GFXMMU_LUT403L + 0x000A02A0, // GFXMMU_LUT403H + 0x00680F01, // GFXMMU_LUT404L + 0x000A0840, // GFXMMU_LUT404H + 0x00680F01, // GFXMMU_LUT405L + 0x000A0DE0, // GFXMMU_LUT405H + 0x00671001, // GFXMMU_LUT406L + 0x000A1370, // GFXMMU_LUT406H + 0x00671001, // GFXMMU_LUT407L + 0x000A18F0, // GFXMMU_LUT407H + 0x00671001, // GFXMMU_LUT408L + 0x000A1E70, // GFXMMU_LUT408H + 0x00671001, // GFXMMU_LUT409L + 0x000A23F0, // GFXMMU_LUT409H + 0x00661101, // GFXMMU_LUT410L + 0x000A2960, // GFXMMU_LUT410H + 0x00661101, // GFXMMU_LUT411L + 0x000A2EC0, // GFXMMU_LUT411H + 0x00661101, // GFXMMU_LUT412L + 0x000A3420, // GFXMMU_LUT412H + 0x00661101, // GFXMMU_LUT413L + 0x000A3980, // GFXMMU_LUT413H + 0x00651201, // GFXMMU_LUT414L + 0x000A3ED0, // GFXMMU_LUT414H + 0x00651201, // GFXMMU_LUT415L + 0x000A4410, // GFXMMU_LUT415H + 0x00651201, // GFXMMU_LUT416L + 0x000A4950, // GFXMMU_LUT416H + 0x00641301, // GFXMMU_LUT417L + 0x000A4E80, // GFXMMU_LUT417H + 0x00641301, // GFXMMU_LUT418L + 0x000A53A0, // GFXMMU_LUT418H + 0x00641301, // GFXMMU_LUT419L + 0x000A58C0, // GFXMMU_LUT419H + 0x00641301, // GFXMMU_LUT420L + 0x000A5DE0, // GFXMMU_LUT420H + 0x00631401, // GFXMMU_LUT421L + 0x000A62F0, // GFXMMU_LUT421H + 0x00631401, // GFXMMU_LUT422L + 0x000A67F0, // GFXMMU_LUT422H + 0x00631401, // GFXMMU_LUT423L + 0x000A6CF0, // GFXMMU_LUT423H + 0x00621501, // GFXMMU_LUT424L + 0x000A71E0, // GFXMMU_LUT424H + 0x00621501, // GFXMMU_LUT425L + 0x000A76C0, // GFXMMU_LUT425H + 0x00621501, // GFXMMU_LUT426L + 0x000A7BA0, // GFXMMU_LUT426H + 0x00621501, // GFXMMU_LUT427L + 0x000A8080, // GFXMMU_LUT427H + 0x00611601, // GFXMMU_LUT428L + 0x000A8550, // GFXMMU_LUT428H + 0x00611601, // GFXMMU_LUT429L + 0x000A8A10, // GFXMMU_LUT429H + 0x00611601, // GFXMMU_LUT430L + 0x000A8ED0, // GFXMMU_LUT430H + 0x00601701, // GFXMMU_LUT431L + 0x000A9380, // GFXMMU_LUT431H + 0x00601701, // GFXMMU_LUT432L + 0x000A9820, // GFXMMU_LUT432H + 0x00601701, // GFXMMU_LUT433L + 0x000A9CC0, // GFXMMU_LUT433H + 0x005F1801, // GFXMMU_LUT434L + 0x000AA150, // GFXMMU_LUT434H + 0x005F1801, // GFXMMU_LUT435L + 0x000AA5D0, // GFXMMU_LUT435H + 0x005F1801, // GFXMMU_LUT436L + 0x000AAA50, // GFXMMU_LUT436H + 0x005E1901, // GFXMMU_LUT437L + 0x000AAEC0, // GFXMMU_LUT437H + 0x005E1901, // GFXMMU_LUT438L + 0x000AB320, // GFXMMU_LUT438H + 0x005D1A01, // GFXMMU_LUT439L + 0x000AB770, // GFXMMU_LUT439H + 0x005D1A01, // GFXMMU_LUT440L + 0x000ABBB0, // GFXMMU_LUT440H + 0x005D1A01, // GFXMMU_LUT441L + 0x000ABFF0, // GFXMMU_LUT441H + 0x005C1B01, // GFXMMU_LUT442L + 0x000AC420, // GFXMMU_LUT442H + 0x005C1B01, // GFXMMU_LUT443L + 0x000AC840, // GFXMMU_LUT443H + 0x005B1C01, // GFXMMU_LUT444L + 0x000ACC50, // GFXMMU_LUT444H + 0x005B1C01, // GFXMMU_LUT445L + 0x000AD050, // GFXMMU_LUT445H + 0x005B1C01, // GFXMMU_LUT446L + 0x000AD450, // GFXMMU_LUT446H + 0x005A1D01, // GFXMMU_LUT447L + 0x000AD840, // GFXMMU_LUT447H + 0x005A1D01, // GFXMMU_LUT448L + 0x000ADC20, // GFXMMU_LUT448H + 0x00591E01, // GFXMMU_LUT449L + 0x000ADFF0, // GFXMMU_LUT449H + 0x00591E01, // GFXMMU_LUT450L + 0x000AE3B0, // GFXMMU_LUT450H + 0x00581F01, // GFXMMU_LUT451L + 0x000AE760, // GFXMMU_LUT451H + 0x00581F01, // GFXMMU_LUT452L + 0x000AEB00, // GFXMMU_LUT452H + 0x00572001, // GFXMMU_LUT453L + 0x000AEE90, // GFXMMU_LUT453H + 0x00572001, // GFXMMU_LUT454L + 0x000AF210, // GFXMMU_LUT454H + 0x00562101, // GFXMMU_LUT455L + 0x000AF580, // GFXMMU_LUT455H + 0x00562101, // GFXMMU_LUT456L + 0x000AF8E0, // GFXMMU_LUT456H + 0x00552201, // GFXMMU_LUT457L + 0x000AFC30, // GFXMMU_LUT457H + 0x00552201, // GFXMMU_LUT458L + 0x000AFF70, // GFXMMU_LUT458H + 0x00542301, // GFXMMU_LUT459L + 0x000B02A0, // GFXMMU_LUT459H + 0x00542301, // GFXMMU_LUT460L + 0x000B05C0, // GFXMMU_LUT460H + 0x00532401, // GFXMMU_LUT461L + 0x000B08D0, // GFXMMU_LUT461H + 0x00522501, // GFXMMU_LUT462L + 0x000B0BC0, // GFXMMU_LUT462H + 0x00522501, // GFXMMU_LUT463L + 0x000B0EA0, // GFXMMU_LUT463H + 0x00512601, // GFXMMU_LUT464L + 0x000B1170, // GFXMMU_LUT464H + 0x00502701, // GFXMMU_LUT465L + 0x000B1420, // GFXMMU_LUT465H + 0x00502701, // GFXMMU_LUT466L + 0x000B16C0, // GFXMMU_LUT466H + 0x004F2801, // GFXMMU_LUT467L + 0x000B1950, // GFXMMU_LUT467H + 0x004E2901, // GFXMMU_LUT468L + 0x000B1BC0, // GFXMMU_LUT468H + 0x004D2A01, // GFXMMU_LUT469L + 0x000B1E10, // GFXMMU_LUT469H + 0x004D2A01, // GFXMMU_LUT470L + 0x000B2050, // GFXMMU_LUT470H + 0x004C2B01, // GFXMMU_LUT471L + 0x000B2280, // GFXMMU_LUT471H + 0x004B2C01, // GFXMMU_LUT472L + 0x000B2490, // GFXMMU_LUT472H + 0x004A2D01, // GFXMMU_LUT473L + 0x000B2680, // GFXMMU_LUT473H + 0x00492E01, // GFXMMU_LUT474L + 0x000B2850, // GFXMMU_LUT474H + 0x00482F01, // GFXMMU_LUT475L + 0x000B2A00, // GFXMMU_LUT475H + 0x00463101, // GFXMMU_LUT476L + 0x000B2B80, // GFXMMU_LUT476H + 0x00453201, // GFXMMU_LUT477L + 0x000B2CD0, // GFXMMU_LUT477H + 0x00433401, // GFXMMU_LUT478L + 0x000B2DF0, // GFXMMU_LUT478H + 0x00413601, // GFXMMU_LUT479L + 0x000B2ED0 // GFXMMU_LUT479H +}; + +#ifdef __cplusplus +} +#endif +#endif /*__ gfxmmu_lut_H */ + +/** + * @} + */ + +/** + * @} + */ diff --git a/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_internal.h b/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_internal.h new file mode 100644 index 000000000..cf3a1e8b6 --- /dev/null +++ b/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_internal.h @@ -0,0 +1,58 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TREZOR_HAL_DISPLAY_INTERNAL_H +#define TREZOR_HAL_DISPLAY_INTERNAL_H + +#include + +// Size of the physical frame buffer in bytes +// +// It's smaller than size of the virtual frame buffer +// due to used GFXMMU settings +#define PHYSICAL_FRAME_BUFFER_SIZE 184320 + +// Pitch (in pixels) of the virtual frame buffer +#define FRAME_BUFFER_PIXELS_PER_LINE 768 + +// Physical frame buffers in internal SRAM memory +// +// Both frame buffers layes in the fixed addresses that +// are shared between bootloaders and the firmware. +extern uint32_t physical_frame_buffer_0[PHYSICAL_FRAME_BUFFER_SIZE]; +extern uint32_t physical_frame_buffer_1[PHYSICAL_FRAME_BUFFER_SIZE]; + +// The current frame buffer selector at fixed memory address +// +// The variable address is shared between bootloaders and the firmware +extern uint32_t current_frame_buffer; + +// LCD orientations +#define LCD_ORIENTATION_PORTRAIT 0U +#define LCD_ORIENTATION_LANDSCAPE 1U +#define LCD_ORIENTATION_PORTRAIT_ROT180 2U +#define LCD_ORIENTATION_LANDSCAPE_ROT180 3U + +int32_t BSP_LCD_Init(uint32_t Instance, uint32_t Orientation); +int32_t BSP_LCD_Reinit(uint32_t Instance); +int32_t BSP_LCD_SetBrightness(uint32_t Instance, uint32_t Brightness); +int32_t BSP_LCD_DisplayOn(uint32_t Instance); +int32_t BSP_LCD_SetFrameBuffer(uint32_t Instance, uint32_t fb_addr); + +#endif // TREZOR_HAL_DISPLAY_INTERNAL_H diff --git a/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_ltdc_dsi.c b/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_ltdc_dsi.c new file mode 100644 index 000000000..fb3e11632 --- /dev/null +++ b/core/embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_ltdc_dsi.c @@ -0,0 +1,1562 @@ +/** + ****************************************************************************** + * @file stm32u5x9j_discovery_lcd.c + * @author MCD Application Team + * @brief This file includes the driver for Liquid Crystal Display (LCD) + module + * mounted on MB1829A board (ARGB8888 color format). + @verbatim + 1. How To use this driver: + -------------------------- + - This driver is used to drive directly in command mode a LCD TFT using the + DSI interface. + The following IPs are implied : DSI Host IP block working + in conjunction to the LTDC controller. + - This driver is linked by construction to LCD. + + 2. Driver description: + ---------------------- + + Initialization steps: + o Initialize the LCD using the BSP_LCD_Init() function. You can select + display orientation with "Orientation" parameter (portrait, landscape, + portrait with 180 degrees rotation or landscape with 180 degrees + rotation). + o Call BSP_LCD_GetXSize() and BSP_LCD_GetYsize() to get respectively + width and height in pixels of LCD in the current orientation. + o Call BSP_LCD_SetBrightness() and BSP_LCD_GetBrightness() to + respectively set and get LCD brightness. + o Call BSP_LCD_SetActiveLayer() to select the current active layer. + o Call BSP_LCD_GetFormat() to get LCD pixel format supported. + + + Display on LCD: + o Call BSP_LCD_DisplayOn() and BSP_LCD_DisplayOff() to respectively + switch on and switch off the LCD display. + o First, check that frame buffer is available using + BSP_LCD_IsFrameBufferAvailable(). o When frame buffer is available, modify it + using following functions: o Call BSP_LCD_WritePixel() and BSP_LCD_ReadPixel() + to respectively write and read a pixel. o Call BSP_LCD_DrawHLine() to draw a + horizontal line. o Call BSP_LCD_DrawVLine() to draw a vertical line. o Call + BSP_LCD_DrawBitmap() to draw a bitmap. o Call BSP_LCD_FillRect() to draw a + rectangle. o Call BSP_LCD_FillRGBRect() to draw a rectangle with RGB buffer. + o Call BSP_LCD_Refresh() to refresh LCD display. + + + De-initialization steps: + o De-initialize the LCD using the BSP_LCD_DeInit() function. + + @endverbatim + ****************************************************************************** + * @attention + * + * Copyright (c) 2023 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ + +/* Includes ------------------------------------------------------------------*/ +#include "colors.h" +#include "stdint.h" +#include "string.h" + +#include TREZOR_BOARD +#include STM32_HAL_H + +#include "display_internal.h" + +/* Common Error codes */ +#define BSP_ERROR_NONE 0 +#define BSP_ERROR_NO_INIT -1 +#define BSP_ERROR_WRONG_PARAM -2 +#define BSP_ERROR_BUSY -3 +#define BSP_ERROR_PERIPH_FAILURE -4 +#define BSP_ERROR_COMPONENT_FAILURE -5 +#define BSP_ERROR_UNKNOWN_FAILURE -6 +#define BSP_ERROR_UNKNOWN_COMPONENT -7 +#define BSP_ERROR_BUS_FAILURE -8 +#define BSP_ERROR_CLOCK_FAILURE -9 +#define BSP_ERROR_MSP_FAILURE -10 +#define BSP_ERROR_FEATURE_NOT_SUPPORTED -11 + +#define BSP_ERROR_BUS_ACKNOWLEDGE_FAILURE (-102) +/* Button user interrupt priority */ +#define BSP_BUTTON_USER_IT_PRIORITY \ + 0x0FUL /* Default is lowest priority level */ + +/* LCD interrupt priorities */ +#define BSP_LCD_GFXMMU_IT_PRIORITY \ + 0x0FUL /* Default is lowest priority level \ + */ +#define BSP_LCD_LTDC_IT_PRIORITY 0x0FUL /* Default is lowest priority level */ +#define BSP_LCD_DSI_IT_PRIORITY 0x0FUL /* Default is lowest priority level */ + +/* HSPI RAM interrupt priority */ +#define BSP_HSPI_RAM_IT_PRIORITY 0x0FUL /* Default is lowest priority level */ +#define LCD_PIXEL_FORMAT_ARGB8888 \ + 0x00000000U /*!< ARGB8888 LTDC pixel format \ + */ +#define LCD_PIXEL_FORMAT_RGB888 0x00000001U /*!< RGB888 LTDC pixel format */ +#define LCD_PIXEL_FORMAT_RGB565 0x00000002U /*!< RGB565 LTDC pixel format */ +#define LCD_PIXEL_FORMAT_ARGB1555 \ + 0x00000003U /*!< ARGB1555 LTDC pixel format \ + */ +#define LCD_PIXEL_FORMAT_ARGB4444 \ + 0x00000004U /*!< ARGB4444 LTDC pixel format \ + */ +#define LCD_PIXEL_FORMAT_L8 0x00000005U /*!< L8 LTDC pixel format */ +#define LCD_PIXEL_FORMAT_AL44 0x00000006U /*!< AL44 LTDC pixel format */ +#define LCD_PIXEL_FORMAT_AL88 0x00000007U /*!< AL88 LTDC pixel format */ +/* LCD instances */ +#define LCD_INSTANCES_NBR 1U + +#define DSI_POWERON_GPIO_PORT GPIOI +#define DSI_POWERON_GPIO_PIN GPIO_PIN_5 +#define DSI_POWERON_GPIO_CLOCK_ENABLE() __HAL_RCC_GPIOI_CLK_ENABLE() + +#define DSI_RESET_GPIO_PORT GPIOD +#define DSI_RESET_GPIO_PIN GPIO_PIN_5 +#define DSI_RESET_GPIO_CLOCK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE() + +#define VSYNC 1 +#define VBP 12 +#define VFP 50 +#define VACT 481 +#define HSYNC 2 +#define HBP 1 +#define HFP 1 +#define HACT 480 +#define LCD_WIDTH 480 +#define LCD_HEIGHT 480 + +#include "display_gfxmmu_lut.h" + +/** @addtogroup BSP + * @{ + */ + +/** @addtogroup STM32U5x9J_DISCOVERY + * @{ + */ + +/** @defgroup STM32U5x9J_DISCOVERY_LCD LCD + * @{ + */ + +/** @defgroup STM32U5x9J_DISCOVERY_LCD_Private_Defines LCD Private Constants + * @{ + */ + +/** + * @} + */ + +/** @defgroup STM32U5x9J_DISCOVERY_LCD_Private_Variables LCD Private Variables + * @{ + */ + +#if (USE_HAL_GFXMMU_REGISTER_CALLBACKS == 1) +static uint32_t LcdGfxmmu_IsMspCbValid[LCD_INSTANCES_NBR] = {0}; +#endif /* (USE_HAL_GFXMMU_REGISTER_CALLBACKS == 1) */ + +#if (USE_HAL_LTDC_REGISTER_CALLBACKS == 1) +static uint32_t LcdLtdc_IsMspCbValid[LCD_INSTANCES_NBR] = {0}; +#endif /* (USE_HAL_LTDC_REGISTER_CALLBACKS == 1) */ + +#if (USE_HAL_DSI_REGISTER_CALLBACKS == 1) +static uint32_t LcdDsi_IsMspCbValid[LCD_INSTANCES_NBR] = {0}; +#endif /* (USE_HAL_DSI_REGISTER_CALLBACKS == 1) */ + +GFXMMU_HandleTypeDef hlcd_gfxmmu = {0}; +LTDC_HandleTypeDef hlcd_ltdc = {0}; +DSI_HandleTypeDef hlcd_dsi = {0}; +static DSI_VidCfgTypeDef DSIVidCfg = {0}; + +/** + * @} + */ + +/** @defgroup STM32U5x9J_DISCOVERY_LCD_Private_FunctionPrototypes LCD Private + * Function Prototypes + * @{ + */ +static int32_t LCD_Init(void); +static int32_t LCD_DeInit(void); + +static void GFXMMU_MspInit(GFXMMU_HandleTypeDef *hgfxmmu); +static void GFXMMU_MspDeInit(GFXMMU_HandleTypeDef *hgfxmmu); +static void LTDC_MspInit(LTDC_HandleTypeDef *hltdc); +static void LTDC_MspDeInit(LTDC_HandleTypeDef *hltdc); +static void DSI_MspInit(DSI_HandleTypeDef *hdsi); +static void DSI_MspDeInit(DSI_HandleTypeDef *hdsi); +#if (USE_HAL_DSI_REGISTER_CALLBACKS == 1) +static void DSI_EndOfRefreshCallback(DSI_HandleTypeDef *hdsi); +#endif /* (USE_HAL_DSI_REGISTER_CALLBACKS == 1) */ +/** + * @} + */ + +/** @addtogroup STM32U5x9J_DISCOVERY_LCD_Exported_Functions + * @{ + */ +/** + * @brief Initialize the LCD. + * @param Instance LCD Instance. + * @param Orientation LCD_ORIENTATION_PORTRAIT, LCD_ORIENTATION_LANDSCAPE, + * LCD_ORIENTATION_PORTRAIT_ROT180 or + * LCD_ORIENTATION_LANDSCAPE_ROT180. + * @retval BSP status. + */ +int32_t BSP_LCD_Init(uint32_t Instance, uint32_t Orientation) { + int32_t status = BSP_ERROR_NONE; + + if ((Instance >= LCD_INSTANCES_NBR) || + (Orientation > LCD_ORIENTATION_LANDSCAPE_ROT180)) { + status = BSP_ERROR_WRONG_PARAM; + } else if ((Orientation == LCD_ORIENTATION_LANDSCAPE) || + (Orientation == LCD_ORIENTATION_LANDSCAPE_ROT180)) { + status = BSP_ERROR_FEATURE_NOT_SUPPORTED; + } else { + if (LCD_Init() != 0) { + status = BSP_ERROR_PERIPH_FAILURE; + } + } + + return status; +} + +/** + * @brief De-Initialize the LCD. + * @param Instance LCD Instance. + * @retval BSP status. + */ +int32_t BSP_LCD_DeInit(uint32_t Instance) { + int32_t status = BSP_ERROR_NONE; + + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + if (LCD_DeInit() != 0) { + status = BSP_ERROR_PERIPH_FAILURE; + } + } + + return status; +} + +/** + * @brief Set the display on. + * @param Instance LCD Instance. + * @retval BSP status. + */ +int32_t BSP_LCD_DisplayOn(uint32_t Instance) { + int32_t status = BSP_ERROR_NONE; + + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + /* Set the display on */ + if (HAL_DSI_ShortWrite(&hlcd_dsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, + DSI_SET_DISPLAY_ON, 0x00) != HAL_OK) { + status = BSP_ERROR_WRONG_PARAM; + } + } + + return status; +} + +/** + * @brief Set the display off. + * @param Instance LCD Instance. + * @retval BSP status. + */ +int32_t BSP_LCD_DisplayOff(uint32_t Instance) { + int32_t status = BSP_ERROR_NONE; + + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + /* Set the display off */ + if (HAL_DSI_ShortWrite(&hlcd_dsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, + DSI_SET_DISPLAY_OFF, 0x00) != HAL_OK) { + status = BSP_ERROR_WRONG_PARAM; + } + } + + return status; +} + +/** + * @brief Set the display brightness. + * @param Instance LCD Instance. + * @param Brightness [00: Min (black), 100 Max]. + * @retval BSP status. + */ +int32_t BSP_LCD_SetBrightness(uint32_t Instance, uint32_t Brightness) { + int32_t status; + + if ((Instance >= LCD_INSTANCES_NBR) || (Brightness > 100U)) { + status = BSP_ERROR_WRONG_PARAM; + } else { + status = BSP_ERROR_FEATURE_NOT_SUPPORTED; + } + + return status; +} + +/** + * @brief Get the display brightness. + * @param Instance LCD Instance. + * @param Brightness [00: Min (black), 100 Max]. + * @retval BSP status. + */ +int32_t BSP_LCD_GetBrightness(uint32_t Instance, uint32_t *Brightness) { + int32_t status; + + if ((Instance >= LCD_INSTANCES_NBR) || (Brightness == NULL)) { + status = BSP_ERROR_WRONG_PARAM; + } else { + status = BSP_ERROR_FEATURE_NOT_SUPPORTED; + } + + return status; +} + +/** + * @brief Get the LCD X size. + * @param Instance LCD Instance. + * @param Xsize LCD X size. + * @retval BSP status. + */ +int32_t BSP_LCD_GetXSize(uint32_t Instance, uint32_t *Xsize) { + int32_t status = BSP_ERROR_NONE; + + if ((Instance >= LCD_INSTANCES_NBR) || (Xsize == NULL)) { + status = BSP_ERROR_WRONG_PARAM; + } else { + /* Get the display Xsize */ + *Xsize = LCD_WIDTH; + } + + return status; +} + +/** + * @brief Get the LCD Y size. + * @param Instance LCD Instance. + * @param Ysize LCD Y size. + * @retval BSP status. + */ +int32_t BSP_LCD_GetYSize(uint32_t Instance, uint32_t *Ysize) { + int32_t status = BSP_ERROR_NONE; + + if ((Instance >= LCD_INSTANCES_NBR) || (Ysize == NULL)) { + status = BSP_ERROR_WRONG_PARAM; + } else { + /* Get the display Ysize */ + *Ysize = LCD_HEIGHT; + } + + return status; +} + +/** + * @brief Set the LCD active layer. + * @param Instance LCD Instance. + * @param LayerIndex Active layer index. + * @retval BSP status. + */ +int32_t BSP_LCD_SetActiveLayer(uint32_t Instance, uint32_t LayerIndex) { + int32_t status = BSP_ERROR_NONE; + + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + /* Nothing to do */ + UNUSED(LayerIndex); + } + + return status; +} + +/** + * @brief Get pixel format supported by LCD. + * @param Instance LCD Instance. + * @param Format Pointer on pixel format. + * @retval BSP status. + */ +int32_t BSP_LCD_GetFormat(uint32_t Instance, uint32_t *Format) { + int32_t status = BSP_ERROR_NONE; + + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + /* Get pixel format supported by LCD */ + *Format = LCD_PIXEL_FORMAT_ARGB8888; + } + + return status; +} + +void MX_GFXMMU_Reinit(GFXMMU_HandleTypeDef *hgfxmmu) { + /* Initialize GFXMMU */ + hgfxmmu->Instance = GFXMMU; + hgfxmmu->Init.BlocksPerLine = GFXMMU_192BLOCKS; + hgfxmmu->Init.DefaultValue = 0xFFFFFFFFU; + hgfxmmu->Init.Buffers.Buf0Address = (uint32_t)physical_frame_buffer_0; + hgfxmmu->Init.Buffers.Buf1Address = (uint32_t)physical_frame_buffer_1; + hgfxmmu->Init.Buffers.Buf2Address = 0; + hgfxmmu->Init.Buffers.Buf3Address = 0; +#if defined(GFXMMU_CR_CE) + hgfxmmu->Init.CachePrefetch.Activation = DISABLE; + hgfxmmu->Init.CachePrefetch.CacheLock = GFXMMU_CACHE_LOCK_DISABLE; + hgfxmmu->Init.CachePrefetch.CacheLockBuffer = + GFXMMU_CACHE_LOCK_BUFFER0; /* NU */ + hgfxmmu->Init.CachePrefetch.CacheForce = GFXMMU_CACHE_FORCE_ENABLE; /* NU */ + hgfxmmu->Init.CachePrefetch.OutterBufferability = + GFXMMU_OUTTER_BUFFERABILITY_DISABLE; + hgfxmmu->Init.CachePrefetch.OutterCachability = + GFXMMU_OUTTER_CACHABILITY_DISABLE; + hgfxmmu->Init.CachePrefetch.Prefetch = GFXMMU_PREFETCH_DISABLE; +#endif /* GFXMMU_CR_CE */ +#if defined(GFXMMU_CR_ACE) + hgfxmmu->Init.AddressCache.Activation = DISABLE; + hgfxmmu->Init.AddressCache.AddressCacheLockBuffer = + GFXMMU_ADDRESSCACHE_LOCK_BUFFER0; +#endif /* GFXMMU_CR_ACE */ + hgfxmmu->Init.Interrupts.Activation = DISABLE; + hgfxmmu->Init.Interrupts.UsedInterrupts = GFXMMU_AHB_MASTER_ERROR_IT; /* NU */ +} + +/** + * @brief MX GFXMMU initialization. + * @param hgfxmmu GFXMMU handle. + * @retval HAL status. + */ +__weak HAL_StatusTypeDef MX_GFXMMU_Init(GFXMMU_HandleTypeDef *hgfxmmu) { + MX_GFXMMU_Reinit(hgfxmmu); + return HAL_GFXMMU_Init(hgfxmmu); +} + +/** + * @brief MX LTDC clock configuration. + * @param hltdc LTDC handle. + * @retval HAL status. + */ +__weak HAL_StatusTypeDef MX_LTDC_ClockConfig(LTDC_HandleTypeDef *hltdc) { + RCC_PeriphCLKInitTypeDef PLL3InitPeriph = {0}; + + /* Prevent unused argument(s) compilation warning */ + UNUSED(hltdc); + + /* Start and configurre PLL3 */ + /* HSE = 16MHZ */ + /* 16/(M=4) = 4MHz input (min) */ + /* 4*(N=125) = 500MHz VCO (almost max) */ + /* 500/(P=8) = 62.5 for DSI ie exactly the lane byte clock*/ + /* 500/(R=24) = 20.83 for LTDC exact match with DSI bandwidth */ + PLL3InitPeriph.PeriphClockSelection = RCC_PERIPHCLK_LTDC; + PLL3InitPeriph.LtdcClockSelection = RCC_LTDCCLKSOURCE_PLL3; + PLL3InitPeriph.PLL3.PLL3M = 4; + PLL3InitPeriph.PLL3.PLL3N = 125; + PLL3InitPeriph.PLL3.PLL3P = 8; + PLL3InitPeriph.PLL3.PLL3Q = 8; + PLL3InitPeriph.PLL3.PLL3R = 24; + PLL3InitPeriph.PLL3.PLL3FRACN = 0; + PLL3InitPeriph.PLL3.PLL3RGE = RCC_PLLVCIRANGE_1; + PLL3InitPeriph.PLL3.PLL3ClockOut = RCC_PLL3_DIVR | RCC_PLL3_DIVP; + PLL3InitPeriph.PLL3.PLL3Source = RCC_PLLSOURCE_HSE; + return HAL_RCCEx_PeriphCLKConfig(&PLL3InitPeriph); +} + +void MX_LTDC_Reinit(LTDC_HandleTypeDef *hltdc) { + /* LTDC initialization */ + hltdc->Instance = LTDC; + hltdc->Init.HSPolarity = LTDC_HSPOLARITY_AL; + hltdc->Init.VSPolarity = LTDC_VSPOLARITY_AL; + hltdc->Init.DEPolarity = LTDC_DEPOLARITY_AL; + hltdc->Init.PCPolarity = LTDC_PCPOLARITY_IPC; + hltdc->Init.HorizontalSync = HSYNC - 1; + hltdc->Init.AccumulatedHBP = HSYNC + HBP - 1; + hltdc->Init.AccumulatedActiveW = HACT + HBP + HSYNC - 1; + hltdc->Init.TotalWidth = HACT + HBP + HFP + HSYNC - 1; + hltdc->Init.Backcolor.Red = 0; /* Not used default value */ + hltdc->Init.Backcolor.Green = 0; /* Not used default value */ + hltdc->Init.Backcolor.Blue = 0; /* Not used default value */ + hltdc->Init.Backcolor.Reserved = 0xFF; + + HAL_LTDCEx_StructInitFromVideoConfig(&hlcd_ltdc, &DSIVidCfg); +} + +/** + * @brief MX LTDC initialization. + * @param hltdc LTDC handle. + * @retval HAL status. + */ +__weak HAL_StatusTypeDef MX_LTDC_Init(LTDC_HandleTypeDef *hltdc) { + MX_LTDC_Reinit(hltdc); + + return HAL_LTDC_Init(hltdc); +} + +// HAL_StatusTypeDef MX_LTDC_ReConfigLayer(LTDC_HandleTypeDef *hltdc, uint32_t +// LayerIndex) +//{ +// LTDC_LayerCfgTypeDef LayerCfg = {0}; +// +///* LTDC layer configuration */ +// LayerCfg.WindowX0 = 0; +// LayerCfg.WindowX1 = LCD_WIDTH; +// LayerCfg.WindowY0 = 1; +// LayerCfg.WindowY1 = (uint32_t)LCD_HEIGHT + 1UL; +// LayerCfg.PixelFormat = LTDC_PIXEL_FORMAT_ARGB8888; +// LayerCfg.Alpha = 0xFF; /* NU default value */ +// LayerCfg.Alpha0 = 0; /* NU default value */ +// LayerCfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA; /* Not Used: default +// value */ LayerCfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA; /* Not +// Used: default value */ LayerCfg.FBStartAdress = +// GFXMMU_VIRTUAL_BUFFER0_BASE; LayerCfg.ImageWidth = +// FRAME_BUFFER_PIXELS_PER_LINE; /* Number of pixels per line in virtual frame +// buffer */ LayerCfg.ImageHeight = LCD_HEIGHT; LayerCfg.Backcolor.Red = 0; +// /* Not Used: default value */ LayerCfg.Backcolor.Green = 0; /* Not Used: +// default value */ LayerCfg.Backcolor.Blue = 0; /* Not Used: default value */ +// LayerCfg.Bac +// return HAL_LTDC_ConfigLayer(hltdc, &LayerCfg, LayerIndex);kcolor.Reserved = +// 0xFF; +//} + +/** + * @brief MX LTDC layer configuration. + * @param hltdc LTDC handle. + * @param LayerIndex LTDC layer index. + * @retval HAL status. + */ +__weak HAL_StatusTypeDef MX_LTDC_ConfigLayer(LTDC_HandleTypeDef *hltdc, + uint32_t LayerIndex, + uint32_t fb_addr) { + LTDC_LayerCfgTypeDef LayerCfg = {0}; + + /* LTDC layer configuration */ + LayerCfg.WindowX0 = 0; + LayerCfg.WindowX1 = LCD_WIDTH; + LayerCfg.WindowY0 = 1; + LayerCfg.WindowY1 = (uint32_t)LCD_HEIGHT + 1UL; + LayerCfg.PixelFormat = LTDC_PIXEL_FORMAT_ARGB8888; + LayerCfg.Alpha = 0xFF; /* NU default value */ + LayerCfg.Alpha0 = 0; /* NU default value */ + LayerCfg.BlendingFactor1 = + LTDC_BLENDING_FACTOR1_PAxCA; /* Not Used: default value */ + LayerCfg.BlendingFactor2 = + LTDC_BLENDING_FACTOR2_PAxCA; /* Not Used: default value */ + LayerCfg.FBStartAdress = fb_addr; + LayerCfg.ImageWidth = + FRAME_BUFFER_PIXELS_PER_LINE; /* Number of pixels per line in virtual + frame buffer */ + LayerCfg.ImageHeight = LCD_HEIGHT; + LayerCfg.Backcolor.Red = 0; /* Not Used: default value */ + LayerCfg.Backcolor.Green = 0; /* Not Used: default value */ + LayerCfg.Backcolor.Blue = 0; /* Not Used: default value */ + LayerCfg.Backcolor.Reserved = 0xFF; + return HAL_LTDC_ConfigLayer(hltdc, &LayerCfg, LayerIndex); +} + +/** + * @brief MX DSI initialization. + * @param hdsi DSI handle. + * @retval HAL status. + */ +HAL_StatusTypeDef MX_DSI_Reinit(DSI_HandleTypeDef *hdsi) { + /* DSI initialization */ + hdsi->Instance = DSI; + hdsi->Init.AutomaticClockLaneControl = DSI_AUTO_CLK_LANE_CTRL_DISABLE; + /* We have 1 data lane at 500Mbps => lane byte clock at 500/8 = 62,5 MHZ */ + /* We want TX escape clock at around 20MHz and under 20MHz so clock division + * is set to 4 */ + hdsi->Init.TXEscapeCkdiv = 4; + hdsi->Init.NumberOfLanes = DSI_TWO_DATA_LANES; + hdsi->Init.PHYFrequencyRange = DSI_DPHY_FRANGE_450MHZ_510MHZ; + hdsi->Init.PHYLowPowerOffset = 0; + + /* Configure the DSI for Video mode */ + DSIVidCfg.VirtualChannelID = 0; + DSIVidCfg.HSPolarity = DSI_HSYNC_ACTIVE_HIGH; + DSIVidCfg.VSPolarity = DSI_VSYNC_ACTIVE_HIGH; + DSIVidCfg.DEPolarity = DSI_DATA_ENABLE_ACTIVE_HIGH; + DSIVidCfg.ColorCoding = DSI_RGB888; + DSIVidCfg.Mode = DSI_VID_MODE_BURST; + DSIVidCfg.PacketSize = LCD_WIDTH; + DSIVidCfg.NullPacketSize = 0xFFFU; + DSIVidCfg.HorizontalSyncActive = HSYNC * 3; + DSIVidCfg.HorizontalBackPorch = HBP * 3; + DSIVidCfg.HorizontalLine = (HACT + HSYNC + HBP + HFP) * 3; + DSIVidCfg.VerticalSyncActive = VSYNC; + DSIVidCfg.VerticalBackPorch = VBP; + DSIVidCfg.VerticalFrontPorch = VFP; + DSIVidCfg.VerticalActive = VACT; + DSIVidCfg.LPCommandEnable = DSI_LP_COMMAND_ENABLE; + DSIVidCfg.LPLargestPacketSize = 64; + /* Specify for each region of the video frame, if the transmission of command + * in LP mode is allowed in this region */ + /* while streaming is active in video mode */ + DSIVidCfg.LPHorizontalFrontPorchEnable = DSI_LP_HFP_ENABLE; + DSIVidCfg.LPHorizontalBackPorchEnable = DSI_LP_HBP_ENABLE; + DSIVidCfg.LPVerticalActiveEnable = DSI_LP_VACT_ENABLE; + DSIVidCfg.LPVerticalFrontPorchEnable = DSI_LP_VFP_ENABLE; + DSIVidCfg.LPVerticalBackPorchEnable = DSI_LP_VBP_ENABLE; + DSIVidCfg.LPVerticalSyncActiveEnable = DSI_LP_VSYNC_ENABLE; + DSIVidCfg.FrameBTAAcknowledgeEnable = DSI_FBTAA_ENABLE; + DSIVidCfg.LooselyPacked = DSI_LOOSELY_PACKED_DISABLE; + + return HAL_OK; +} + +/** + * @brief MX DSI initialization. + * @param hdsi DSI handle. + * @retval HAL status. + */ +__weak HAL_StatusTypeDef MX_DSI_Init(DSI_HandleTypeDef *hdsi) { + DSI_PLLInitTypeDef PLLInit = {0}; + + /* DSI initialization */ + hdsi->Instance = DSI; + hdsi->Init.AutomaticClockLaneControl = DSI_AUTO_CLK_LANE_CTRL_DISABLE; + /* We have 1 data lane at 500Mbps => lane byte clock at 500/8 = 62,5 MHZ */ + /* We want TX escape clock at around 20MHz and under 20MHz so clock division + * is set to 4 */ + hdsi->Init.TXEscapeCkdiv = 4; + hdsi->Init.NumberOfLanes = DSI_TWO_DATA_LANES; + hdsi->Init.PHYFrequencyRange = DSI_DPHY_FRANGE_450MHZ_510MHZ; + hdsi->Init.PHYLowPowerOffset = 0; + + PLLInit.PLLNDIV = 125; + PLLInit.PLLIDF = 4; + PLLInit.PLLODF = 2; + PLLInit.PLLVCORange = DSI_DPHY_VCO_FRANGE_800MHZ_1GHZ; + PLLInit.PLLChargePump = DSI_PLL_CHARGE_PUMP_2000HZ_4400HZ; + PLLInit.PLLTuning = DSI_PLL_LOOP_FILTER_2000HZ_4400HZ; + + if (HAL_DSI_Init(hdsi, &PLLInit) != HAL_OK) { + return HAL_ERROR; + } + + if (HAL_DSI_SetGenericVCID(&hlcd_dsi, 0) != HAL_OK) { + return HAL_ERROR; + } + + /* Configure the DSI for Video mode */ + DSIVidCfg.VirtualChannelID = 0; + DSIVidCfg.HSPolarity = DSI_HSYNC_ACTIVE_HIGH; + DSIVidCfg.VSPolarity = DSI_VSYNC_ACTIVE_HIGH; + DSIVidCfg.DEPolarity = DSI_DATA_ENABLE_ACTIVE_HIGH; + DSIVidCfg.ColorCoding = DSI_RGB888; + DSIVidCfg.Mode = DSI_VID_MODE_BURST; + DSIVidCfg.PacketSize = LCD_WIDTH; + DSIVidCfg.NullPacketSize = 0xFFFU; + DSIVidCfg.HorizontalSyncActive = HSYNC * 3; + DSIVidCfg.HorizontalBackPorch = HBP * 3; + DSIVidCfg.HorizontalLine = (HACT + HSYNC + HBP + HFP) * 3; + DSIVidCfg.VerticalSyncActive = VSYNC; + DSIVidCfg.VerticalBackPorch = VBP; + DSIVidCfg.VerticalFrontPorch = VFP; + DSIVidCfg.VerticalActive = VACT; + DSIVidCfg.LPCommandEnable = DSI_LP_COMMAND_ENABLE; + DSIVidCfg.LPLargestPacketSize = 64; + /* Specify for each region of the video frame, if the transmission of command + * in LP mode is allowed in this region */ + /* while streaming is active in video mode */ + DSIVidCfg.LPHorizontalFrontPorchEnable = DSI_LP_HFP_ENABLE; + DSIVidCfg.LPHorizontalBackPorchEnable = DSI_LP_HBP_ENABLE; + DSIVidCfg.LPVerticalActiveEnable = DSI_LP_VACT_ENABLE; + DSIVidCfg.LPVerticalFrontPorchEnable = DSI_LP_VFP_ENABLE; + DSIVidCfg.LPVerticalBackPorchEnable = DSI_LP_VBP_ENABLE; + DSIVidCfg.LPVerticalSyncActiveEnable = DSI_LP_VSYNC_ENABLE; + DSIVidCfg.FrameBTAAcknowledgeEnable = DSI_FBTAA_ENABLE; + DSIVidCfg.LooselyPacked = DSI_LOOSELY_PACKED_DISABLE; + + /* Drive the display */ + if (HAL_DSI_ConfigVideoMode(&hlcd_dsi, &DSIVidCfg) != HAL_OK) { + return HAL_ERROR; + } + + return HAL_OK; +} + +/** + * @brief MX DMA2D initialization. + * @param hdma2d DMA2D handle. + * @param Mode DMA2D transfer mode. + * @param OffLine DMA2D output offset. + * @retval HAL status. + */ +__weak HAL_StatusTypeDef MX_DMA2D_Init(DMA2D_HandleTypeDef *hdma2d, + uint32_t Mode, uint32_t OffLine) { + /* Register to memory mode with ARGB8888 as color Mode */ + hdma2d->Instance = DMA2D; + hdma2d->Init.Mode = Mode; + hdma2d->Init.ColorMode = DMA2D_OUTPUT_ARGB8888; + hdma2d->Init.OutputOffset = OffLine; + hdma2d->Init.AlphaInverted = DMA2D_REGULAR_ALPHA; + hdma2d->Init.RedBlueSwap = DMA2D_RB_REGULAR; + hdma2d->Init.BytesSwap = DMA2D_BYTES_REGULAR; + hdma2d->Init.LineOffsetMode = DMA2D_LOM_PIXELS; + + /* DMA2D Initialization */ + return HAL_DMA2D_Init(hdma2d); +} + +#if (USE_HAL_GFXMMU_REGISTER_CALLBACKS == 1) +/** + * @brief Register Default LCD GFXMMU Msp Callbacks + * @retval BSP status + */ +int32_t BSP_LCD_GFXMMU_RegisterDefaultMspCallbacks(uint32_t Instance) { + int32_t status = BSP_ERROR_NONE; + + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + __HAL_GFXMMU_RESET_HANDLE_STATE(&hlcd_gfxmmu); + + /* Register default MspInit/MspDeInit Callback */ + if (HAL_GFXMMU_RegisterCallback(&hlcd_gfxmmu, HAL_GFXMMU_MSPINIT_CB_ID, + GFXMMU_MspInit) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else if (HAL_GFXMMU_RegisterCallback(&hlcd_gfxmmu, + HAL_GFXMMU_MSPDEINIT_CB_ID, + GFXMMU_MspDeInit) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else { + LcdGfxmmu_IsMspCbValid[Instance] = 1U; + } + } + + /* BSP status */ + return status; +} + +/** + * @brief Register LCD GFXMMU Msp Callback + * @param Callbacks pointer to LCD MspInit/MspDeInit callback functions + * @retval BSP status + */ +int32_t BSP_LCD_GFXMMU_RegisterMspCallbacks(uint32_t Instance, + BSP_LCD_GFXMMU_Cb_t *Callback) { + int32_t status = BSP_ERROR_NONE; + + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + __HAL_GFXMMU_RESET_HANDLE_STATE(&hlcd_gfxmmu); + + /* Register MspInit/MspDeInit Callbacks */ + if (HAL_GFXMMU_RegisterCallback(&hlcd_gfxmmu, HAL_GFXMMU_MSPINIT_CB_ID, + Callback->pMspGfxmmuInitCb) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else if (HAL_GFXMMU_RegisterCallback( + &hlcd_gfxmmu, HAL_GFXMMU_MSPDEINIT_CB_ID, + Callback->pMspGfxmmuDeInitCb) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else { + LcdGfxmmu_IsMspCbValid[Instance] = 1U; + } + } + + /* BSP status */ + return status; +} +#endif /* USE_HAL_GFXMMU_REGISTER_CALLBACKS */ + +#if (USE_HAL_LTDC_REGISTER_CALLBACKS == 1) +/** + * @brief Register Default LCD LTDC Msp Callbacks + * @retval BSP status + */ +int32_t BSP_LCD_LTDC_RegisterDefaultMspCallbacks(uint32_t Instance) { + int32_t status = BSP_ERROR_NONE; + + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + __HAL_LTDC_RESET_HANDLE_STATE(&hlcd_ltdc); + + /* Register default MspInit/MspDeInit Callback */ + if (HAL_LTDC_RegisterCallback(&hlcd_ltdc, HAL_LTDC_MSPINIT_CB_ID, + LTDC_MspInit) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else if (HAL_LTDC_RegisterCallback(&hlcd_ltdc, HAL_LTDC_MSPDEINIT_CB_ID, + LTDC_MspDeInit) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else { + LcdLtdc_IsMspCbValid[Instance] = 1U; + } + } + + /* BSP status */ + return status; +} + +/** + * @brief Register LCD LTDC Msp Callback + * @param Callbacks pointer to LCD MspInit/MspDeInit callback functions + * @retval BSP status + */ +int32_t BSP_LCD_LTDC_RegisterMspCallbacks(uint32_t Instance, + BSP_LCD_LTDC_Cb_t *Callback) { + int32_t status = BSP_ERROR_NONE; + + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + __HAL_LTDC_RESET_HANDLE_STATE(&hlcd_ltdc); + + /* Register MspInit/MspDeInit Callbacks */ + if (HAL_LTDC_RegisterCallback(&hlcd_ltdc, HAL_LTDC_MSPINIT_CB_ID, + Callback->pMspLtdcInitCb) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else if (HAL_LTDC_RegisterCallback(&hlcd_ltdc, HAL_LTDC_MSPDEINIT_CB_ID, + Callback->pMspLtdcDeInitCb) != + HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else { + LcdLtdc_IsMspCbValid[Instance] = 1U; + } + } + + /* BSP status */ + return status; +} +#endif /* USE_HAL_LTDC_REGISTER_CALLBACKS */ + +#if (USE_HAL_DSI_REGISTER_CALLBACKS == 1) +/** + * @brief Register Default LCD DSI Msp Callbacks + * @retval BSP status + */ +int32_t BSP_LCD_DSI_RegisterDefaultMspCallbacks(uint32_t Instance) { + int32_t status = BSP_ERROR_NONE; + + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + __HAL_DSI_RESET_HANDLE_STATE(&hlcd_dsi); + + /* Register default MspInit/MspDeInit Callback */ + if (HAL_DSI_RegisterCallback(&hlcd_dsi, HAL_DSI_MSPINIT_CB_ID, + DSI_MspInit) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else if (HAL_DSI_RegisterCallback(&hlcd_dsi, HAL_DSI_MSPDEINIT_CB_ID, + DSI_MspDeInit) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else { + LcdDsi_IsMspCbValid[Instance] = 1U; + } + } + + /* BSP status */ + return status; +} + +/** + * @brief Register LCD DSI Msp Callback + * @param Callbacks pointer to LCD MspInit/MspDeInit callback functions + * @retval BSP status + */ +int32_t BSP_LCD_DSI_RegisterMspCallbacks(uint32_t Instance, + BSP_LCD_DSI_Cb_t *Callback) { + int32_t status = BSP_ERROR_NONE; + + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + __HAL_DSI_RESET_HANDLE_STATE(&hlcd_dsi); + + /* Register MspInit/MspDeInit Callbacks */ + if (HAL_DSI_RegisterCallback(&hlcd_dsi, HAL_DSI_MSPINIT_CB_ID, + Callback->pMspDsiInitCb) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else if (HAL_DSI_RegisterCallback(&hlcd_dsi, HAL_DSI_MSPDEINIT_CB_ID, + Callback->pMspDsiDeInitCb) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else { + LcdDsi_IsMspCbValid[Instance] = 1U; + } + } + + /* BSP status */ + return status; +} +#endif /* USE_HAL_DSI_REGISTER_CALLBACKS */ + +#if (USE_HAL_DMA2D_REGISTER_CALLBACKS == 1) +/** + * @brief Register Default LCD DMA2D Msp Callbacks + * @retval BSP status + */ +int32_t BSP_LCD_DMA2D_RegisterDefaultMspCallbacks(uint32_t Instance) { + int32_t status = BSP_ERROR_NONE; + + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + __HAL_DMA2D_RESET_HANDLE_STATE(&hlcd_dma2d); + + /* Register default MspInit/MspDeInit Callback */ + if (HAL_DMA2D_RegisterCallback(&hlcd_dma2d, HAL_DMA2D_MSPINIT_CB_ID, + DMA2D_MspInit) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else if (HAL_DMA2D_RegisterCallback(&hlcd_dma2d, + HAL_DMA2D_MSPDEINIT_CB_ID, + DMA2D_MspDeInit) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else { + LcdDma2d_IsMspCbValid[Instance] = 1U; + } + } + + /* BSP status */ + return status; +} + +/** + * @brief Register LCD DMA2D Msp Callback + * @param Callbacks pointer to LCD MspInit/MspDeInit callback functions + * @retval BSP status + */ +int32_t BSP_LCD_DMA2D_RegisterMspCallbacks(uint32_t Instance, + BSP_LCD_DMA2D_Cb_t *Callback) { + int32_t status = BSP_ERROR_NONE; + + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + __HAL_DMA2D_RESET_HANDLE_STATE(&hlcd_dma2d); + + /* Register MspInit/MspDeInit Callbacks */ + if (HAL_DMA2D_RegisterCallback(&hlcd_dma2d, HAL_DMA2D_MSPINIT_CB_ID, + Callback->pMspDma2dInitCb) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else if (HAL_DMA2D_RegisterCallback( + &hlcd_dma2d, HAL_DMA2D_MSPDEINIT_CB_ID, + Callback->pMspDma2dDeInitCb) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else { + LcdDma2d_IsMspCbValid[Instance] = 1U; + } + } + + /* BSP status */ + return status; +} +#endif /* USE_HAL_DMA2D_REGISTER_CALLBACKS */ +/** + * @} + */ + +/** @defgroup STM32U5x9J_DISCOVERY_LCD_Private_Functions LCD Private Functions + * @{ + */ + +/** + * @brief Initialize LCD. + * @retval BSP status. + */ +static int32_t LCD_Init(void) { + int32_t status = BSP_ERROR_NONE; + uint32_t ErrorNumber = 0; + DSI_PHY_TimerTypeDef PhyTimers = {0}; + DSI_HOST_TimeoutTypeDef HostTimeouts = {0}; + + /***************/ + /* GFXMMU init */ + /***************/ +#if (USE_HAL_GFXMMU_REGISTER_CALLBACKS == 0) + GFXMMU_MspInit(&hlcd_gfxmmu); +#else + /* Register the GFXMMU MSP Callbacks */ + if (LcdGfxmmu_IsMspCbValid[0] == 0U) { + if (BSP_LCD_GFXMMU_RegisterDefaultMspCallbacks(0) != BSP_ERROR_NONE) { + status = BSP_ERROR_PERIPH_FAILURE; + } + } +#endif /* (USE_HAL_GFXMMU_REGISTER_CALLBACKS == 0) */ + + if (status == BSP_ERROR_NONE) { + /* GFXMMU peripheral initialization */ + if (MX_GFXMMU_Init(&hlcd_gfxmmu) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } + /* Initialize LUT */ + else if (HAL_GFXMMU_ConfigLut(&hlcd_gfxmmu, 0, LCD_WIDTH, + (uint32_t)&gfxmmu_lut) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else { + /* Disable non visible lines : from line 480 to 1023 */ + if (HAL_OK != HAL_GFXMMU_DisableLutLines(&hlcd_gfxmmu, LCD_WIDTH, 544)) { + status = BSP_ERROR_PERIPH_FAILURE; + } + } + } + + /************/ + /* DSI init */ + /************/ + if (status == BSP_ERROR_NONE) { +#if (USE_HAL_DSI_REGISTER_CALLBACKS == 0) + DSI_MspInit(&hlcd_dsi); +#else + /* Register the DSI MSP Callbacks */ + if (LcdDsi_IsMspCbValid[0] == 0U) { + if (BSP_LCD_DSI_RegisterDefaultMspCallbacks(0) != BSP_ERROR_NONE) { + status = BSP_ERROR_PERIPH_FAILURE; + } + } +#endif /* (USE_HAL_DSI_REGISTER_CALLBACKS == 0) */ + + if (status == BSP_ERROR_NONE) { + /* DSI peripheral initialization */ + if (MX_DSI_Init(&hlcd_dsi) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } + } + } + + /*********************/ + /* LCD configuration */ + /*********************/ + if (status == BSP_ERROR_NONE) { + PhyTimers.ClockLaneHS2LPTime = 11; + PhyTimers.ClockLaneLP2HSTime = 40; + PhyTimers.DataLaneHS2LPTime = 12; + PhyTimers.DataLaneLP2HSTime = 23; + PhyTimers.DataLaneMaxReadTime = 0; + PhyTimers.StopWaitTime = 7; + + if (HAL_DSI_ConfigPhyTimer(&hlcd_dsi, &PhyTimers) != HAL_OK) { + return 6; + } + + HostTimeouts.TimeoutCkdiv = 1; + HostTimeouts.HighSpeedTransmissionTimeout = 0; + HostTimeouts.LowPowerReceptionTimeout = 0; + HostTimeouts.HighSpeedReadTimeout = 0; + HostTimeouts.LowPowerReadTimeout = 0; + HostTimeouts.HighSpeedWriteTimeout = 0; + HostTimeouts.HighSpeedWritePrespMode = 0; + HostTimeouts.LowPowerWriteTimeout = 0; + HostTimeouts.BTATimeout = 0; + + if (HAL_DSI_ConfigHostTimeouts(&hlcd_dsi, &HostTimeouts) != HAL_OK) { + return 7; + } + + if (HAL_DSI_ConfigFlowControl(&hlcd_dsi, DSI_FLOW_CONTROL_BTA) != HAL_OK) { + return 7; + } + + /* Enable the DSI host */ + __HAL_DSI_ENABLE(&hlcd_dsi); + + /*************/ + /* LTDC init */ + /*************/ + if (status == BSP_ERROR_NONE) { + if (MX_LTDC_ClockConfig(&hlcd_ltdc) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else { +#if (USE_HAL_LTDC_REGISTER_CALLBACKS == 0) + LTDC_MspInit(&hlcd_ltdc); +#else + /* Register the LTDC MSP Callbacks */ + if (LcdLtdc_IsMspCbValid[0] == 0U) { + if (BSP_LCD_LTDC_RegisterDefaultMspCallbacks(0) != BSP_ERROR_NONE) { + status = BSP_ERROR_PERIPH_FAILURE; + } + } +#endif /* (USE_HAL_GFXMMU_REGISTER_CALLBACKS == 0) */ + + if (status == BSP_ERROR_NONE) { + /* LTDC peripheral initialization */ + if (MX_LTDC_Init(&hlcd_ltdc) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } else { + if (MX_LTDC_ConfigLayer(&hlcd_ltdc, LTDC_LAYER_1, + GFXMMU_VIRTUAL_BUFFER0_BASE_S) != HAL_OK) { + status = BSP_ERROR_PERIPH_FAILURE; + } + } + } + } + } + + /* Start DSI */ + if (HAL_DSI_Start(&(hlcd_dsi)) != HAL_OK) { + return 8; + } + + /* CMD Mode */ + uint8_t InitParam1[3] = {0xFF, 0x83, 0x79}; + if (HAL_DSI_LongWrite(&hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 3, 0xB9, + InitParam1) != HAL_OK) { + ErrorNumber++; + } + + /* SETPOWER */ + uint8_t InitParam2[16] = {0x44, 0x1C, 0x1C, 0x37, 0x57, 0x90, 0xD0, 0xE2, + 0x58, 0x80, 0x38, 0x38, 0xF8, 0x33, 0x34, 0x42}; + if (HAL_DSI_LongWrite(&hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 16, 0xB1, + InitParam2) != HAL_OK) { + ErrorNumber++; + } + + /* SETDISP */ + uint8_t InitParam3[9] = {0x80, 0x14, 0x0C, 0x30, 0x20, + 0x50, 0x11, 0x42, 0x1D}; + if (HAL_DSI_LongWrite(&hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 9, 0xB2, + InitParam3) != HAL_OK) { + ErrorNumber++; + } + + /* Set display cycle timing */ + uint8_t InitParam4[10] = {0x01, 0xAA, 0x01, 0xAF, 0x01, + 0xAF, 0x10, 0xEA, 0x1C, 0xEA}; + if (HAL_DSI_LongWrite(&hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 10, 0xB4, + InitParam4) != HAL_OK) { + ErrorNumber++; + } + + /* SETVCOM */ + uint8_t InitParam5[4] = {00, 00, 00, 0xC0}; + if (HAL_DSI_LongWrite(&hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 4, 0xC7, + InitParam5) != HAL_OK) { + ErrorNumber++; + } + + /* Set Panel Related Registers */ + if (HAL_DSI_ShortWrite(&hlcd_dsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, 0xCC, + 0x02) != HAL_OK) { + ErrorNumber++; + } + + if (HAL_DSI_ShortWrite(&hlcd_dsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, 0xD2, + 0x77) != HAL_OK) { + ErrorNumber++; + } + + uint8_t InitParam6[37] = {0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x08, 0x32, + 0x10, 0x01, 0x00, 0x01, 0x03, 0x72, 0x03, 0x72, + 0x00, 0x08, 0x00, 0x08, 0x33, 0x33, 0x05, 0x05, + 0x37, 0x05, 0x05, 0x37, 0x0A, 0x00, 0x00, 0x00, + 0x0A, 0x00, 0x01, 0x00, 0x0E}; + if (HAL_DSI_LongWrite(&hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 37, 0xD3, + InitParam6) != HAL_OK) { + ErrorNumber++; + } + + uint8_t InitParam7[34] = { + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x18, 0x18, + 0x18, 0x18, 0x19, 0x19, 0x01, 0x00, 0x03, 0x02, 0x05, 0x04, 0x07, 0x06, + 0x23, 0x22, 0x21, 0x20, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00}; + if (HAL_DSI_LongWrite(&hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 34, 0xD5, + InitParam7) != HAL_OK) { + ErrorNumber++; + } + + uint8_t InitParam8[32] = {0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x19, 0x19, 0x18, 0x18, 0x19, 0x19, 0x18, 0x18, + 0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00, 0x01, + 0x20, 0x21, 0x22, 0x23, 0x18, 0x18, 0x18, 0x18}; + if (HAL_DSI_LongWrite(&hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 35, 0xD6, + InitParam8) != HAL_OK) { + ErrorNumber++; + } + + /* SET GAMMA */ + uint8_t InitParam9[42] = { + 0x00, 0x16, 0x1B, 0x30, 0x36, 0x3F, 0x24, 0x40, 0x09, 0x0D, 0x0F, + 0x18, 0x0E, 0x11, 0x12, 0x11, 0x14, 0x07, 0x12, 0x13, 0x18, 0x00, + 0x17, 0x1C, 0x30, 0x36, 0x3F, 0x24, 0x40, 0x09, 0x0C, 0x0F, 0x18, + 0x0E, 0x11, 0x14, 0x11, 0x12, 0x07, 0x12, 0x14, 0x18}; + if (HAL_DSI_LongWrite(&hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 42, 0xE0, + InitParam9) != HAL_OK) { + ErrorNumber++; + } + + uint8_t InitParam10[3] = {0x2C, 0x2C, 00}; + if (HAL_DSI_LongWrite(&hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 3, 0xB6, + InitParam10) != HAL_OK) { + ErrorNumber++; + } + + if (HAL_DSI_ShortWrite(&hlcd_dsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, 0xBD, + 0x00) != HAL_OK) { + ErrorNumber++; + } + + uint8_t InitParam11[] = { + 0x01, 0x00, 0x07, 0x0F, 0x16, 0x1F, 0x27, 0x30, 0x38, 0x40, 0x47, + 0x4E, 0x56, 0x5D, 0x65, 0x6D, 0x74, 0x7D, 0x84, 0x8A, 0x90, 0x99, + 0xA1, 0xA9, 0xB0, 0xB6, 0xBD, 0xC4, 0xCD, 0xD4, 0xDD, 0xE5, 0xEC, + 0xF3, 0x36, 0x07, 0x1C, 0xC0, 0x1B, 0x01, 0xF1, 0x34, 0x00}; + if (HAL_DSI_LongWrite(&hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 42, 0xC1, + InitParam11) != HAL_OK) { + ErrorNumber++; + } + + if (HAL_DSI_ShortWrite(&hlcd_dsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, 0xBD, + 0x01) != HAL_OK) { + ErrorNumber++; + } + + uint8_t InitParam12[] = { + 0x00, 0x08, 0x0F, 0x16, 0x1F, 0x28, 0x31, 0x39, 0x41, 0x48, 0x51, + 0x59, 0x60, 0x68, 0x70, 0x78, 0x7F, 0x87, 0x8D, 0x94, 0x9C, 0xA3, + 0xAB, 0xB3, 0xB9, 0xC1, 0xC8, 0xD0, 0xD8, 0xE0, 0xE8, 0xEE, 0xF5, + 0x3B, 0x1A, 0xB6, 0xA0, 0x07, 0x45, 0xC5, 0x37, 0x00}; + if (HAL_DSI_LongWrite(&hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 42, 0xC1, + InitParam12) != HAL_OK) { + ErrorNumber++; + } + + if (HAL_DSI_ShortWrite(&hlcd_dsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, 0xBD, + 0x02) != HAL_OK) { + ErrorNumber++; + } + + uint8_t InitParam13[42] = { + 0x00, 0x09, 0x0F, 0x18, 0x21, 0x2A, 0x34, 0x3C, 0x45, 0x4C, 0x56, + 0x5E, 0x66, 0x6E, 0x76, 0x7E, 0x87, 0x8E, 0x95, 0x9D, 0xA6, 0xAF, + 0xB7, 0xBD, 0xC5, 0xCE, 0xD5, 0xDF, 0xE7, 0xEE, 0xF4, 0xFA, 0xFF, + 0x0C, 0x31, 0x83, 0x3C, 0x5B, 0x56, 0x1E, 0x5A, 0xFF}; + if (HAL_DSI_LongWrite(&hlcd_dsi, 0, DSI_DCS_LONG_PKT_WRITE, 42, 0xC1, + InitParam13) != HAL_OK) { + ErrorNumber++; + } + + if (HAL_DSI_ShortWrite(&hlcd_dsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, 0xBD, + 0x00) != HAL_OK) { + ErrorNumber++; + } + + /* Exit Sleep Mode*/ + if (HAL_DSI_ShortWrite(&hlcd_dsi, 0, DSI_DCS_SHORT_PKT_WRITE_P0, 0x11, + 0x00) != HAL_OK) { + ErrorNumber++; + } + + HAL_Delay(120); + + /* Display On */ + if (HAL_DSI_ShortWrite(&hlcd_dsi, 0, DSI_DCS_SHORT_PKT_WRITE_P0, 0x29, + 0x00) != HAL_OK) { + ErrorNumber++; + } + + HAL_Delay(120); + + if (ErrorNumber != 0U) { + status = BSP_ERROR_PERIPH_FAILURE; + } + } + + return status; +} + +/** + * @brief De-Initialize LCD. + * @retval BSP status. + */ +static int32_t LCD_DeInit(void) { + int32_t status = BSP_ERROR_NONE; + uint32_t ErrorNumber = 0; + + /* Disable DSI wrapper */ + __HAL_DSI_WRAPPER_DISABLE(&hlcd_dsi); + + /* Set display off */ + if (HAL_DSI_ShortWrite(&hlcd_dsi, 0, DSI_DCS_SHORT_PKT_WRITE_P1, + DSI_SET_DISPLAY_OFF, 0x00) != HAL_OK) { + ErrorNumber++; + } + + /* Wait before entering in sleep mode */ + HAL_Delay(2000); + + /* Put LCD in sleep mode */ + if (HAL_DSI_ShortWrite(&hlcd_dsi, 0, DSI_DCS_SHORT_PKT_WRITE_P0, + DSI_ENTER_SLEEP_MODE, 0x0) != HAL_OK) { + ErrorNumber++; + } + + HAL_Delay(120); + + /* De-initialize DSI */ + if (HAL_DSI_DeInit(&hlcd_dsi) != HAL_OK) { + ErrorNumber++; + } +#if (USE_HAL_DSI_REGISTER_CALLBACKS == 0) + DSI_MspDeInit(&hlcd_dsi); +#endif /* (USE_HAL_DSI_REGISTER_CALLBACKS == 0) */ + + /* De-initialize LTDC */ + if (HAL_LTDC_DeInit(&hlcd_ltdc) != HAL_OK) { + ErrorNumber++; + } +#if (USE_HAL_LTDC_REGISTER_CALLBACKS == 0) + LTDC_MspDeInit(&hlcd_ltdc); +#endif /* (USE_HAL_LTDC_REGISTER_CALLBACKS == 0) */ + + /* De-initialize GFXMMU */ + if (HAL_GFXMMU_DeInit(&hlcd_gfxmmu) != HAL_OK) { + ErrorNumber++; + } +#if (USE_HAL_GFXMMU_REGISTER_CALLBACKS == 0) + GFXMMU_MspDeInit(&hlcd_gfxmmu); +#endif /* (USE_HAL_GFXMMU_REGISTER_CALLBACKS == 0) */ + + if (ErrorNumber != 0U) { + status = BSP_ERROR_PERIPH_FAILURE; + } + + return status; +} + +/** + * @brief Initialize GFXMMU MSP. + * @param hgfxmmu GFXMMU handle + * @retval None + */ +static void GFXMMU_MspInit(GFXMMU_HandleTypeDef *hgfxmmu) { + /* Prevent unused argument(s) compilation warning */ + UNUSED(hgfxmmu); + + /* GFXMMU clock enable */ + __HAL_RCC_GFXMMU_CLK_ENABLE(); + + /* Enable GFXMMU interrupt */ + HAL_NVIC_SetPriority(GFXMMU_IRQn, BSP_LCD_GFXMMU_IT_PRIORITY, 0); + HAL_NVIC_EnableIRQ(GFXMMU_IRQn); +} + +/** + * @brief De-Initialize GFXMMU MSP. + * @param hgfxmmu GFXMMU handle + * @retval None + */ +static void GFXMMU_MspDeInit(GFXMMU_HandleTypeDef *hgfxmmu) { + /* Prevent unused argument(s) compilation warning */ + UNUSED(hgfxmmu); + + /* Disable GFXMMU interrupt */ + HAL_NVIC_DisableIRQ(GFXMMU_IRQn); + + /* GFXMMU clock disable */ + __HAL_RCC_GFXMMU_CLK_DISABLE(); +} + +/** + * @brief Initialize LTDC MSP. + * @param hltdc LTDC handle + * @retval None + */ +static void LTDC_MspInit(LTDC_HandleTypeDef *hltdc) { + /* Prevent unused argument(s) compilation warning */ + UNUSED(hltdc); + + /* Enable LCD clock */ + __HAL_RCC_LTDC_CLK_ENABLE(); + + /* Enable LTDC interrupt */ + HAL_NVIC_SetPriority(LTDC_IRQn, BSP_LCD_LTDC_IT_PRIORITY, 0); + HAL_NVIC_EnableIRQ(LTDC_IRQn); + + HAL_NVIC_SetPriority(LTDC_ER_IRQn, BSP_LCD_LTDC_IT_PRIORITY, 0); + HAL_NVIC_EnableIRQ(LTDC_ER_IRQn); +} + +/** + * @brief De-Initialize LTDC MSP. + * @param hltdc LTDC handle + * @retval None + */ +static void LTDC_MspDeInit(LTDC_HandleTypeDef *hltdc) { + /* Prevent unused argument(s) compilation warning */ + UNUSED(hltdc); + + /* Disable LTDC interrupts */ + HAL_NVIC_DisableIRQ(LTDC_ER_IRQn); + HAL_NVIC_DisableIRQ(LTDC_IRQn); + + /* LTDC clock disable */ + __HAL_RCC_LTDC_CLK_DISABLE(); +} + +/** + * @brief Initialize DSI MSP. + * @param hdsi DSI handle + * @retval None + */ +static void DSI_MspInit(DSI_HandleTypeDef *hdsi) { + RCC_PeriphCLKInitTypeDef PLL3InitPeriph = {0}; + RCC_PeriphCLKInitTypeDef DSIPHYInitPeriph = {0}; + GPIO_InitTypeDef GPIO_InitStruct = {0}; + + UNUSED(hdsi); + + /* Enable GPIOI & GPIOD clocks */ + __HAL_RCC_GPIOD_CLK_ENABLE(); + __HAL_RCC_GPIOI_CLK_ENABLE(); + + /* Configure DSI Reset pin */ + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_PULLDOWN; + GPIO_InitStruct.Pin = GPIO_PIN_5; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); + + /* Configure LCD Backlight Pin */ + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_PULLUP; + GPIO_InitStruct.Pin = GPIO_PIN_6; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(GPIOI, &GPIO_InitStruct); + + /* Enable DSI clock */ + __HAL_RCC_DSI_CLK_ENABLE(); + + /** ################ Set DSI clock to D-PHY source clock ################## + * **/ + + /* Start and configurre PLL3 */ + /* HSE = 16MHZ */ + /* 16/(M=4) = 4MHz input (min) */ + /* 4*(N=125) = 500MHz VCO (almost max) */ + /* 500/(P=8) = 62.5 for DSI ie exactly the lane byte clock*/ + + PLL3InitPeriph.PeriphClockSelection = RCC_PERIPHCLK_DSI; + PLL3InitPeriph.DsiClockSelection = RCC_DSICLKSOURCE_PLL3; + PLL3InitPeriph.PLL3.PLL3M = 4; + PLL3InitPeriph.PLL3.PLL3N = 125; + PLL3InitPeriph.PLL3.PLL3P = 8; + PLL3InitPeriph.PLL3.PLL3Q = 8; + PLL3InitPeriph.PLL3.PLL3R = 24; + PLL3InitPeriph.PLL3.PLL3FRACN = 0; + PLL3InitPeriph.PLL3.PLL3RGE = RCC_PLLVCIRANGE_1; + PLL3InitPeriph.PLL3.PLL3ClockOut = RCC_PLL3_DIVR | RCC_PLL3_DIVP; + PLL3InitPeriph.PLL3.PLL3Source = RCC_PLLSOURCE_HSE; + (void)HAL_RCCEx_PeriphCLKConfig(&PLL3InitPeriph); + + __HAL_RCC_DSI_CLK_ENABLE(); + + /* Switch to D-PHY source clock */ + /* Enable the DSI host */ + hlcd_dsi.Instance = DSI; + + __HAL_DSI_ENABLE(&hlcd_dsi); + + /* Enable the DSI PLL */ + __HAL_DSI_PLL_ENABLE(&hlcd_dsi); + + HAL_Delay(1); + + /* Enable the clock lane and the digital section of the D-PHY */ + hlcd_dsi.Instance->PCTLR |= (DSI_PCTLR_CKE | DSI_PCTLR_DEN); + + /* Set the TX escape clock division factor */ + hlcd_dsi.Instance->CCR = 4; + + HAL_Delay(1); + + /* Config DSI Clock to DSI PHY */ + DSIPHYInitPeriph.PeriphClockSelection = RCC_PERIPHCLK_DSI; + DSIPHYInitPeriph.DsiClockSelection = RCC_DSICLKSOURCE_DSIPHY; + + (void)HAL_RCCEx_PeriphCLKConfig(&DSIPHYInitPeriph); + + /* Reset */ + HAL_Delay(11); + HAL_GPIO_WritePin(GPIOD, GPIO_PIN_5, GPIO_PIN_SET); + HAL_Delay(150); + + /* Reset the TX escape clock division factor */ + hlcd_dsi.Instance->CCR &= ~DSI_CCR_TXECKDIV; + + /* Disable the DSI PLL */ + __HAL_DSI_PLL_DISABLE(&hlcd_dsi); + + /* Disable the DSI host */ + __HAL_DSI_DISABLE(&hlcd_dsi); + + /** ######################################################################### + * **/ + + /* Enable DSI NVIC interrupt */ + /* Default is lowest priority level */ + HAL_NVIC_SetPriority(DSI_IRQn, 0x0FUL, 0); + HAL_NVIC_EnableIRQ(DSI_IRQn); +} + +/** + * @brief De-Initialize DSI MSP. + * @param hdsi DSI handle + * @retval None + */ +static void DSI_MspDeInit(DSI_HandleTypeDef *hdsi) { + RCC_PeriphCLKInitTypeDef PLL3InitPeriph = {0}; + + UNUSED(hdsi); + + /* Switch to PLL3 before Disable */ + PLL3InitPeriph.PeriphClockSelection = RCC_PERIPHCLK_DSI; + PLL3InitPeriph.DsiClockSelection = RCC_DSICLKSOURCE_PLL3; + PLL3InitPeriph.PLL3.PLL3M = 4; + PLL3InitPeriph.PLL3.PLL3N = 125; + PLL3InitPeriph.PLL3.PLL3P = 8; + PLL3InitPeriph.PLL3.PLL3Q = 8; + PLL3InitPeriph.PLL3.PLL3R = 24; + PLL3InitPeriph.PLL3.PLL3FRACN = 0; + PLL3InitPeriph.PLL3.PLL3RGE = RCC_PLLVCIRANGE_1; + PLL3InitPeriph.PLL3.PLL3ClockOut = RCC_PLL3_DIVR | RCC_PLL3_DIVP; + PLL3InitPeriph.PLL3.PLL3Source = RCC_PLLSOURCE_HSE; + (void)HAL_RCCEx_PeriphCLKConfig(&PLL3InitPeriph); + + /* DSI clock disable */ + __HAL_RCC_DSI_CLK_DISABLE(); + + /** @brief Toggle Sw reset of DSI IP */ + __HAL_RCC_DSI_FORCE_RESET(); + __HAL_RCC_DSI_RELEASE_RESET(); + + /* Disable DSI interrupts */ + HAL_NVIC_DisableIRQ(DSI_IRQn); +} + +int32_t BSP_LCD_SetFrameBuffer(uint32_t Instance, uint32_t fb_addr) { + int32_t status = BSP_ERROR_NONE; + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + MX_LTDC_ConfigLayer(&hlcd_ltdc, 0, fb_addr); + } + + return status; +} + +int32_t BSP_LCD_Reinit(uint32_t Instance) { + int32_t status = BSP_ERROR_NONE; + if (Instance >= LCD_INSTANCES_NBR) { + status = BSP_ERROR_WRONG_PARAM; + } else { + // Switch to D-PHY source clock + // Enable the DSI host + hlcd_dsi.Instance = DSI; + + MX_GFXMMU_Reinit(&hlcd_gfxmmu); + MX_DSI_Reinit(&hlcd_dsi); + MX_LTDC_Reinit(&hlcd_ltdc); + } + + return status; +} diff --git a/core/embed/trezorhal/stm32u5/display/vg-2864 b/core/embed/trezorhal/stm32u5/display/vg-2864 new file mode 120000 index 000000000..88c9a8fad --- /dev/null +++ b/core/embed/trezorhal/stm32u5/display/vg-2864 @@ -0,0 +1 @@ +../../stm32f4/display/vg-2864 \ No newline at end of file diff --git a/core/embed/trezorhal/stm32u5/displays/dsi.h b/core/embed/trezorhal/stm32u5/displays/dsi.h index a717e4bb7..913adb369 100644 --- a/core/embed/trezorhal/stm32u5/displays/dsi.h +++ b/core/embed/trezorhal/stm32u5/displays/dsi.h @@ -3,10 +3,6 @@ #include STM32_HAL_H -#define MAX_DISPLAY_RESX 240 -#define MAX_DISPLAY_RESY 240 -#define DISPLAY_RESX 240 -#define DISPLAY_RESY 240 #define DISPLAY_COLOR_MODE DMA2D_OUTPUT_ARGB8888 #define DISPLAY_FRAMEBUFFER_WIDTH 768 #define DISPLAY_FRAMEBUFFER_HEIGHT 480 diff --git a/core/embed/trezorhal/stm32u5/dma2d_bitblt.c b/core/embed/trezorhal/stm32u5/dma2d_bitblt.c new file mode 120000 index 000000000..faeb9cc40 --- /dev/null +++ b/core/embed/trezorhal/stm32u5/dma2d_bitblt.c @@ -0,0 +1 @@ +../stm32f4/dma2d_bitblt.c \ No newline at end of file diff --git a/core/embed/trezorhal/unix/display-unix.c b/core/embed/trezorhal/unix/display-unix.c index f0442cf4a..51fe9cc8c 100644 --- a/core/embed/trezorhal/unix/display-unix.c +++ b/core/embed/trezorhal/unix/display-unix.c @@ -31,7 +31,7 @@ #include "common.h" #include "display-unix.h" -#include "display_interface.h" +#include "display.h" #include "profile.h" #define EMULATOR_BORDER 16 @@ -354,3 +354,5 @@ void display_clear_save(void) { uint8_t *display_get_wr_addr(void) { return (uint8_t *)DISPLAY_DATA_ADDRESS; } void display_finish_actions(void) {} + +void display_reinit(void) {} diff --git a/core/embed/trezorhal/unix/display_driver.c b/core/embed/trezorhal/unix/display_driver.c new file mode 100644 index 000000000..9844f6304 --- /dev/null +++ b/core/embed/trezorhal/unix/display_driver.c @@ -0,0 +1,461 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define _GNU_SOURCE + +#include + +#include +#include + +#include "common.h" +#include "profile.h" + +#define EMULATOR_BORDER 16 + +#if defined TREZOR_MODEL_T + +#ifdef TREZOR_EMULATOR_RASPI +#define WINDOW_WIDTH 480 +#define WINDOW_HEIGHT 320 +#define TOUCH_OFFSET_X 110 +#define TOUCH_OFFSET_Y 40 +#else +#define WINDOW_WIDTH 400 +#define WINDOW_HEIGHT 600 +#define TOUCH_OFFSET_X 80 +#define TOUCH_OFFSET_Y 110 +#endif + +#elif defined TREZOR_MODEL_1 + +#define WINDOW_WIDTH 200 +#define WINDOW_HEIGHT 340 +#define TOUCH_OFFSET_X 36 +#define TOUCH_OFFSET_Y 92 + +#elif defined TREZOR_MODEL_R + +#define WINDOW_WIDTH 193 +#define WINDOW_HEIGHT 339 +#define TOUCH_OFFSET_X 32 +#define TOUCH_OFFSET_Y 84 + +#elif defined TREZOR_MODEL_T3T1 + +#define WINDOW_WIDTH 400 +#define WINDOW_HEIGHT 600 +#define TOUCH_OFFSET_X 80 +#define TOUCH_OFFSET_Y 110 + +#else +#error Unknown Trezor model +#endif + +typedef struct { + // Current display orientation (0 or 180) + int orientation_angle; + // Current backlight level ranging from 0 to 255 + int backlight_level; + + SDL_Window *window; + SDL_Renderer *renderer; + SDL_Surface *buffer; + SDL_Texture *texture; + SDL_Texture *background; + SDL_Surface *prev_saved; + +#if DISPLAY_MONO + // SDL2 does not support 8bit surface/texture + // and we have to simulate it + uint8_t mono_framebuf[DISPLAY_RESX * DISPLAY_RESY]; +#endif + +} display_driver_t; + +static display_driver_t g_display_driver; + +//!@# TODO get rid of this... +int sdl_display_res_x = DISPLAY_RESX, sdl_display_res_y = DISPLAY_RESY; +int sdl_touch_offset_x, sdl_touch_offset_y; + +void display_deinit(void) { + display_driver_t *drv = &g_display_driver; + + SDL_FreeSurface(drv->prev_saved); + SDL_FreeSurface(drv->buffer); + if (drv->background != NULL) { + SDL_DestroyTexture(drv->background); + } + if (drv->texture != NULL) { + SDL_DestroyTexture(drv->texture); + } + if (drv->renderer != NULL) { + SDL_DestroyRenderer(drv->renderer); + } + if (drv->window != NULL) { + SDL_DestroyWindow(drv->window); + } + SDL_Quit(); +} + +void display_init(void) { + display_driver_t *drv = &g_display_driver; + + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + printf("%s\n", SDL_GetError()); + ensure(secfalse, "SDL_Init error"); + } + atexit(display_deinit); + + char *window_title = NULL; + char *window_title_alloc = NULL; + if (asprintf(&window_title_alloc, "Trezor^emu: %s", profile_name()) > 0) { + window_title = window_title_alloc; + } else { + window_title = "Trezor^emu"; + window_title_alloc = NULL; + } + + drv->window = + SDL_CreateWindow(window_title, SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH, WINDOW_HEIGHT, +#ifdef TREZOR_EMULATOR_RASPI + SDL_WINDOW_SHOWN | SDL_WINDOW_FULLSCREEN +#else + SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI +#endif + ); + free(window_title_alloc); + if (!drv->window) { + printf("%s\n", SDL_GetError()); + ensure(secfalse, "SDL_CreateWindow error"); + } + drv->renderer = SDL_CreateRenderer(drv->window, -1, SDL_RENDERER_SOFTWARE); + if (!drv->renderer) { + printf("%s\n", SDL_GetError()); + SDL_DestroyWindow(drv->window); + ensure(secfalse, "SDL_CreateRenderer error"); + } + SDL_SetRenderDrawColor(drv->renderer, 0, 0, 0, 255); + SDL_RenderClear(drv->renderer); + + drv->buffer = SDL_CreateRGBSurface(0, DISPLAY_RESX, DISPLAY_RESY, 16, 0xF800, + 0x07E0, 0x001F, 0x0000); + drv->texture = SDL_CreateTexture(drv->renderer, SDL_PIXELFORMAT_RGB565, + SDL_TEXTUREACCESS_STREAMING, DISPLAY_RESX, + DISPLAY_RESY); + SDL_SetTextureBlendMode(drv->texture, SDL_BLENDMODE_BLEND); +#ifdef __APPLE__ + // macOS Mojave SDL black screen workaround + SDL_PumpEvents(); + SDL_SetWindowSize(drv->window, WINDOW_WIDTH, WINDOW_HEIGHT); +#endif +#ifdef TREZOR_EMULATOR_RASPI +#include "background_raspi.h" + drv->background = IMG_LoadTexture_RW( + drv->renderer, + SDL_RWFromMem(background_raspi_jpg, background_raspi_jpg_len), 0); +#else +#if defined TREZOR_MODEL_T +#include "background_T.h" + drv->background = IMG_LoadTexture_RW( + drv->renderer, SDL_RWFromMem(background_T_jpg, background_T_jpg_len), 0); +#elif defined TREZOR_MODEL_1 +#include "background_1.h" + drv->background = IMG_LoadTexture_RW( + drv->renderer, SDL_RWFromMem(background_1_jpg, background_1_jpg_len), 0); +#elif defined TREZOR_MODEL_R +#include "background_T2B1.h" + drv->background = IMG_LoadTexture_RW( + drv->renderer, + SDL_RWFromMem(background_T2B1_png, background_T2B1_png_len), 0); +#endif +#endif + if (drv->background) { + SDL_SetTextureBlendMode(drv->background, SDL_BLENDMODE_NONE); + sdl_touch_offset_x = TOUCH_OFFSET_X; + sdl_touch_offset_y = TOUCH_OFFSET_Y; + } else { + SDL_SetWindowSize(drv->window, DISPLAY_RESX + 2 * EMULATOR_BORDER, + DISPLAY_RESY + 2 * EMULATOR_BORDER); + sdl_touch_offset_x = EMULATOR_BORDER; + sdl_touch_offset_y = EMULATOR_BORDER; + } +#if defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R + // T1 and TR do not have backlight capabilities in hardware, so + // setting its value here for emulator to avoid + // calling any `set_backlight` functions + drv->backlight_level = 255; +#else + drv->backlight_level = 0; +#endif +#ifdef TREZOR_EMULATOR_RASPI + drv->orientation_angle = 270; + SDL_ShowCursor(SDL_DISABLE); +#else + drv->orientation_angle = 0; +#endif +} + +void display_reinit(void) { + // not used +} + +void display_finish_actions(void) { + // not used +} + +int display_set_backlight(int level) { + display_driver_t *drv = &g_display_driver; + +#if defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R + level = 255; +#endif + + if (drv->backlight_level != level && level >= 0 && level <= 255) { + drv->backlight_level = level; + display_refresh(); + } + + return drv->backlight_level; +} + +int display_get_backlight(void) { + display_driver_t *drv = &g_display_driver; + return drv->backlight_level; +} + +int display_set_orientation(int angle) { + display_driver_t *drv = &g_display_driver; + if (angle != drv->orientation_angle) { +#if defined TREZOR_MODEL_T || defined TREZOR_MODEL_T3T1 + if (angle == 0 || angle == 90 || angle == 180 || angle == 270) { +#elif defined TREZOR_MODEL_1 || defined TREZOR_MODEL_R + if (angle == 0 || angle == 180) { +#else +#error Unknown Trezor model +#endif + drv->orientation_angle = angle; + display_refresh(); + } + } + return drv->orientation_angle; +} + +int display_get_orientation(void) { + display_driver_t *drv = &g_display_driver; + return drv->orientation_angle; +} + +#ifdef XFRAMEBUFFER +display_fb_info_t display_get_frame_buffer(void) { + display_driver_t *drv = &g_display_driver; + +#ifdef DISPLAY_MONO + display_fb_info_t fb = { + .ptr = drv->mono_framebuf, + .stride = DISPLAY_RESX, + }; +#else + display_fb_info_t fb = { + .ptr = drv->buffer->pixels, + .stride = DISPLAY_RESX * sizeof(uint16_t), + }; +#endif + + return fb; +} + +#else // XFRAMEBUFFER + +void display_wait_for_sync(void) { + // not used +} +#endif + +#ifdef DISPLAY_MONO +// Copies driver's monochromatic framebuffer into the RGB framebuffer used by +// SDL +static void copy_mono_framebuf(display_driver_t *drv) { + for (int y = 0; y < DISPLAY_RESY; y++) { + uint16_t *dst = + (uint16_t *)((uint8_t *)drv->buffer->pixels + drv->buffer->pitch * y); + uint8_t *src = &drv->mono_framebuf[y * DISPLAY_RESX]; + for (int x = 0; x < DISPLAY_RESX; x++) { + uint8_t lum = src[x] > 40 ? 255 : 0; + dst[x] = gl_color16_rgb(lum, lum, lum); + } + } +} +#endif + +void display_refresh(void) { + display_driver_t *drv = &g_display_driver; + + if (!drv->renderer) { + display_init(); + } + +#ifdef DISPLAY_MONO + copy_mono_framebuf(drv); +#endif + + if (drv->background) { + const SDL_Rect r = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT}; + SDL_RenderCopy(drv->renderer, drv->background, NULL, &r); + } else { + SDL_RenderClear(drv->renderer); + } + // Show the display buffer + SDL_UpdateTexture(drv->texture, NULL, drv->buffer->pixels, + drv->buffer->pitch); +#define BACKLIGHT_NORMAL 150 + SDL_SetTextureAlphaMod( + drv->texture, MIN(255, 255 * drv->backlight_level / BACKLIGHT_NORMAL)); + if (drv->background) { + const SDL_Rect r = {TOUCH_OFFSET_X, TOUCH_OFFSET_Y, DISPLAY_RESX, + DISPLAY_RESY}; + SDL_RenderCopyEx(drv->renderer, drv->texture, NULL, &r, + drv->orientation_angle, NULL, 0); + } else { + const SDL_Rect r = {EMULATOR_BORDER, EMULATOR_BORDER, DISPLAY_RESX, + DISPLAY_RESY}; + SDL_RenderCopyEx(drv->renderer, drv->texture, NULL, &r, + drv->orientation_angle, NULL, 0); + } + SDL_RenderPresent(drv->renderer); +} + +void display_set_compatible_settings(void) { + // not used +} + +#ifndef DISPLAY_MONO + +void display_fill(const gl_bitblt_t *bb) { + display_driver_t *drv = &g_display_driver; + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = + (uint8_t *)drv->buffer->pixels + (drv->buffer->pitch * bb_new.dst_y); + bb_new.dst_stride = drv->buffer->pitch; + + gl_rgb565_fill(&bb_new); +} + +void display_copy_rgb565(const gl_bitblt_t *bb) { + display_driver_t *drv = &g_display_driver; + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = + (uint8_t *)drv->buffer->pixels + (drv->buffer->pitch * bb_new.dst_y); + bb_new.dst_stride = drv->buffer->pitch; + + gl_rgb565_copy_rgb565(&bb_new); +} + +void display_copy_mono1p(const gl_bitblt_t *bb) { + display_driver_t *drv = &g_display_driver; + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = + (uint8_t *)drv->buffer->pixels + (drv->buffer->pitch * bb_new.dst_y); + bb_new.dst_stride = DISPLAY_RESX; + + gl_rgb565_copy_mono1p(&bb_new); +} + +void display_copy_mono4(const gl_bitblt_t *bb) { + display_driver_t *drv = &g_display_driver; + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = + (uint8_t *)drv->buffer->pixels + (drv->buffer->pitch * bb_new.dst_y); + bb_new.dst_stride = drv->buffer->pitch; + + gl_rgb565_copy_mono4(&bb_new); +} + +#else // DISPLAY_MONO + +void display_fill(const gl_bitblt_t *bb) { + display_driver_t *drv = &g_display_driver; + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = drv->mono_framebuf + (DISPLAY_RESX * bb_new.dst_y); + bb_new.dst_stride = DISPLAY_RESX; + + gl_mono8_fill(&bb_new); +} + +void display_copy_mono1p(const gl_bitblt_t *bb) { + display_driver_t *drv = &g_display_driver; + + gl_bitblt_t bb_new = *bb; + bb_new.dst_row = drv->mono_framebuf + (DISPLAY_RESX * bb_new.dst_y); + bb_new.dst_stride = DISPLAY_RESX; + + gl_mono8_copy_mono1p(&bb_new); +} + +#endif + +const char *display_save(const char *prefix) { + display_driver_t *drv = &g_display_driver; + + if (!drv->renderer) { + display_init(); + } + +#ifdef DISPLAY_MONO + copy_mono_framebuf(drv); +#endif + + static int count; + static char filename[256]; + // take a cropped view of the screen contents + const SDL_Rect rect = {0, 0, DISPLAY_RESX, DISPLAY_RESY}; + SDL_Surface *crop = SDL_CreateRGBSurface( + drv->buffer->flags, rect.w, rect.h, drv->buffer->format->BitsPerPixel, + drv->buffer->format->Rmask, drv->buffer->format->Gmask, + drv->buffer->format->Bmask, drv->buffer->format->Amask); + SDL_BlitSurface(drv->buffer, &rect, crop, NULL); + // compare with previous screen, skip if equal + if (drv->prev_saved != NULL) { + if (memcmp(drv->prev_saved->pixels, crop->pixels, crop->pitch * crop->h) == + 0) { + SDL_FreeSurface(crop); + return filename; + } + SDL_FreeSurface(drv->prev_saved); + } + // save to png + snprintf(filename, sizeof(filename), "%s%08d.png", prefix, count++); + IMG_SavePNG(crop, filename); + drv->prev_saved = crop; + return filename; +} + +void display_clear_save(void) { + display_driver_t *drv = &g_display_driver; + + SDL_FreeSurface(drv->prev_saved); + drv->prev_saved = NULL; +} diff --git a/core/embed/trezorhal/unix/dma2d_bitblt.c b/core/embed/trezorhal/unix/dma2d_bitblt.c new file mode 100644 index 000000000..c2fe9c71a --- /dev/null +++ b/core/embed/trezorhal/unix/dma2d_bitblt.c @@ -0,0 +1,22 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "dma2d_bitblt.h" + +void dma2d_wait(void) {} diff --git a/core/embed/trezorhal/xdisplay.h b/core/embed/trezorhal/xdisplay.h new file mode 100644 index 000000000..53cfb54e4 --- /dev/null +++ b/core/embed/trezorhal/xdisplay.h @@ -0,0 +1,156 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TREZORHAL_XDISPLAY_H +#define TREZORHAL_XDISPLAY_H + +#include +#include +#include "gl_bitblt.h" + +#include TREZOR_BOARD + +// This is a universal API for controlling different types of display +// controllers. +// +// Currently, following displays displays are supported +// +// VG-2864KSWEG01 - OLED Mono / 128x64 pixels / SPI +// - Model T1B1 / Model T2B1 +// +// UG-2828SWIG01 - OLED Mono / 128x128 pixels / Parallel +// - Early revisions of T2B1 +// +// ST7789V - TFT RGB / 240x240 pixels / Parallel +// - Model T2T1 / Model T3T1 +// +// ILI9341 - TFT RGB / 320x240 pixels / Parallel / LTDC + SPI +// - STM32F429I-DISC1 Discovery Board +// +// MIPI - +// - STM32U5A9J-DK Discovery Board + +// Fully initializes the display controller. +void display_init(void); + +// Called in application or bootloader to reinitialize an already initialized +// display controller without any distrubing visible effect (blinking, etc.). +void display_reinit(void); + +// Waits for any backround operations (such as DMA copying) and returns. +// +// The function provides a barrier when jumping between +// boardloader/bootloader and firmware. +void display_finish_actions(void); + +// Sets display backlight level ranging from 0 (off)..255 (maximum). +// +// The default backligt level is 0. Without settings it +// to some higher value the displayed pixels are not visible. +// Beware that his also applies to the emulator. +// +// Returns the set level (usually the same value or the +// closest value to the `level` argument) +int display_set_backlight(int level); + +// Gets current display level ranging from 0 (off)..255 (maximum). +int display_get_backlight(void); + +// Sets the display orientation. +// +// May accept one of following values: 0, 90, 180, 270 +// but accepted values are model-dependent. +// Default display orientation is always 0. +// +// Returns the set orientation +int display_set_orientation(int angle); + +// Gets the display's current orientation +// +// Returned value is one of 0, 90, 180, 270. +int display_get_orientation(void); + +#ifdef XFRAMEBUFFER + +typedef struct { + // Pointer to the top-left pixel + void *ptr; + // Stride in bytes + size_t stride; + +} display_fb_info_t; + +// Provides pointer to the inactive (writeable) framebuffer. +// +// If framebuffer is not available yet due to display refreshing etc., +// the function may block until the buffer is ready to write. +display_fb_info_t display_get_frame_buffer(void); + +#else // XFRAMEBUFFER + +// Waits for the vertical synchronization pulse. +// +// Used for synchronization with the display refresh cycle +// to achieve tearless UX if possible when not using a frame buffer. +void display_wait_for_sync(void); +#endif + +// Swaps the frame buffers +// +// The function waits for vertical synchronization and +// swaps the active (currently displayed) and the inactive frame buffers. +void display_refresh(void); + +// Sets display to the mode compatible with the legacy bootloader code. +// +// This is used when switching between the firmware and the bootloader. +void display_set_compatible_settings(void); + +// Following function define display's bitblt interface. +// +// These functions draw directly to to display or to the +// currently inactive framebuffer. +// +// bb->dst_row and bb->dst_stride must be 0 + +// Fills a rectangle with a solid color. +// This function is supported by all types of displays. +void display_fill(const gl_bitblt_t *bb); +// Copies an RGB565 bitmap. +// This function is supported by RGB displays only. +void display_copy_rgb565(const gl_bitblt_t *bb); +// Copies a MONO4 bitmap (supported only with RGB displays). +// This function is supported by RGB displays only. +void display_copy_mono4(const gl_bitblt_t *bb); +// Copies a MONO1P bitmap. +// This function is supported by all types of displays. +void display_copy_mono1p(const gl_bitblt_t *bb); + +#ifdef TREZOR_EMULATOR +// Save the screen content to a file. +// The function is available only on the emulator. +const char *display_save(const char *prefix); +void display_clear_save(void); +#endif + +// Adds some declarations needed to compile with the legacy code +// (will be removed with the display legacy code) +#include "xdisplay_legacy.h" + +#endif // TREZORHAL_XDISPLAY_H diff --git a/core/embed/trezorhal/xdisplay_legacy.c b/core/embed/trezorhal/xdisplay_legacy.c new file mode 100644 index 000000000..516f74640 --- /dev/null +++ b/core/embed/trezorhal/xdisplay_legacy.c @@ -0,0 +1,43 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "xdisplay_legacy.h" +#include "xdisplay.h" + +// This code emulates the legacy display interface and will be +// removed after final cleanup of display drivers when the legacy code +// is removed. + +int display_orientation(int angle) { + if (angle >= 0) { + return display_set_orientation(angle); + } else { + return display_get_orientation(); + } +} + +int display_backlight(int level) { + if (level >= 0) { + return display_set_backlight(level); + } else { + return display_get_backlight(); + } +} + +void display_sync(void) {} diff --git a/core/embed/trezorhal/xdisplay_legacy.h b/core/embed/trezorhal/xdisplay_legacy.h new file mode 100644 index 000000000..0fc4d75cc --- /dev/null +++ b/core/embed/trezorhal/xdisplay_legacy.h @@ -0,0 +1,56 @@ +/* + * This file is part of the Trezor project, https://trezor.io/ + * + * Copyright (c) SatoshiLabs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TREZORHAL_DISPLAY_LEGACY_H +#define TREZORHAL_DISPLAY_LEGACY_H + +#include +#include + +// These declarationscode emulates will be removed after the +// final cleanup of display drivers. They are here just to simplify +// integration with the legacy code. +// +// Most of these function are not called when NEW_RENDERING=1 +// and they are only needed to for succesfully code compilation + +#define DISPLAY_FRAMEBUFFER_WIDTH 768 +#define DISPLAY_FRAMEBUFFER_HEIGHT 480 +#define DISPLAY_FRAMEBUFFER_OFFSET_X 0 +#define DISPLAY_FRAMEBUFFER_OFFSET_Y 0 + +int display_orientation(int angle); +int display_backlight(int level); +void display_refresh(void); +void display_shift_window(uint16_t pixels); +uint16_t display_get_window_offset(void); +void display_pixeldata_dirty(void); +uint8_t* display_get_wr_addr(void); +void display_sync(void); +void display_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); +void display_pixeldata(uint16_t c); +uint32_t* display_get_fb_addr(void); + +void display_clear(void); +void display_text_render_buffer(const char* text, int textlen, int font, + buffer_text_t* buffer, int text_offset); + +#define PIXELDATA(c) display_pixeldata(c) + +#endif // TREZORHAL_DISPLAY_LEGACY_H diff --git a/core/embed/unix/main.c b/core/embed/unix/main.c index f7c6461ea..2acd5b528 100644 --- a/core/embed/unix/main.c +++ b/core/embed/unix/main.c @@ -37,6 +37,7 @@ #include #include +#include "display.h" #include "extmod/misc.h" #include "extmod/vfs_posix.h" #include "flash.h" @@ -481,6 +482,8 @@ MP_NOINLINE int main_(int argc, char **argv) { pre_process_options(argc, argv); + display_init(); + // Map trezor.flash to memory. flash_init(); flash_otp_init(); diff --git a/core/site_scons/boards/discovery.py b/core/site_scons/boards/discovery.py index 8f0738429..aa9ed0bbf 100644 --- a/core/site_scons/boards/discovery.py +++ b/core/site_scons/boards/discovery.py @@ -36,9 +36,20 @@ def configure( sources += [ "embed/models/model_D001_layout.c", ] - sources += [f"embed/trezorhal/stm32f4/displays/{display}"] - sources += ["embed/trezorhal/stm32f4/displays/ili9341_spi.c"] + + if "new_rendering" in features_wanted: + sources += [ + "embed/trezorhal/xdisplay_legacy.c", + "embed/trezorhal/stm32f4/display/stm32f429i-disc1/display_driver.c", + "embed/trezorhal/stm32f4/display/stm32f429i-disc1/display_ltdc.c", + "embed/trezorhal/stm32f4/display/stm32f429i-disc1/ili9341_spi.c", + ] + else: + sources += [f"embed/trezorhal/stm32f4/displays/{display}"] + sources += ["embed/trezorhal/stm32f4/displays/ili9341_spi.c"] + sources += ["embed/trezorhal/stm32f4/dma2d.c"] + sources += ["embed/trezorhal/stm32f4/dma2d_bitblt.c"] sources += [ "vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma2d.c" ] @@ -51,6 +62,11 @@ def configure( features_available.append("dma2d") features_available.append("framebuffer") + if "new_rendering" in features_wanted: + defines += ["XFRAMEBUFFER"] + features_available.append("xframebuffer") + features_available.append("display_rgb565") + if "input" in features_wanted: sources += ["embed/trezorhal/stm32f4/i2c.c"] sources += ["embed/trezorhal/stm32f4/touch/stmpe811.c"] @@ -71,4 +87,10 @@ def configure( env.get("ENV")["TREZOR_BOARD"] = board env.get("ENV")["MCU_TYPE"] = mcu + rust_defs = env.get("ENV")["RUST_INCLUDES"] + rust_defs += "-DFRAMEBUFFER;" + if "new_rendering" in features_wanted: + rust_defs += "-DXFRAMEBUFFER;" + env.get("ENV")["RUST_INCLUDES"] = rust_defs + return features_available diff --git a/core/site_scons/boards/discovery2.py b/core/site_scons/boards/discovery2.py index 5a7c2d32a..b15ebcabc 100644 --- a/core/site_scons/boards/discovery2.py +++ b/core/site_scons/boards/discovery2.py @@ -43,9 +43,18 @@ def configure( sources += [ "embed/models/model_D002_layout.c", ] - sources += [ - f"embed/trezorhal/stm32u5/displays/{display}", - ] + + if "new_rendering" in features_wanted: + sources += [ + "embed/trezorhal/xdisplay_legacy.c", + "embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_driver.c", + "embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_fb.c", + "embed/trezorhal/stm32u5/display/stm32u5a9j-dk/display_ltdc_dsi.c", + ] + else: + sources += [ + f"embed/trezorhal/stm32u5/displays/{display}", + ] if "input" in features_wanted: sources += [ @@ -85,11 +94,17 @@ def configure( defines += ["USE_DMA2D", "FRAMEBUFFER", "FRAMEBUFFER32BIT"] sources += [ "embed/trezorhal/stm32u5/dma2d.c", + "embed/trezorhal/stm32u5/dma2d_bitblt.c", ] features_available.append("dma2d") features_available.append("framebuffer") features_available.append("framebuffer32bit") + if "new_rendering" in features_wanted: + defines += ["XFRAMEBUFFER"] + features_available.append("xframebuffer") + features_available.append("display_rgba8888") + env.get("ENV")["TREZOR_BOARD"] = board env.get("ENV")["MCU_TYPE"] = mcu env.get("ENV")["LINKER_SCRIPT"] = linker_script @@ -97,4 +112,10 @@ def configure( defs = env.get("CPPDEFINES_IMPLICIT") defs += ["__ARM_FEATURE_CMSE=3"] + rust_defs = env.get("ENV")["RUST_INCLUDES"] + rust_defs += "-DFRAMEBUFFER;" + if "new_rendering" in features_wanted: + rust_defs += "-DXFRAMEBUFFER;" + env.get("ENV")["RUST_INCLUDES"] = rust_defs + return features_available diff --git a/core/site_scons/boards/trezor_r_v10.py b/core/site_scons/boards/trezor_r_v10.py index f1b9dd029..10570dc2c 100644 --- a/core/site_scons/boards/trezor_r_v10.py +++ b/core/site_scons/boards/trezor_r_v10.py @@ -17,6 +17,11 @@ def configure( board = "trezor_r_v10.h" display = "vg-2864ksweg01.c" + if "new_rendering" in features_wanted: + defines += ["XFRAMEBUFFER"] + features_available.append("xframebuffer") + features_available.append("display_mono") + mcu = "STM32F427xx" stm32f4_common_files(env, defines, sources, paths) @@ -36,7 +41,12 @@ def configure( sources += [ "embed/models/model_T2B1_layout.c", ] - sources += [f"embed/trezorhal/stm32f4/displays/{display}"] + + if "new_rendering" in features_wanted: + sources += ["embed/trezorhal/xdisplay_legacy.c"] + sources += ["embed/trezorhal/stm32f4/display/vg-2864/display_driver.c"] + else: + sources += [f"embed/trezorhal/stm32f4/displays/{display}"] sources += ["embed/trezorhal/stm32f4/i2c.c"] @@ -76,4 +86,9 @@ def configure( env.get("ENV")["TREZOR_BOARD"] = board env.get("ENV")["MCU_TYPE"] = mcu + if "new_rendering" in features_wanted: + rust_defs = env.get("ENV")["RUST_INCLUDES"] + rust_defs += "-DXFRAMEBUFFER;" + env.get("ENV")["RUST_INCLUDES"] = rust_defs + return features_available diff --git a/core/site_scons/boards/trezor_r_v3.py b/core/site_scons/boards/trezor_r_v3.py index 25e2c9dec..f2565ede1 100644 --- a/core/site_scons/boards/trezor_r_v3.py +++ b/core/site_scons/boards/trezor_r_v3.py @@ -17,6 +17,11 @@ def configure( board = "trezor_r_v3.h" display = "ug-2828tswig01.c" + if "new_rendering" in features_wanted: + defines += ["XFRAMEBUFFER"] + features_available.append("xframebuffer") + features_available.append("display_mono") + mcu = "STM32F427xx" stm32f4_common_files(env, defines, sources, paths) @@ -36,7 +41,12 @@ def configure( sources += [ "embed/models/model_T2B1_layout.c", ] - sources += [f"embed/trezorhal/stm32f4/displays/{display}"] + + if "new_rendering" in features_wanted: + sources += ["embed/trezorhal/xdisplay_legacy.c"] + sources += ["embed/trezorhal/stm32f4/display/ug-2828/display_driver.c"] + else: + sources += [f"embed/trezorhal/stm32f4/displays/{display}"] if "input" in features_wanted: sources += ["embed/trezorhal/stm32f4/button.c"] @@ -64,4 +74,9 @@ def configure( env.get("ENV")["TREZOR_BOARD"] = board env.get("ENV")["MCU_TYPE"] = mcu + if "new_rendering" in features_wanted: + rust_defs = env.get("ENV")["RUST_INCLUDES"] + rust_defs += "-DXFRAMEBUFFER;" + env.get("ENV")["RUST_INCLUDES"] = rust_defs + return features_available diff --git a/core/site_scons/boards/trezor_r_v4.py b/core/site_scons/boards/trezor_r_v4.py index 63dab36fc..e3461e5ee 100644 --- a/core/site_scons/boards/trezor_r_v4.py +++ b/core/site_scons/boards/trezor_r_v4.py @@ -17,6 +17,11 @@ def configure( board = "trezor_r_v4.h" display = "vg-2864ksweg01.c" + if "new_rendering" in features_wanted: + defines += ["XFRAMEBUFFER"] + features_available.append("xframebuffer") + features_available.append("display_mono") + mcu = "STM32F427xx" stm32f4_common_files(env, defines, sources, paths) @@ -36,7 +41,12 @@ def configure( sources += [ "embed/models/model_T2B1_layout.c", ] - sources += [f"embed/trezorhal/stm32f4/displays/{display}"] + + if "new_rendering" in features_wanted: + sources += ["embed/trezorhal/xdisplay_legacy.c"] + sources += ["embed/trezorhal/stm32f4/display/vg-2864/display_driver.c"] + else: + sources += [f"embed/trezorhal/stm32f4/displays/{display}"] if "input" in features_wanted: sources += ["embed/trezorhal/stm32f4/button.c"] @@ -60,4 +70,9 @@ def configure( env.get("ENV")["TREZOR_BOARD"] = board env.get("ENV")["MCU_TYPE"] = mcu + if "new_rendering" in features_wanted: + rust_defs = env.get("ENV")["RUST_INCLUDES"] + rust_defs += "-DXFRAMEBUFFER;" + env.get("ENV")["RUST_INCLUDES"] = rust_defs + return features_available diff --git a/core/site_scons/boards/trezor_r_v6.py b/core/site_scons/boards/trezor_r_v6.py index 6fe8f3044..3444c4bb1 100644 --- a/core/site_scons/boards/trezor_r_v6.py +++ b/core/site_scons/boards/trezor_r_v6.py @@ -17,6 +17,11 @@ def configure( board = "trezor_r_v6.h" display = "vg-2864ksweg01.c" + if "new_rendering" in features_wanted: + defines += ["XFRAMEBUFFER"] + features_available.append("xframebuffer") + features_available.append("display_mono") + mcu = "STM32F427xx" stm32f4_common_files(env, defines, sources, paths) @@ -36,7 +41,12 @@ def configure( sources += [ "embed/models/model_T2B1_layout.c", ] - sources += [f"embed/trezorhal/stm32f4/displays/{display}"] + + if "new_rendering" in features_wanted: + sources += ["embed/trezorhal/xdisplay_legacy.c"] + sources += ["embed/trezorhal/stm32f4/display/vg-2864/display_driver.c"] + else: + sources += [f"embed/trezorhal/stm32f4/displays/{display}"] if "input" in features_wanted: sources += ["embed/trezorhal/stm32f4/button.c"] @@ -60,4 +70,9 @@ def configure( env.get("ENV")["TREZOR_BOARD"] = board env.get("ENV")["MCU_TYPE"] = mcu + if "new_rendering" in features_wanted: + rust_defs = env.get("ENV")["RUST_INCLUDES"] + rust_defs += "-DXFRAMEBUFFER;" + env.get("ENV")["RUST_INCLUDES"] = rust_defs + return features_available diff --git a/core/site_scons/boards/trezor_t.py b/core/site_scons/boards/trezor_t.py index 1bf39befb..999018edb 100644 --- a/core/site_scons/boards/trezor_t.py +++ b/core/site_scons/boards/trezor_t.py @@ -18,6 +18,9 @@ def configure( hw_revision = 0 features_available.append("disp_i8080_8bit_dw") + if "new_rendering" in features_wanted: + features_available.append("display_rgb565") + mcu = "STM32F427xx" stm32f4_common_files(env, defines, sources, paths) @@ -37,20 +40,41 @@ def configure( sources += [ "embed/models/model_T2T1_layout.c", ] - sources += [f"embed/trezorhal/stm32f4/displays/{display}"] + if "new_rendering" in features_wanted: + sources += ["embed/trezorhal/xdisplay_legacy.c"] + sources += ["embed/trezorhal/stm32f4/display/st-7789/display_nofb.c"] + sources += ["embed/trezorhal/stm32f4/display/st-7789/display_driver.c"] + sources += ["embed/trezorhal/stm32f4/display/st-7789/display_io.c"] + sources += ["embed/trezorhal/stm32f4/display/st-7789/display_panel.c"] + sources += [ + "embed/trezorhal/stm32f4/display/st-7789/panels/tf15411a.c", + ] + sources += [ + "embed/trezorhal/stm32f4/display/st-7789/panels/154a.c", + ] + sources += [ + "embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2411.c", + ] + sources += [ + "embed/trezorhal/stm32f4/display/st-7789/panels/lx154a2422.c", + ] + + else: + sources += [f"embed/trezorhal/stm32f4/displays/{display}"] + sources += [ + "embed/trezorhal/stm32f4/displays/panels/tf15411a.c", + ] + sources += [ + "embed/trezorhal/stm32f4/displays/panels/154a.c", + ] + sources += [ + "embed/trezorhal/stm32f4/displays/panels/lx154a2411.c", + ] + sources += [ + "embed/trezorhal/stm32f4/displays/panels/lx154a2422.c", + ] + sources += ["embed/trezorhal/stm32f4/backlight_pwm.c"] - sources += [ - "embed/trezorhal/stm32f4/displays/panels/tf15411a.c", - ] - sources += [ - "embed/trezorhal/stm32f4/displays/panels/154a.c", - ] - sources += [ - "embed/trezorhal/stm32f4/displays/panels/lx154a2411.c", - ] - sources += [ - "embed/trezorhal/stm32f4/displays/panels/lx154a2422.c", - ] features_available.append("backlight") @@ -87,6 +111,7 @@ def configure( if "dma2d" in features_wanted: defines += ["USE_DMA2D"] sources += ["embed/trezorhal/stm32f4/dma2d.c"] + sources += ["embed/trezorhal/stm32f4/dma2d_bitblt.c"] sources += [ "vendor/micropython/lib/stm32lib/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma2d.c" ] diff --git a/core/site_scons/boards/trezor_t3t1_revE.py b/core/site_scons/boards/trezor_t3t1_revE.py index e005a17eb..0af6572ae 100644 --- a/core/site_scons/boards/trezor_t3t1_revE.py +++ b/core/site_scons/boards/trezor_t3t1_revE.py @@ -20,6 +20,11 @@ def configure( features_available.append("framebuffer") defines += ["FRAMEBUFFER"] + if "new_rendering" in features_wanted: + features_available.append("xframebuffer") + features_available.append("display_rgb565") + defines += ["XFRAMEBUFFER"] + mcu = "STM32U585xx" linker_script = "stm32u58" @@ -40,11 +45,23 @@ def configure( sources += [ "embed/models/model_T3T1_layout.c", ] - sources += [f"embed/trezorhal/stm32u5/displays/{display}"] + + if "new_rendering" in features_wanted: + sources += ["embed/trezorhal/xdisplay_legacy.c"] + sources += ["embed/trezorhal/stm32u5/display/st-7789/display_fb.c"] + sources += ["embed/trezorhal/stm32u5/display/st-7789/display_driver.c"] + sources += ["embed/trezorhal/stm32u5/display/st-7789/display_io.c"] + sources += ["embed/trezorhal/stm32u5/display/st-7789/display_panel.c"] + sources += [ + "embed/trezorhal/stm32u5/display/st-7789/panels/lx154a2422.c", + ] + else: + sources += [f"embed/trezorhal/stm32u5/displays/{display}"] + sources += [ + "embed/trezorhal/stm32u5/displays/panels/lx154a2422.c", + ] + sources += ["embed/trezorhal/stm32u5/backlight_pwm.c"] - sources += [ - "embed/trezorhal/stm32u5/displays/panels/lx154a2422.c", - ] env_constraints = env.get("CONSTRAINTS") if not (env_constraints and "limited_util_s" in env_constraints): @@ -94,6 +111,7 @@ def configure( if "dma2d" in features_wanted: defines += ["USE_DMA2D"] sources += ["embed/trezorhal/stm32u5/dma2d.c"] + sources += ["embed/trezorhal/stm32u5/dma2d_bitblt.c"] features_available.append("dma2d") if "optiga" in features_wanted: @@ -114,6 +132,8 @@ def configure( rust_defs = env.get("ENV")["RUST_INCLUDES"] rust_defs += "-DFRAMEBUFFER;" + if "new_rendering" in features_wanted: + rust_defs += "-DXFRAMEBUFFER;" env.get("ENV")["RUST_INCLUDES"] = rust_defs return features_available diff --git a/core/site_scons/boards/trezor_t3t1_v4.py b/core/site_scons/boards/trezor_t3t1_v4.py index f11949c2f..ba1dd672a 100644 --- a/core/site_scons/boards/trezor_t3t1_v4.py +++ b/core/site_scons/boards/trezor_t3t1_v4.py @@ -20,6 +20,11 @@ def configure( features_available.append("framebuffer") defines += ["FRAMEBUFFER"] + if "new_rendering" in features_wanted: + features_available.append("xframebuffer") + features_available.append("display_rgb565") + defines += ["XFRAMEBUFFER"] + mcu = "STM32U585xx" linker_script = "stm32u58" @@ -41,10 +46,24 @@ def configure( "embed/models/model_T3T1_layout.c", ] sources += [f"embed/trezorhal/stm32u5/displays/{display}"] + + if "new_rendering" in features_wanted: + sources += ["embed/trezorhal/xdisplay_legacy.c"] + sources += ["embed/trezorhal/stm32u5/display/st-7789/display_fb.c"] + sources += ["embed/trezorhal/stm32u5/display/st-7789/display_driver.c"] + sources += ["embed/trezorhal/stm32u5/display/st-7789/display_io.c"] + sources += ["embed/trezorhal/stm32u5/display/st-7789/display_panel.c"] + sources += [ + "embed/trezorhal/stm32u5/display/st-7789/panels/lx154a2422.c", + ] + + else: + sources += [f"embed/trezorhal/stm32u5/displays/{display}"] + sources += [ + "embed/trezorhal/stm32u5/displays/panels/lx154a2422.c", + ] + sources += ["embed/trezorhal/stm32u5/backlight_pwm.c"] - sources += [ - "embed/trezorhal/stm32u5/displays/panels/lx154a2422.c", - ] env_constraints = env.get("CONSTRAINTS") if not (env_constraints and "limited_util_s" in env_constraints): @@ -94,6 +113,7 @@ def configure( if "dma2d" in features_wanted: defines += ["USE_DMA2D"] sources += ["embed/trezorhal/stm32u5/dma2d.c"] + sources += ["embed/trezorhal/stm32u5/dma2d_bitblt.c"] features_available.append("dma2d") if "optiga" in features_wanted: @@ -114,6 +134,8 @@ def configure( rust_defs = env.get("ENV")["RUST_INCLUDES"] rust_defs += "-DFRAMEBUFFER;" + if "new_rendering" in features_wanted: + rust_defs += "-DXFRAMEBUFFER;" env.get("ENV")["RUST_INCLUDES"] = rust_defs return features_available From 1031132a77db57d9af1c83ea24874e4c09891a38 Mon Sep 17 00:00:00 2001 From: cepetr Date: Mon, 22 Jan 2024 16:09:21 +0100 Subject: [PATCH 02/14] refactor(core): improve tjpg interface --- core/embed/rust/src/ui/display/tjpgd.rs | 6 +- .../ui/model_tt/component/homescreen/mod.rs | 16 +-- .../model_tt/component/homescreen/render.rs | 12 +- rust/trezor-tjpgdec/src/lib.rs | 105 ++++++++++++------ 4 files changed, 93 insertions(+), 46 deletions(-) diff --git a/core/embed/rust/src/ui/display/tjpgd.rs b/core/embed/rust/src/ui/display/tjpgd.rs index 1fc6de345..734cad50c 100644 --- a/core/embed/rust/src/ui/display/tjpgd.rs +++ b/core/embed/rust/src/ui/display/tjpgd.rs @@ -20,7 +20,7 @@ pub fn jpeg(data: &[u8], pos: Point, scale: u8) { let mut inp = BufferInput(data); if let Ok(mut jd) = JDEC::new(&mut inp, pool) { let _ = jd.set_scale(scale); - let _ = jd.decomp(&mut out); + let _ = jd.decomp(&mut inp, &mut out); } } @@ -50,9 +50,9 @@ pub fn jpeg_test(data: &[u8]) -> bool { } let mut out = BlackHoleOutput; - let mut res = jd.decomp(&mut out); + let mut res = jd.decomp(&mut inp, &mut out); while res == Err(Error::Interrupted) { - res = jd.decomp(&mut out); + res = jd.decomp(&mut inp, &mut out); } res.is_ok() } else { diff --git a/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs b/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs index 1033139f5..ec5769e77 100644 --- a/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs +++ b/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs @@ -215,9 +215,9 @@ impl Component for Homescreen { if let Ok(data) = res { if is_image_jpeg(data.as_ref()) { - let mut input = BufferInput(data.as_ref()); + let input = BufferInput(data.as_ref()); let mut pool = BufferJpegWork::get_cleared(); - let mut hs_img = HomescreenJpeg::new(&mut input, pool.buffer.as_mut_slice()); + let mut hs_img = HomescreenJpeg::new(input, pool.buffer.as_mut_slice()); homescreen( &mut hs_img, &[text], @@ -241,9 +241,9 @@ impl Component for Homescreen { } if show_default { - let mut input = BufferInput(IMAGE_HOMESCREEN); + let input = BufferInput(IMAGE_HOMESCREEN); let mut pool = BufferJpegWork::get_cleared(); - let mut hs_img = HomescreenJpeg::new(&mut input, pool.buffer.as_mut_slice()); + let mut hs_img = HomescreenJpeg::new(input, pool.buffer.as_mut_slice()); homescreen( &mut hs_img, &[text], @@ -348,9 +348,9 @@ impl Component for Lockscreen<'_> { if let Ok(data) = res { if is_image_jpeg(data.as_ref()) { - let mut input = BufferInput(data.as_ref()); + let input = BufferInput(data.as_ref()); let mut pool = BufferJpegWork::get_cleared(); - let mut hs_img = HomescreenJpeg::new(&mut input, pool.buffer.as_mut_slice()); + let mut hs_img = HomescreenJpeg::new(input, pool.buffer.as_mut_slice()); homescreen_blurred(&mut hs_img, texts); show_default = false; } else if is_image_toif(data.as_ref()) { @@ -364,9 +364,9 @@ impl Component for Lockscreen<'_> { } if show_default { - let mut input = BufferInput(IMAGE_HOMESCREEN); + let input = BufferInput(IMAGE_HOMESCREEN); let mut pool = BufferJpegWork::get_cleared(); - let mut hs_img = HomescreenJpeg::new(&mut input, pool.buffer.as_mut_slice()); + let mut hs_img = HomescreenJpeg::new(input, pool.buffer.as_mut_slice()); homescreen_blurred(&mut hs_img, texts); } } 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 458f5aa35..b50b5f28b 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 @@ -86,14 +86,16 @@ pub trait HomescreenDecompressor { pub struct HomescreenJpeg<'i> { pub output: BufferOutput, - pub jdec: Option>, + pub input: BufferInput<'i>, + pub jdec: Option>, } impl<'i> HomescreenJpeg<'i> { - pub fn new(input: &'i mut BufferInput<'i>, pool: &'i mut [u8]) -> Self { + pub fn new(mut input: BufferInput<'i>, pool: &'i mut [u8]) -> Self { Self { output: BufferOutput::new(WIDTH, 16), - jdec: JDEC::new(input, pool).ok(), + jdec: JDEC::new(&mut input, pool).ok(), + input, } } } @@ -107,7 +109,9 @@ impl<'i> HomescreenDecompressor for HomescreenJpeg<'i> { } fn decompress(&mut self) { - self.jdec.as_mut().map(|dec| dec.decomp(&mut self.output)); + self.jdec + .as_mut() + .map(|dec| dec.decomp(&mut self.input, &mut self.output)); } fn get_data(&mut self) -> &mut BufferJpeg { diff --git a/rust/trezor-tjpgdec/src/lib.rs b/rust/trezor-tjpgdec/src/lib.rs index 39a2c890e..29671db58 100644 --- a/rust/trezor-tjpgdec/src/lib.rs +++ b/rust/trezor-tjpgdec/src/lib.rs @@ -85,7 +85,7 @@ pub enum Error { UnsupportedJpeg, } -pub struct JDEC<'i, 'p> { +pub struct JDEC<'p> { dctr: usize, dptr: usize, inbuf: &'p mut [u8], @@ -113,8 +113,9 @@ pub struct JDEC<'i, 'p> { hufflut_dc: [&'p mut [u8]; 2], workbuf: &'p mut [i32], mcubuf: &'p mut [i16], + mcu_x: u16, + mcu_y: u16, pool: &'p mut [u8], - input_func: &'i mut dyn JpegInput, } /// Zigzag-order to raster-order conversion table @@ -146,7 +147,7 @@ const IPSF: [u16; 64] = [ f!(0.27590), f!(0.38268), f!(0.36048), f!(0.32442), f!(0.27590), f!(0.21678), f!(0.14932), f!(0.07612), ]; -impl<'i, 'p> JDEC<'i, 'p> { +impl<'p> JDEC<'p> { /// Allocate a memory block from memory pool /// `self`: decompressor object reference /// `ndata` number of `T` items to allocate @@ -175,12 +176,12 @@ impl<'i, 'p> JDEC<'i, 'p> { } } - fn jpeg_in(&mut self, inbuf_offset: Option, n_data: usize) -> usize { + fn jpeg_in(&mut self, inbuf_offset: Option, n_data: usize, input_func: &mut dyn JpegInput) -> usize { if let Some(offset) = inbuf_offset { let inbuf = &mut self.inbuf[offset..offset + n_data]; - self.input_func.read(Some(inbuf), n_data) + input_func.read(Some(inbuf), n_data) } else { - self.input_func.read(None, n_data) + input_func.read(None, n_data) } } @@ -355,7 +356,7 @@ impl<'i, 'p> JDEC<'i, 'p> { /// `self`: decompressor object reference /// `id`: table ID (0:Y, 1:C) /// `cls`: table class (0:DC, 1:AC) - fn huffext(&mut self, id: usize, cls: usize) -> Result { + fn huffext(&mut self, id: usize, cls: usize, input_func: &mut dyn JpegInput) -> Result { let mut dc: usize = self.dctr; let mut dp: usize = self.dptr; let mut d: u32; @@ -373,7 +374,7 @@ impl<'i, 'p> JDEC<'i, 'p> { if dc == 0 { // Buffer empty, re-fill input buffer dp = 0; // Top of input buffer - dc = self.jpeg_in(Some(0), JD_SZBUF); + dc = self.jpeg_in(Some(0), JD_SZBUF, input_func); if dc == 0 { // Err: read error or wrong stream termination return Err(Error::Input); @@ -479,7 +480,7 @@ impl<'i, 'p> JDEC<'i, 'p> { /// Extract N bits from input stream /// `self`: decompressor object reference /// `nbit`: number of bits to extract (1 to 16) - fn bitext(&mut self, nbit: u32) -> Result { + fn bitext(&mut self, nbit: u32, input_func: &mut dyn JpegInput) -> Result { let mut dc: usize = self.dctr; let mut dp: usize = self.dptr; let mut d: u32; @@ -494,7 +495,7 @@ impl<'i, 'p> JDEC<'i, 'p> { if dc == 0 { // Buffer empty, re-fill input buffer dp = 0; // Top of input buffer - dc = self.jpeg_in(Some(0), JD_SZBUF); + dc = self.jpeg_in(Some(0), JD_SZBUF, input_func); if dc == 0 { // Err: read error or wrong stream termination return Err(Error::Input); @@ -531,7 +532,7 @@ impl<'i, 'p> JDEC<'i, 'p> { /// Process restart interval /// `self`: decompressor object reference /// `rstn`: expected restart sequence number - fn restart(&mut self, rstn: u16) -> Result<(), Error> { + fn restart(&mut self, rstn: u16, input_func: &mut dyn JpegInput) -> Result<(), Error> { let mut dp = self.dptr; let mut dc: usize = self.dctr; let mut marker: u16; @@ -546,7 +547,7 @@ impl<'i, 'p> JDEC<'i, 'p> { if dc == 0 { // No input data is available, re-fill input buffer dp = 0; - dc = self.jpeg_in(Some(0), JD_SZBUF); + dc = self.jpeg_in(Some(0), JD_SZBUF, input_func); if dc == 0 { return Err(Error::Input); } @@ -696,7 +697,7 @@ impl<'i, 'p> JDEC<'i, 'p> { /// Load all blocks in an MCU into working buffer /// `self`: decompressor object reference - fn mcu_load(&mut self) -> Result<(), Error> { + fn mcu_load(&mut self, input_func: &mut dyn JpegInput) -> Result<(), Error> { let mut d: i32; let mut e: i32; let mut blk: u32; @@ -720,12 +721,12 @@ impl<'i, 'p> JDEC<'i, 'p> { id = if cmp != 0 { 1 } else { 0 }; // Huffman table ID of this component // Extract a DC element from input stream - d = self.huffext(id as usize, 0)?; // Extract a huffman coded data (bit length) + d = self.huffext(id as usize, 0, input_func)?; // Extract a huffman coded data (bit length) bc = d as u32; d = self.dcv[cmp as usize] as i32; // DC value of previous block if bc != 0 { // If there is any difference from previous block - e = self.bitext(bc)?; // Extract data bits + e = self.bitext(bc, input_func)?; // Extract data bits bc = 1 << (bc - 1); // MSB position if e as u32 & bc == 0 { e -= ((bc << 1) - 1) as i32; // Restore negative value @@ -751,7 +752,7 @@ impl<'i, 'p> JDEC<'i, 'p> { z = 1; // Top of the AC elements (in zigzag-order) loop { // Extract a huffman coded value (zero runs and bit length) - d = self.huffext(id as usize, 1)?; + d = self.huffext(id as usize, 1, input_func)?; if d == 0 { // EOB? break; @@ -765,7 +766,7 @@ impl<'i, 'p> JDEC<'i, 'p> { bc &= 0xf; if bc != 0 { // Bit length? - d = self.bitext(bc)?; // Extract data bits + d = self.bitext(bc, input_func)?; // Extract data bits bc = 1 << (bc - 1); // MSB position if d as u32 & bc == 0 { // Restore negative value if needed @@ -1112,7 +1113,7 @@ impl<'i, 'p> JDEC<'i, 'p> { } /// Analyze the JPEG image and Initialize decompressor object - pub fn new(input_func: &'i mut dyn JpegInput, pool: &'p mut [u8]) -> Result { + pub fn new(input_func: &mut dyn JpegInput, pool: &'p mut [u8]) -> Result { let mut jd = JDEC { dctr: 0, dptr: 0, @@ -1142,7 +1143,8 @@ impl<'i, 'p> JDEC<'i, 'p> { ncomp: 0, nrst: 0, mcubuf: &mut [], - input_func, + mcu_x: 0, + mcu_y: 0, }; let mut marker: u16; @@ -1156,7 +1158,7 @@ impl<'i, 'p> JDEC<'i, 'p> { marker = 0; ofs = marker as u32; loop { - if jd.jpeg_in(Some(0), 1) != 1 { + if jd.jpeg_in(Some(0), 1, input_func) != 1 { // Err: SOI was not detected return Err(Error::Input); } @@ -1169,7 +1171,7 @@ impl<'i, 'p> JDEC<'i, 'p> { loop { // Parse JPEG segments // Get a JPEG marker - if jd.jpeg_in(Some(0), 4) != 4 { + if jd.jpeg_in(Some(0), 4, input_func) != 4 { return Err(Error::Input); } // Marker @@ -1189,7 +1191,7 @@ impl<'i, 'p> JDEC<'i, 'p> { return Err(Error::MemoryInput); } // Load segment data - if jd.jpeg_in(Some(0), len) != len { + if jd.jpeg_in(Some(0), len, input_func) != len { return Err(Error::Input); } // Image width in unit of pixel @@ -1235,7 +1237,7 @@ impl<'i, 'p> JDEC<'i, 'p> { return Err(Error::MemoryInput); } // Load segment data - if jd.jpeg_in(Some(0), len) != len { + if jd.jpeg_in(Some(0), len, input_func) != len { return Err(Error::Input); } // Get restart interval (MCUs) @@ -1247,7 +1249,7 @@ impl<'i, 'p> JDEC<'i, 'p> { return Err(Error::MemoryInput); } // Load segment data - if jd.jpeg_in(Some(0), len) != len { + if jd.jpeg_in(Some(0), len, input_func) != len { return Err(Error::Input); } // Create huffman tables @@ -1259,7 +1261,7 @@ impl<'i, 'p> JDEC<'i, 'p> { return Err(Error::MemoryInput); } // Load segment data - if jd.jpeg_in(Some(0), len) != len { + if jd.jpeg_in(Some(0), len, input_func) != len { return Err(Error::Input); } // Create de-quantizer tables @@ -1271,7 +1273,7 @@ impl<'i, 'p> JDEC<'i, 'p> { return Err(Error::MemoryInput); } // Load segment data - if jd.jpeg_in(Some(0), len) != len { + if jd.jpeg_in(Some(0), len, input_func) != len { return Err(Error::Input); } if jd.width == 0 || jd.height == 0 { @@ -1323,7 +1325,7 @@ impl<'i, 'p> JDEC<'i, 'p> { // Align stream read offset to JD_SZBUF ofs %= JD_SZBUF as u32; if ofs != 0 { - jd.dctr = jd.jpeg_in(Some(ofs as usize), (JD_SZBUF as u32 - ofs) as usize); + jd.dctr = jd.jpeg_in(Some(ofs as usize), (JD_SZBUF as u32 - ofs) as usize, input_func); } jd.dptr = (ofs - (if JD_FASTDECODE != 0 { 0 } else { 1 })) as usize; return Ok(jd); // Initialization succeeded. Ready to @@ -1338,7 +1340,7 @@ impl<'i, 'p> JDEC<'i, 'p> { _ => { // Unknown segment (comment, exif or etc..) // Skip segment data (null pointer specifies to remove data from the stream) - if jd.jpeg_in(None, len) != len { + if jd.jpeg_in(None, len, input_func) != len { return Err(Error::Input); } } @@ -1348,7 +1350,7 @@ impl<'i, 'p> JDEC<'i, 'p> { /// Start to decompress the JPEG picture /// `scale`: output de-scaling factor (0 to 3) - pub fn decomp(&mut self, output_func: &mut dyn JpegOutput) -> Result<(), Error> { + pub fn decomp(&mut self, input_func: &mut dyn JpegInput, output_func: &mut dyn JpegOutput) -> Result<(), Error> { let mx = (self.msx as i32 * 8) as u32; // Size of the MCU (pixel) let my = (self.msy as i32 * 8) as u32; // Size of the MCU (pixel) let mut y = 0; @@ -1365,11 +1367,11 @@ impl<'i, 'p> JDEC<'i, 'p> { } { let val = self.rsc; self.rsc += 1; - self.restart(val)?; + self.restart(val, input_func)?; self.rst = 1; } // Load an MCU (decompress huffman coded stream, dequantize and apply IDCT) - self.mcu_load()?; + self.mcu_load(input_func)?; // Output the MCU (YCbCr to RGB, scaling and output) self.mcu_output(x, y, output_func)?; x += mx; @@ -1378,6 +1380,47 @@ impl<'i, 'p> JDEC<'i, 'p> { } Ok(()) } + + /// Start to decompress the JPEG picture + /// `scale`: output de-scaling factor (0 to 3) + pub fn decomp2(&mut self, input_func: &mut dyn JpegInput, output_func: &mut dyn JpegOutput) -> Result<(), Error> { + let mx = self.msx as u16 * 8; // Size of the MCU (pixel) + let my = self.msy as u16 * 8; // Size of the MCU (pixel) + while self.mcu_y < self.height { + if self.nrst != 0 && { + // Process restart interval if enabled + let val = self.rst; + self.rst += 1; + val == self.nrst + } { + let val = self.rsc; + self.rsc += 1; + self.restart(val, input_func)?; + self.rst = 1; + } + + // Load an MCU (decompress huffman coded stream, dequantize and apply IDCT) + self.mcu_load(input_func)?; + + let x = self.mcu_x as u32; + let y = self.mcu_y as u32; + + self.mcu_x += mx; + if self.mcu_x >= self.width { + self.mcu_x = 0; + self.mcu_y += my; + } + + // Output the MCU (YCbCr to RGB, scaling and output) + self.mcu_output(x, y, output_func)?; + } + Ok(()) + } + + pub fn next_mcu(&self) -> (u16, u16) { + (self.mcu_x, self.mcu_y) + } + } pub trait JpegInput { From bb2c84b1534d93420c7981b4a479e4612f24b808 Mon Sep 17 00:00:00 2001 From: cepetr Date: Tue, 16 Apr 2024 15:53:13 +0200 Subject: [PATCH 03/14] feat(core): introduce new drawing library [no changelog] --- core/embed/rust/Cargo.lock | 37 + core/embed/rust/Cargo.toml | 11 + core/embed/rust/build.rs | 31 + core/embed/rust/rust_ui_bootloader.h | 4 + core/embed/rust/src/trezorhal/bitblt.rs | 216 +++++ core/embed/rust/src/trezorhal/display.rs | 20 +- core/embed/rust/src/trezorhal/mod.rs | 2 + core/embed/rust/src/ui/display/color.rs | 2 +- core/embed/rust/src/ui/display/font.rs | 45 +- core/embed/rust/src/ui/geometry.rs | 3 + core/embed/rust/src/ui/mod.rs | 1 + core/embed/rust/src/ui/shape/algo/blur.rs | 357 +++++++ core/embed/rust/src/ui/shape/algo/circle.rs | 76 ++ core/embed/rust/src/ui/shape/algo/line.rs | 94 ++ core/embed/rust/src/ui/shape/algo/mod.rs | 9 + core/embed/rust/src/ui/shape/algo/trigo.rs | 29 + core/embed/rust/src/ui/shape/bar.rs | 128 +++ core/embed/rust/src/ui/shape/base.rs | 53 ++ .../rust/src/ui/shape/bitmap/bitmap_base.rs | 313 ++++++ core/embed/rust/src/ui/shape/bitmap/mod.rs | 6 + core/embed/rust/src/ui/shape/bitmap/mono8.rs | 48 + core/embed/rust/src/ui/shape/bitmap/rgb565.rs | 47 + .../rust/src/ui/shape/bitmap/rgba8888.rs | 47 + core/embed/rust/src/ui/shape/blur.rs | 45 + .../rust/src/ui/shape/cache/blur_cache.rs | 59 ++ .../rust/src/ui/shape/cache/drawing_cache.rs | 138 +++ .../rust/src/ui/shape/cache/jpeg_cache.rs | 372 ++++++++ core/embed/rust/src/ui/shape/cache/mod.rs | 7 + .../rust/src/ui/shape/cache/zlib_cache.rs | 173 ++++ core/embed/rust/src/ui/shape/canvas/common.rs | 887 ++++++++++++++++++ core/embed/rust/src/ui/shape/canvas/mod.rs | 11 + core/embed/rust/src/ui/shape/canvas/mono8.rs | 105 +++ core/embed/rust/src/ui/shape/canvas/rgb565.rs | 128 +++ .../rust/src/ui/shape/canvas/rgba8888.rs | 120 +++ .../rust/src/ui/shape/canvas/viewport.rs | 103 ++ core/embed/rust/src/ui/shape/circle.rs | 139 +++ .../rust/src/ui/shape/display/fake_display.rs | 12 + .../rust/src/ui/shape/display/fb_mono8.rs | 55 ++ .../rust/src/ui/shape/display/fb_rgb565.rs | 62 ++ .../rust/src/ui/shape/display/fb_rgba8888.rs | 62 ++ .../embed/rust/src/ui/shape/display/memory.md | 58 ++ core/embed/rust/src/ui/shape/display/mod.rs | 40 + .../rust/src/ui/shape/display/nofb_rgb565.rs | 110 +++ .../ui/shape/drawlib-rust-objects.drawio.svg | 4 + .../ui/shape/drawlib-toplevel-arch.drawio.svg | 4 + core/embed/rust/src/ui/shape/jpeg.rs | 196 ++++ core/embed/rust/src/ui/shape/mod.rs | 33 + core/embed/rust/src/ui/shape/qrcode.rs | 169 ++++ core/embed/rust/src/ui/shape/render.rs | 250 +++++ core/embed/rust/src/ui/shape/text.rs | 134 +++ core/embed/rust/src/ui/shape/toif.rs | 174 ++++ core/embed/rust/src/ui/util.rs | 6 + 52 files changed, 5226 insertions(+), 9 deletions(-) create mode 100644 core/embed/rust/src/trezorhal/bitblt.rs create mode 100644 core/embed/rust/src/ui/shape/algo/blur.rs create mode 100644 core/embed/rust/src/ui/shape/algo/circle.rs create mode 100644 core/embed/rust/src/ui/shape/algo/line.rs create mode 100644 core/embed/rust/src/ui/shape/algo/mod.rs create mode 100644 core/embed/rust/src/ui/shape/algo/trigo.rs create mode 100644 core/embed/rust/src/ui/shape/bar.rs create mode 100644 core/embed/rust/src/ui/shape/base.rs create mode 100644 core/embed/rust/src/ui/shape/bitmap/bitmap_base.rs create mode 100644 core/embed/rust/src/ui/shape/bitmap/mod.rs create mode 100644 core/embed/rust/src/ui/shape/bitmap/mono8.rs create mode 100644 core/embed/rust/src/ui/shape/bitmap/rgb565.rs create mode 100644 core/embed/rust/src/ui/shape/bitmap/rgba8888.rs create mode 100644 core/embed/rust/src/ui/shape/blur.rs create mode 100644 core/embed/rust/src/ui/shape/cache/blur_cache.rs create mode 100644 core/embed/rust/src/ui/shape/cache/drawing_cache.rs create mode 100644 core/embed/rust/src/ui/shape/cache/jpeg_cache.rs create mode 100644 core/embed/rust/src/ui/shape/cache/mod.rs create mode 100644 core/embed/rust/src/ui/shape/cache/zlib_cache.rs create mode 100644 core/embed/rust/src/ui/shape/canvas/common.rs create mode 100644 core/embed/rust/src/ui/shape/canvas/mod.rs create mode 100644 core/embed/rust/src/ui/shape/canvas/mono8.rs create mode 100644 core/embed/rust/src/ui/shape/canvas/rgb565.rs create mode 100644 core/embed/rust/src/ui/shape/canvas/rgba8888.rs create mode 100644 core/embed/rust/src/ui/shape/canvas/viewport.rs create mode 100644 core/embed/rust/src/ui/shape/circle.rs create mode 100644 core/embed/rust/src/ui/shape/display/fake_display.rs create mode 100644 core/embed/rust/src/ui/shape/display/fb_mono8.rs create mode 100644 core/embed/rust/src/ui/shape/display/fb_rgb565.rs create mode 100644 core/embed/rust/src/ui/shape/display/fb_rgba8888.rs create mode 100644 core/embed/rust/src/ui/shape/display/memory.md create mode 100644 core/embed/rust/src/ui/shape/display/mod.rs create mode 100644 core/embed/rust/src/ui/shape/display/nofb_rgb565.rs create mode 100644 core/embed/rust/src/ui/shape/drawlib-rust-objects.drawio.svg create mode 100644 core/embed/rust/src/ui/shape/drawlib-toplevel-arch.drawio.svg create mode 100644 core/embed/rust/src/ui/shape/jpeg.rs create mode 100644 core/embed/rust/src/ui/shape/mod.rs create mode 100644 core/embed/rust/src/ui/shape/qrcode.rs create mode 100644 core/embed/rust/src/ui/shape/render.rs create mode 100644 core/embed/rust/src/ui/shape/text.rs create mode 100644 core/embed/rust/src/ui/shape/toif.rs diff --git a/core/embed/rust/Cargo.lock b/core/embed/rust/Cargo.lock index 9cbd4f828..c2149e641 100644 --- a/core/embed/rust/Cargo.lock +++ b/core/embed/rust/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "alloc-traits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b2d54853319fd101b8dd81de382bcbf3e03410a64d8928bbee85a3e7dcde483" + [[package]] name = "autocfg" version = "1.1.0" @@ -279,6 +285,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static-alloc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "570b7e840addf99f80c5b26abba410e21537002316fc82f2747fd87c171e9d7e" +dependencies = [ + "alloc-traits", +] + [[package]] name = "syn" version = "1.0.80" @@ -309,8 +324,11 @@ dependencies = [ "qrcodegen", "serde_json", "spin", + "static-alloc", "trezor-tjpgdec", "ufmt", + "unsize", + "without-alloc", "zeroize", ] @@ -347,6 +365,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "unsize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa7a7a734c1a5664a662ddcea0b6c9472a21da8888c957c7f1eaa09dba7a939" +dependencies = [ + "autocfg", +] + [[package]] name = "winapi" version = "0.3.9" @@ -369,6 +396,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "without-alloc" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375db0478b203b950ef10d1cce23cdbe5f30c2454fd9e7673ff56656df23adbb" +dependencies = [ + "alloc-traits", + "unsize", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml index 6454358e8..d66ab18c1 100644 --- a/core/embed/rust/Cargo.toml +++ b/core/embed/rust/Cargo.toml @@ -52,6 +52,8 @@ test = [ "micropython", "protobuf", "ui", + "ui_jpeg_decoder", + "ui_blurring", "dma2d", "touch", "backlight", @@ -113,6 +115,15 @@ version = "0.2.6" default-features = false features = ["nightly"] +[dependencies.static-alloc] +version = "0.2.4" + +[dependencies.without-alloc] +version = "0.2.2" + +[dependencies.unsize] +version = "1.1.0" + # Build dependencies [build-dependencies.bindgen] diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs index 4bf8f574b..a534e1d20 100644 --- a/core/embed/rust/build.rs +++ b/core/embed/rust/build.rs @@ -110,6 +110,16 @@ fn prepare_bindings() -> bindgen::Builder { format!("-DTREZOR_BOARD=\"{}\"", board()).as_str(), ]); + #[cfg(feature = "xframebuffer")] + { + bindings = bindings.clang_args(&["-DXFRAMEBUFFER"]); + } + + #[cfg(feature = "new_rendering")] + { + bindings = bindings.clang_args(["-DNEW_RENDERING"]); + } + // Pass in correct include paths and defines. if is_firmware() { let mut clang_args: Vec<&str> = Vec::new(); @@ -338,6 +348,27 @@ fn generate_trezorhal_bindings() { .allowlist_var("DISPLAY_FRAMEBUFFER_OFFSET_Y") .allowlist_var("DISPLAY_RESX") .allowlist_var("DISPLAY_RESY") + .allowlist_type("display_fb_info_t") + .allowlist_function("display_get_frame_buffer") + .allowlist_function("display_fill") + .allowlist_function("display_copy_rgb565") + // gl_bitblt + .allowlist_type("gl_bitblt_t") + .allowlist_function("gl_rgb565_fill") + .allowlist_function("gl_rgb565_copy_mono4") + .allowlist_function("gl_rgb565_copy_rgb565") + .allowlist_function("gl_rgb565_blend_mono4") + .allowlist_function("gl_rgba8888_fill") + .allowlist_function("gl_rgba8888_copy_mono4") + .allowlist_function("gl_rgba8888_copy_rgb565") + .allowlist_function("gl_rgba8888_copy_rgba8888") + .allowlist_function("gl_rgba8888_blend_mono4") + .allowlist_function("gl_mono8_fill") + .allowlist_function("gl_mono8_copy_mono1p") + .allowlist_function("gl_mono8_copy_mono4") + .allowlist_function("gl_mono8_blend_mono1p") + .allowlist_function("gl_mono8_blend_mono4") + .allowlist_function("dma2d_wait") // fonts .allowlist_function("font_height") .allowlist_function("font_max_height") diff --git a/core/embed/rust/rust_ui_bootloader.h b/core/embed/rust/rust_ui_bootloader.h index 09613cf03..c69f700ac 100644 --- a/core/embed/rust/rust_ui_bootloader.h +++ b/core/embed/rust/rust_ui_bootloader.h @@ -24,3 +24,7 @@ void screen_boot_stage_1(bool fading); uint32_t screen_unlock_bootloader_confirm(void); void screen_unlock_bootloader_success(void); void bld_continue_label(uint16_t bg_color); +void screen_boot(bool warning, const char* vendor_str, size_t vendor_str_len, + uint32_t version, const void* vendor_img, + size_t vendor_img_len, int wait); + diff --git a/core/embed/rust/src/trezorhal/bitblt.rs b/core/embed/rust/src/trezorhal/bitblt.rs new file mode 100644 index 000000000..49eef2751 --- /dev/null +++ b/core/embed/rust/src/trezorhal/bitblt.rs @@ -0,0 +1,216 @@ +use super::ffi; + +use crate::ui::{ + display::Color, + geometry::Rect, + shape::{Bitmap, BitmapFormat, BitmapView}, +}; + +pub type BitBlt = ffi::gl_bitblt_t; + +impl Default for BitBlt { + fn default() -> Self { + Self { + width: 0, + height: 0, + dst_row: core::ptr::null_mut(), + dst_stride: 0, + dst_x: 0, + dst_y: 0, + src_row: core::ptr::null_mut(), + src_bg: 0, + src_fg: 0, + src_stride: 0, + src_x: 0, + src_y: 0, + src_alpha: 255, + } + } +} + +impl BitBlt { + pub fn new_fill(r: Rect, clip: Rect, color: Color, alpha: u8) -> Option { + let r = r.clamp(clip); + if !r.is_empty() { + Some( + Self::default() + .with_rect(r) + .with_fg(color) + .with_alpha(alpha), + ) + } else { + None + } + } + + pub fn new_copy(r: Rect, clip: Rect, src: &BitmapView) -> Option { + let mut offset = src.offset; + let mut r_dst = r; + + // Normalize negative x & y-offset of the bitmap + if offset.x < 0 { + r_dst.x0 -= offset.x; + offset.x = 0; + } + + if offset.y < 0 { + r_dst.y0 -= offset.y; + offset.y = 0; + } + + // Clip with the canvas viewport + let mut r = r_dst.clamp(clip); + + // Clip with the bitmap top-left + if r.x0 > r_dst.x0 { + offset.x += r.x0 - r_dst.x0; + } + + if r.y0 > r_dst.y0 { + offset.y += r.y0 - r_dst.y0; + } + + // Clip with the bitmap size + r.x1 = core::cmp::min(r.x0 + src.size().x - offset.x, r.x1); + r.y1 = core::cmp::min(r.y0 + src.size().y - offset.y, r.y1); + + if !r.is_empty() { + Some( + BitBlt::default() + .with_rect(r) + .with_src(src.bitmap, offset.x, offset.y) + .with_bg(src.bg_color) + .with_fg(src.fg_color), + ) + } else { + None + } + } + + pub fn with_dst(self, dst: &mut Bitmap) -> Self { + Self { + dst_row: unsafe { dst.row_ptr(self.dst_y) }, + dst_stride: dst.stride() as u16, + ..self + } + } + + fn with_rect(self, r: Rect) -> Self { + Self { + width: r.width() as u16, + height: r.height() as u16, + dst_x: r.x0 as u16, + dst_y: r.y0 as u16, + ..self + } + } + + fn with_src(self, bitmap: &Bitmap, x: i16, y: i16) -> Self { + let bitmap_stride = match bitmap.format() { + BitmapFormat::MONO1P => bitmap.width() as u16, // packed bits + _ => bitmap.stride() as u16, + }; + + Self { + src_row: unsafe { bitmap.row_ptr(y as u16) }, + src_stride: bitmap_stride, + src_x: x as u16, + src_y: y as u16, + ..self + } + } + + fn with_fg(self, fg_color: Color) -> Self { + Self { + src_fg: fg_color.into(), + ..self + } + } + + fn with_bg(self, bg_color: Color) -> Self { + Self { + src_bg: bg_color.into(), + ..self + } + } + + fn with_alpha(self, alpha: u8) -> Self { + Self { + src_alpha: alpha, + ..self + } + } + + pub fn wait_for_transfer() { + #[cfg(feature = "dma2d")] + unsafe { + ffi::dma2d_wait() + } + } + + pub unsafe fn rgb565_fill(&self) { + unsafe { ffi::gl_rgb565_fill(self) }; + } + + pub unsafe fn rgb565_copy_mono4(&self) { + unsafe { ffi::gl_rgb565_copy_mono4(self) }; + } + + pub unsafe fn rgb565_copy_rgb565(&self) { + unsafe { ffi::gl_rgb565_copy_rgb565(self) }; + } + + pub unsafe fn rgb565_blend_mono4(&self) { + unsafe { ffi::gl_rgb565_blend_mono4(self) }; + } + + pub unsafe fn rgba8888_fill(&self) { + unsafe { ffi::gl_rgba8888_fill(self) }; + } + + pub unsafe fn rgba8888_copy_mono4(&self) { + unsafe { ffi::gl_rgba8888_copy_mono4(self) }; + } + + pub unsafe fn rgba8888_copy_rgb565(&self) { + unsafe { ffi::gl_rgba8888_copy_rgb565(self) }; + } + + pub unsafe fn rgba8888_copy_rgba8888(&self) { + unsafe { ffi::gl_rgba8888_copy_rgba8888(self) }; + } + + pub unsafe fn rgba8888_blend_mono4(&self) { + unsafe { ffi::gl_rgba8888_blend_mono4(self) }; + } + + pub unsafe fn mono8_fill(&self) { + unsafe { ffi::gl_mono8_fill(self) }; + } + + pub unsafe fn mono8_copy_mono1p(&self) { + unsafe { ffi::gl_mono8_copy_mono1p(self) }; + } + + pub unsafe fn mono8_copy_mono4(&self) { + unsafe { ffi::gl_mono8_copy_mono4(self) }; + } + + pub unsafe fn mono8_blend_mono1p(&self) { + unsafe { ffi::gl_mono8_blend_mono1p(self) }; + } + + pub unsafe fn mono8_blend_mono4(&self) { + unsafe { ffi::gl_mono8_blend_mono4(self) }; + } + + #[cfg(feature = "new_rendering")] + pub unsafe fn display_fill(&self) { + unsafe { ffi::display_fill(self) }; + } + + #[cfg(feature = "new_rendering")] + pub unsafe fn display_copy_rgb565(&self) { + unsafe { ffi::display_copy_rgb565(self) }; + } +} diff --git a/core/embed/rust/src/trezorhal/display.rs b/core/embed/rust/src/trezorhal/display.rs index 95a17350b..b2713f695 100644 --- a/core/embed/rust/src/trezorhal/display.rs +++ b/core/embed/rust/src/trezorhal/display.rs @@ -13,11 +13,11 @@ pub use ffi::{ #[cfg(all(feature = "framebuffer", not(feature = "framebuffer32bit")))] #[derive(Copy, Clone)] -pub struct FrameBuffer(*mut u16); +pub struct FrameBuffer(pub *mut u16); #[cfg(all(feature = "framebuffer", feature = "framebuffer32bit"))] #[derive(Copy, Clone)] -pub struct FrameBuffer(*mut u32); +pub struct FrameBuffer(pub *mut u32); pub fn backlight(val: i32) -> i32 { unsafe { ffi::display_backlight(val) } @@ -98,7 +98,9 @@ pub fn get_fb_addr() -> FrameBuffer { #[inline(always)] #[cfg(all(not(feature = "framebuffer"), feature = "disp_i8080_8bit_dw"))] +#[allow(unused_variables)] pub fn pixeldata(c: u16) { + #[cfg(not(feature = "new_rendering"))] unsafe { ffi::DISPLAY_DATA_ADDRESS.write_volatile((c & 0xff) as u8); ffi::DISPLAY_DATA_ADDRESS.write_volatile((c >> 8) as u8); @@ -178,3 +180,17 @@ pub fn clear() { ffi::display_clear(); } } + +#[cfg(feature = "xframebuffer")] +pub fn get_frame_buffer() -> (&'static mut [u8], usize) { + let fb_info = unsafe { ffi::display_get_frame_buffer() }; + + let fb = unsafe { + core::slice::from_raw_parts_mut( + fb_info.ptr as *mut u8, + DISPLAY_RESY as usize * fb_info.stride, + ) + }; + + (fb, fb_info.stride) +} diff --git a/core/embed/rust/src/trezorhal/mod.rs b/core/embed/rust/src/trezorhal/mod.rs index 39de95151..5175d262d 100644 --- a/core/embed/rust/src/trezorhal/mod.rs +++ b/core/embed/rust/src/trezorhal/mod.rs @@ -2,6 +2,7 @@ pub mod bip39; #[macro_use] #[allow(unused_macros)] pub mod fatal_error; +pub mod bitblt; #[cfg(feature = "ui")] pub mod display; #[cfg(feature = "dma2d")] @@ -9,6 +10,7 @@ pub mod dma2d; mod ffi; #[cfg(feature = "haptic")] pub mod haptic; + pub mod io; pub mod model; pub mod random; diff --git a/core/embed/rust/src/ui/display/color.rs b/core/embed/rust/src/ui/display/color.rs index 945eba3ee..4ad86389c 100644 --- a/core/embed/rust/src/ui/display/color.rs +++ b/core/embed/rust/src/ui/display/color.rs @@ -98,7 +98,7 @@ impl Color { let r = (fg.r() as u16) * fg_mul + (self.r() as u16) * bg_mul; let g = (fg.g() as u16) * fg_mul + (self.g() as u16) * bg_mul; let b = (fg.b() as u16) * fg_mul + (self.b() as u16) * bg_mul; - Color::rgb((r >> 8) as u8, (g >> 8) as u8, (b >> 8) as u8) + Color::rgb((r / 255) as u8, (g / 255) as u8, (b / 255) as u8) } } diff --git a/core/embed/rust/src/ui/display/font.rs b/core/embed/rust/src/ui/display/font.rs index b88953b98..a466fce3c 100644 --- a/core/embed/rust/src/ui/display/font.rs +++ b/core/embed/rust/src/ui/display/font.rs @@ -3,6 +3,7 @@ use crate::{ ui::{ constant, geometry::{Offset, Point, Rect}, + shape::{Bitmap, BitmapFormat}, }, }; use core::slice; @@ -41,12 +42,12 @@ impl Glyph { let width = *data.offset(0) as i16; let height = *data.offset(1) as i16; - let data_bits = constant::FONT_BPP * width * height; - - let data_bytes = if data_bits % 8 == 0 { - data_bits / 8 - } else { - (data_bits / 8) + 1 + let data_bytes = match constant::FONT_BPP { + 1 => (width * height + 7) / 8, // packed bits + 2 => (width * height + 3) / 4, // packed bits + 4 => (width + 1) / 2 * height, // row aligned to bytes + 8 => width * height, + _ => panic!(), }; Glyph { @@ -119,6 +120,28 @@ impl Glyph { _ => 0, } } + + pub fn bitmap(&self) -> Bitmap<'static> { + match constant::FONT_BPP { + 1 => unwrap!(Bitmap::new( + BitmapFormat::MONO1P, + None, + Offset::new(self.width, self.height), + None, + self.data, + )), + 2 => panic!(), + 4 => unwrap!(Bitmap::new( + BitmapFormat::MONO4, + None, + Offset::new(self.width, self.height), + None, + self.data, + )), + 8 => panic!(), + _ => panic!(), + } + } } /// Font constants. Keep in sync with FONT_ definitions in @@ -240,6 +263,16 @@ impl Font { constant::LINE_SPACE + self.text_height() } + // Returns x-coordinate of the text start (including left bearing) + pub fn horz_center(&self, start: i16, end: i16, text: &str) -> i16 { + (start + end - self.visible_text_width(text)) / 2 - self.start_x_bearing(text) + } + + // Returns y-coordinate of the text baseline + pub fn vert_center(&self, start: i16, end: i16, text: &str) -> i16 { + (start + end + self.visible_text_height(text)) / 2 + } + pub fn get_glyph(self, ch: char) -> Glyph { let gl_data = display::get_char_glyph(ch as u16, self.into()); diff --git a/core/embed/rust/src/ui/geometry.rs b/core/embed/rust/src/ui/geometry.rs index d73702340..a5a7d8a2b 100644 --- a/core/embed/rust/src/ui/geometry.rs +++ b/core/embed/rust/src/ui/geometry.rs @@ -545,6 +545,7 @@ pub enum Alignment { End, } +#[derive(Copy, Clone)] pub struct Alignment2D(pub Alignment, pub Alignment); impl Alignment2D { @@ -552,6 +553,8 @@ impl Alignment2D { pub const TOP_RIGHT: Alignment2D = Alignment2D(Alignment::End, Alignment::Start); pub const TOP_CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::Start); pub const CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::Center); + pub const CENTER_LEFT: Alignment2D = Alignment2D(Alignment::Start, Alignment::Center); + pub const CENTER_RIGHT: Alignment2D = Alignment2D(Alignment::End, Alignment::Center); pub const BOTTOM_LEFT: Alignment2D = Alignment2D(Alignment::Start, Alignment::End); pub const BOTTOM_RIGHT: Alignment2D = Alignment2D(Alignment::End, Alignment::End); pub const BOTTOM_CENTER: Alignment2D = Alignment2D(Alignment::Center, Alignment::End); diff --git a/core/embed/rust/src/ui/mod.rs b/core/embed/rust/src/ui/mod.rs index fd8c51dac..b08f77789 100644 --- a/core/embed/rust/src/ui/mod.rs +++ b/core/embed/rust/src/ui/mod.rs @@ -8,6 +8,7 @@ pub mod display; pub mod event; pub mod geometry; pub mod lerp; +pub mod shape; #[macro_use] pub mod util; diff --git a/core/embed/rust/src/ui/shape/algo/blur.rs b/core/embed/rust/src/ui/shape/algo/blur.rs new file mode 100644 index 000000000..25270bf94 --- /dev/null +++ b/core/embed/rust/src/ui/shape/algo/blur.rs @@ -0,0 +1,357 @@ +use crate::ui::geometry::Offset; +/// This is a simple and fast blurring algorithm that uses a box filter - +/// a square kernel with all coefficients set to 1. +/// +/// The `BlurFilter` structure holds the context of a simple 2D window averaging +/// filter - a sliding window and the sum of all rows in the sliding window. +/// +/// The `BlurFilter` implements only five public functions - `new`, `push`, +/// `push_read`, `pop` and `pop_ready`. +/// +/// The `new()` function creates a blur filter context. +/// - The `size` argument specifies the size of the blurred area. +/// - The `radius` argument specifies the length of the kernel side. +/// +/// ```rust +/// let blur = BlurFilter::new(size, radius); +/// ``` +/// +/// The `push_ready()` function returns the row from the source bitmap +/// needed to be pushed +/// +/// The `push()` function pushes source row data into the sliding window and +/// performs all necessary calculations. +/// +/// ```rust +/// if let Some(y) = blur.push_ready() { +/// blur.push(&src_bitmap.row(y)[x0..x1]); +/// } +/// ``` +/// +/// The `pop_ready()` function returns the row from the destination bitmap +/// that can be popped out +/// +/// The `pop()` function pops the blurred row from the sliding window. +/// +/// ```rust +/// if let Some(y) = blur.pop_ready() { +/// blur.pop(&mut dst_bitmap.row(y)[x0..x1]); +/// } +/// ``` +use core::mem::size_of; + +const MAX_RADIUS: usize = 4; +const MAX_SIDE: usize = 1 + MAX_RADIUS * 2; +const MAX_WIDTH: usize = 240; + +pub type BlurBuff = [u8; MAX_WIDTH * (MAX_SIDE * 3 + size_of::() * 3) + 8]; + +type PixelColor = u16; + +#[derive(Default, Copy, Clone)] +struct Rgb { + pub r: T, + pub g: T, + pub b: T, +} + +impl Rgb { + #[inline(always)] + fn mulshift(&self, multiplier: u32, shift: u8) -> Rgb { + Rgb:: { + r: ((self.r as u32 * multiplier) >> shift) as u8, + g: ((self.g as u32 * multiplier) >> shift) as u8, + b: ((self.b as u32 * multiplier) >> shift) as u8, + } + } +} + +impl From for Rgb { + #[inline(always)] + fn from(value: u16) -> Rgb { + Rgb:: { + r: (value >> 8) & 0xF8, + g: (value >> 3) & 0xFC, + b: (value << 3) & 0xF8, + } + } +} + +impl core::ops::AddAssign for Rgb { + #[inline(always)] + fn add_assign(&mut self, rhs: u16) { + let rgb: Rgb = rhs.into(); + *self += rgb; + } +} + +impl core::ops::SubAssign for Rgb { + #[inline(always)] + fn sub_assign(&mut self, rhs: u16) { + let rgb: Rgb = rhs.into(); + *self -= rgb; + } +} + +impl core::ops::AddAssign for Rgb { + #[inline(always)] + fn add_assign(&mut self, rhs: Self) { + self.r += rhs.r; + self.g += rhs.g; + self.b += rhs.b; + } +} + +impl core::ops::SubAssign for Rgb { + #[inline(always)] + fn sub_assign(&mut self, rhs: Rgb) { + self.r -= rhs.r; + self.g -= rhs.g; + self.b -= rhs.b; + } +} + +impl From> for u16 { + #[inline(always)] + fn from(value: Rgb) -> u16 { + let r = (value.r as u16 & 0xF8) << 8; + let g = (value.g as u16 & 0xFC) << 3; + let b = (value.b as u16 & 0xF8) >> 3; + r | g | b + } +} + +impl From> for Rgb { + #[inline(always)] + fn from(value: Rgb) -> Rgb { + Rgb:: { + r: value.r as u8, + g: value.g as u8, + b: value.b as u8, + } + } +} + +impl core::ops::AddAssign> for Rgb { + #[inline(always)] + fn add_assign(&mut self, rhs: Rgb) { + self.r += rhs.r as u16; + self.g += rhs.g as u16; + self.b += rhs.b as u16; + } +} + +impl core::ops::SubAssign> for Rgb { + #[inline(always)] + fn sub_assign(&mut self, rhs: Rgb) { + self.r -= rhs.r as u16; + self.g -= rhs.g as u16; + self.b -= rhs.b as u16; + } +} + +pub struct BlurAlgorithm<'a> { + size: Offset, + radius: usize, + row: usize, + totals: &'a mut [Rgb], + window: &'a mut [Rgb], + row_count: usize, +} + +impl<'a> BlurAlgorithm<'a> { + /// Constraints: + /// width <= MAX_WIDTH + /// radius <= MAX_RADIUS + /// width >= radius + pub fn new(size: Offset, radius: usize, memory: &'a mut BlurBuff) -> Result { + assert!(size.x as usize <= MAX_WIDTH); + assert!(radius <= MAX_RADIUS); + assert!(size.x as usize > 2 * radius - 1); + + // Split buffer into two parts + let window_size = size.x as usize * (1 + radius * 2); + let (window_buff, total_buff) = + memory.split_at_mut(window_size * core::mem::size_of::>()); + + // Allocate `window` from the beginning of the buffer + let (_, window_buff, _) = unsafe { window_buff.align_to_mut() }; + if window_buff.len() < window_size { + return Err(()); + } + let window = &mut window_buff[..window_size]; + window.iter_mut().for_each(|it| *it = Rgb::::default()); + + // Allocate `totals` from the rest of the buffer + let (_, totals_buff, _) = unsafe { total_buff.align_to_mut() }; + if totals_buff.len() < size.x as usize { + return Err(()); + } + let totals = &mut totals_buff[..size.x as usize]; + totals.iter_mut().for_each(|it| *it = Rgb::::default()); + + Ok(Self { + size, + radius, + row: 0, + window, + totals, + row_count: 0, + }) + } + + /// Returns the length of the box filter side. + fn box_side(&self) -> usize { + 1 + self.radius * 2 + } + + /// Takes an input row and calculates the same-sized vector + /// as the floating average of n subsequent elements where n = 2 * radius + + /// 1. Finally, it stores it into the specifed row in the sliding + /// window. + fn average_to_row(&mut self, inp: &[PixelColor], row: usize) { + let radius = self.radius; + let offset = self.size.x as usize * row; + let row = &mut self.window[offset..offset + self.size.x as usize]; + + let mut sum = Rgb::::default(); + + let divisor = (radius * 2 + 1) as u16; + let shift = 10; + let multiplier = (1 << shift) as u32 / divisor as u32; + + // Prepare before averaging + for i in 0..radius { + sum += inp[0]; // Duplicate pixels on the left + sum += inp[i]; // Add first radius pixels + } + + // Process the first few pixels of the row + for i in 0..radius { + sum += inp[i + radius]; + row[i] = sum.mulshift(multiplier, shift); + sum -= inp[0]; + } + + // Process the inner part of the row + for i in radius..row.len() - radius { + sum += inp[i + radius]; + row[i] = sum.mulshift(multiplier, shift); + sum -= inp[i - radius]; + } + + // Process the last few pixels of the row + for i in (row.len() - radius)..row.len() { + sum += inp[inp.len() - 1]; + row[i] = sum.mulshift(multiplier, shift); + sum -= inp[i - radius]; // Duplicate pixels on the right + } + } + + /// Copy one row from the window to the another row. + fn copy_row(&mut self, from_row: usize, to_row: usize) { + let from_offset = self.size.x as usize * from_row; + let to_offset = self.size.x as usize * to_row; + for i in 0..self.size.x as usize { + self.window[to_offset + i] = self.window[from_offset + i]; + } + } + + /// Subtracts the specified row of sliding window from `totals[]`. + fn subtract_row(&mut self, row: usize) { + let offset = self.size.x as usize * row; + let row = &self.window[offset..offset + self.size.x as usize]; + + for (i, item) in row.iter().enumerate() { + self.totals[i] -= *item; + } + } + + /// Adds the specified row of sliding window to `totals[]`. + fn add_row(&mut self, row: usize) { + let offset = self.size.x as usize * row; + let row = &self.window[offset..offset + self.size.x as usize]; + + for (i, item) in row.iter().enumerate() { + self.totals[i] += *item; + } + } + + /// Pushes the most recently pushed row again. + fn push_last_row(&mut self) { + let to_row = self.row; + let from_row = if to_row > 0 { + to_row - 1 + } else { + self.box_side() - 1 + }; + + self.subtract_row(to_row); + self.copy_row(from_row, to_row); + self.add_row(to_row); + + self.row = (to_row + 1) % self.box_side(); + self.row_count += 1; + } + + /// Returns the index of the row needed to be pushed into. + pub fn push_ready(&self) -> Option { + let y = core::cmp::max(0, self.row_count as i16 - self.radius as i16); + if y < self.size.y { + Some(y) + } else { + None + } + } + + /// Takes the source row and pushes it into the sliding window. + pub fn push(&mut self, input: &[PixelColor]) { + let row = self.row; + + self.subtract_row(row); + self.average_to_row(input, row); + self.add_row(row); + + self.row = (row + 1) % self.box_side(); + self.row_count += 1; + + while self.row_count <= self.radius { + self.push_last_row(); + } + } + + /// Returns the index of row ready to be popped out. + pub fn pop_ready(&self) -> Option { + let y = self.row_count as i16 - self.box_side() as i16; + if y < 0 { + None + } else { + Some(y) + } + } + + /// Copies the current content of `totals[]` to the output buffer. + pub fn pop(&mut self, output: &mut [PixelColor], dim: Option) { + let divisor = match dim { + Some(dim) => { + if dim > 0 { + (self.box_side() as u16 * 255) / dim as u16 + } else { + 65535u16 + } + } + None => self.box_side() as u16, + }; + + let shift = 10; + let multiplier = (1 << shift) as u32 / divisor as u32; + + for (i, item) in output.iter_mut().enumerate() { + *item = self.totals[i].mulshift(multiplier, shift).into(); + } + + if self.push_ready().is_none() { + self.push_last_row(); + } + } +} diff --git a/core/embed/rust/src/ui/shape/algo/circle.rs b/core/embed/rust/src/ui/shape/algo/circle.rs new file mode 100644 index 000000000..2e56a295e --- /dev/null +++ b/core/embed/rust/src/ui/shape/algo/circle.rs @@ -0,0 +1,76 @@ +/// Iterator providing points for 1/8th of a circle (single octant) +/// +/// The iterator supplies coordinates of pixels relative to the +/// circle's center point, along with an alpha value in +/// the range (0..255), indicating the proportion of the pixel +/// that lies inside the circle. +/// +/// for p in circle_points(radius) { +/// println!("{}, {}", p.u, p.v); // coordinates <0,radius>.. +/// println!("{}", p.frac); // distance from the circle <0..255> +/// println!("{}", p.first); // `v` has changed +/// println!("{}", p.last); // next `v` will change +/// } +/// +/// `u` axis is the main and increments at each iteration. +/// +/// endpoint [t, t] or [t - 1, t] where t = radius * (1 / sqrt(2)) + +pub fn circle_points(radius: i16) -> CirclePoints { + CirclePoints { + radius, + u: 0, + v: radius, + t1: radius / 16, + first: true, + } +} + +pub struct CirclePoints { + radius: i16, + u: i16, + v: i16, + t1: i16, + first: bool, +} + +#[derive(Copy, Clone)] +pub struct CirclePointsItem { + pub u: i16, + pub v: i16, + pub frac: u8, + pub first: bool, + pub last: bool, +} + +impl Iterator for CirclePoints { + type Item = CirclePointsItem; + + fn next(&mut self) -> Option { + if self.v >= self.u { + let mut item = CirclePointsItem { + u: self.u, + v: self.v, + frac: 255 - ((self.t1 as i32 * 255) / self.radius as i32) as u8, + first: self.first, + last: false, + }; + + self.first = false; + self.u += 1; + self.t1 += self.u; + let t2 = self.t1 - self.v; + if t2 >= 0 { + self.t1 = t2; + self.v -= 1; + self.first = true; + } + + item.last = item.v != self.v; + + Some(item) + } else { + None + } + } +} diff --git a/core/embed/rust/src/ui/shape/algo/line.rs b/core/embed/rust/src/ui/shape/algo/line.rs new file mode 100644 index 000000000..eeae577cb --- /dev/null +++ b/core/embed/rust/src/ui/shape/algo/line.rs @@ -0,0 +1,94 @@ +/// Iterator providing points on a line (using bresenham's algorithm) +/// +/// The iterator supplies coordinates of pixels relative to the +/// line's start point. +/// +/// constraint: `du` >= `dv`, `start_u` < `du` +/// +/// for p in line_points(du, dv, start_u) { +/// println!("{}, {}", p.u, p.v); // coordinates <0,radius>.. +/// println!("{}", p.frac); // distance from the line <0..255> +/// println!("{}", p.first); // `v` has changed +/// println!("{}", p.last); // next `v` will change +/// } +/// +/// `u` axis is the main and increments at each iteration. + +pub fn line_points(du: i16, dv: i16, start_u: i16) -> LinePoints { + let mut d = 2 * du - 2 * dv; + let mut y = 0; + + for _ in 0..start_u { + if d <= 0 { + d += 2 * du - 2 * dv; + y += 1; + } else { + d -= 2 * dv; + } + } + + LinePoints { + du, + dv, + d, + u: start_u, + v: y, + first: true, + } +} + +pub struct LinePoints { + du: i16, + dv: i16, + d: i16, + u: i16, + v: i16, + first: bool, +} + +#[derive(Copy, Clone)] +pub struct LinePointsItem { + pub u: i16, + pub v: i16, + pub frac: u8, + pub first: bool, + pub last: bool, +} + +impl Iterator for LinePoints { + type Item = LinePointsItem; + + #[inline(always)] + fn next(&mut self) -> Option { + if self.u < self.du { + let frac = if self.dv < self.du { + 255 - ((self.d + 2 * self.dv - 1) as i32 * 255 / (2 * self.du - 1) as i32) as u8 + } else { + 0 + }; + + let next = LinePointsItem { + u: self.u, + v: self.v, + frac, + first: self.first, + last: self.d <= 0, + }; + + if self.d <= 0 { + self.d += 2 * self.du - 2 * self.dv; + self.v += 1; + self.first = true; + } else { + self.d -= 2 * self.dv; + self.first = false; + } + + self.u += 1; + + Some(next) + } else { + None + } + } +} diff --git a/core/embed/rust/src/ui/shape/algo/mod.rs b/core/embed/rust/src/ui/shape/algo/mod.rs new file mode 100644 index 000000000..d9e23288c --- /dev/null +++ b/core/embed/rust/src/ui/shape/algo/mod.rs @@ -0,0 +1,9 @@ +mod blur; +mod circle; +mod line; +mod trigo; + +pub use blur::{BlurAlgorithm, BlurBuff}; +pub use circle::circle_points; +pub use line::line_points; +pub use trigo::{sin_i16, PI4}; diff --git a/core/embed/rust/src/ui/shape/algo/trigo.rs b/core/embed/rust/src/ui/shape/algo/trigo.rs new file mode 100644 index 000000000..a18a7bf79 --- /dev/null +++ b/core/embed/rust/src/ui/shape/algo/trigo.rs @@ -0,0 +1,29 @@ +/// Integer representing an angle of 45 degress (PI/4). +// +// Changing this constant requires revisiting isin() algorithm +// (for higher values consider changing T type to i64 or f32) +pub const PI4: i16 = 45; + +/// Fast sine approximation. +/// +/// Returns mult * sin(angle). +/// +/// Angle must be in range <0..PI4>. +/// This function provides an error within +-1 for multiplier up to 500 +pub fn sin_i16(angle: i16, mult: i16) -> i16 { + assert!((0..=PI4).contains(&angle)); + assert!(mult <= 2500); + + type T = i32; + + // Based on polynomial x - x^3 / 6 + let x = angle as T; + + // Constants for the approximation + const K: f32 = (PI4 as f32) * 4.0 / core::f32::consts::PI; + const M: T = (6.0 * K * K + 0.5) as T; + const N: T = (6.0 * K * K * K + 0.5) as T; + + // Applying the approximation + (((M * x - x * x * x) * mult as T + N / 2) / N) as i16 +} diff --git a/core/embed/rust/src/ui/shape/bar.rs b/core/embed/rust/src/ui/shape/bar.rs new file mode 100644 index 000000000..b23623cde --- /dev/null +++ b/core/embed/rust/src/ui/shape/bar.rs @@ -0,0 +1,128 @@ +use crate::ui::{display::Color, geometry::Rect}; + +use super::{Canvas, DrawingCache, Renderer, Shape, ShapeClone}; + +use without_alloc::alloc::LocalAllocLeakExt; + +/// A shape for the rendering variuous type of rectangles. +pub struct Bar { + /// Rectangle position and dimenstion + area: Rect, + /// Foreground color (default None) + fg_color: Option, + /// Background color (default None) + bg_color: Option, + /// Thickness (default 0) + thickness: i16, + /// Corner radius (default 0) + radius: i16, + /// Alpha (default 255) + alpha: u8, +} + +impl Bar { + pub fn new(area: Rect) -> Self { + Self { + area, + fg_color: None, + bg_color: None, + thickness: 1, + radius: 0, + alpha: 255, + } + } + + pub fn with_fg(self, fg_color: Color) -> Self { + Self { + fg_color: Some(fg_color), + ..self + } + } + + pub fn with_bg(self, bg_color: Color) -> Self { + Self { + bg_color: Some(bg_color), + ..self + } + } + + pub fn with_radius(self, radius: i16) -> Self { + Self { radius, ..self } + } + + pub fn with_thickness(self, thickness: i16) -> Self { + Self { thickness, ..self } + } + + pub fn with_alpha(self, alpha: u8) -> Self { + Self { alpha, ..self } + } + + pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) { + renderer.render_shape(self); + } +} + +impl Shape<'_> for Bar { + fn bounds(&self, _cache: &DrawingCache) -> Rect { + self.area + } + + fn cleanup(&mut self, _cache: &DrawingCache) {} + + fn draw(&mut self, canvas: &mut dyn Canvas, _cache: &DrawingCache) { + // NOTE: drawing of rounded bars without a background + // is not supported. If we needed it, we would have to + // introduce a new function in RgbCanvas. + + // TODO: panic! in unsupported scenarious + + let th = match self.fg_color { + Some(_) => self.thickness, + None => 0, + }; + + if self.radius == 0 { + if let Some(fg_color) = self.fg_color { + // outline + if th > 0 { + let r = self.area; + canvas.fill_rect(Rect { y1: r.y0 + th, ..r }, fg_color, self.alpha); + canvas.fill_rect(Rect { x1: r.x0 + th, ..r }, fg_color, self.alpha); + canvas.fill_rect(Rect { x0: r.x1 - th, ..r }, fg_color, self.alpha); + canvas.fill_rect(Rect { y0: r.y1 - th, ..r }, fg_color, self.alpha); + } + } + if let Some(bg_color) = self.bg_color { + // background + let bg_r = self.area.shrink(th); + canvas.fill_rect(bg_r, bg_color, self.alpha); + } + } else { + if let Some(fg_color) = self.fg_color { + if th > 0 { + if self.bg_color.is_some() { + canvas.fill_round_rect(self.area, self.radius, fg_color, self.alpha); + } else { + #[cfg(not(feature = "ui_antialiasing"))] + canvas.draw_round_rect(self.area, self.radius, fg_color); + } + } + } + if let Some(bg_color) = self.bg_color { + let bg_r = self.area.shrink(th); + canvas.fill_round_rect(bg_r, self.radius, bg_color, self.alpha); + } + } + } +} + +impl<'s> ShapeClone<'s> for Bar { + fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>> + where + T: LocalAllocLeakExt<'alloc>, + { + let clone = bump.alloc_t::()?; + Some(clone.uninit.init(Bar { ..self })) + } +} diff --git a/core/embed/rust/src/ui/shape/base.rs b/core/embed/rust/src/ui/shape/base.rs new file mode 100644 index 000000000..59e0a9990 --- /dev/null +++ b/core/embed/rust/src/ui/shape/base.rs @@ -0,0 +1,53 @@ +use crate::ui::geometry::Rect; + +use super::{Canvas, DrawingCache}; + +use without_alloc::alloc::LocalAllocLeakExt; + +// ========================================================================== +// trait Shape +// ========================================================================== + +/// This trait is used internally by so-called Renderers - +/// `DirectRenderer` & `ProgressiveRederer`. +/// +/// All shapes (like `Bar`, `Text`, `Circle`, ...) that can be rendered +/// must implement `Shape` trait. +/// +/// `Shape` objects may use `DrawingCache` as a scratch-pad memory or for +/// caching expensive calculations results. +pub trait Shape<'s> { + /// Returns the smallest bounding rectangle containing whole parts of the + /// shape. + /// + /// The function is used by renderer for optimization if the shape + /// must be renderer or not. + fn bounds(&self, cache: &DrawingCache<'s>) -> Rect; + + /// Draws shape on the canvas. + fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache<'s>); + + /// The function should release all allocated resources needed + /// for shape drawing. + /// + /// It's called by renderer if the shape's draw() function won't be called + /// anymore. + fn cleanup(&mut self, cache: &DrawingCache<'s>); +} + +// ========================================================================== +// trait ShapeClone +// ========================================================================== + +/// All shapes (like `Bar`, `Text`, `Circle`, ...) that can be rendered +/// by `ProgressiveRender` must implement `ShapeClone`. +pub trait ShapeClone<'s> { + /// Clones a shape object at the specified memory bump. + /// + /// The method is used by `ProgressiveRenderer` to store shape objects for + /// deferred drawing. + fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>> + where + T: LocalAllocLeakExt<'alloc>, + 'alloc: 's; +} diff --git a/core/embed/rust/src/ui/shape/bitmap/bitmap_base.rs b/core/embed/rust/src/ui/shape/bitmap/bitmap_base.rs new file mode 100644 index 000000000..9cb6c48a7 --- /dev/null +++ b/core/embed/rust/src/ui/shape/bitmap/bitmap_base.rs @@ -0,0 +1,313 @@ +use crate::trezorhal::bitblt::BitBlt; + +use crate::ui::{display::Color, geometry::Offset}; + +use core::{cell::Cell, marker::PhantomData}; + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum BitmapFormat { + /// 1-bit mono + MONO1, + /// 1-bit mono packed (bitmap stride is in bits) + MONO1P, + /// 4-bit mono + MONO4, + /// 8-bit mono + MONO8, + /// 16-bit color, RGB565 format + RGB565, + /// 32-bit color, RGBA format + RGBA8888, +} + +pub struct Bitmap<'a> { + /// Pointer to top-left pixel + ptr: *mut u8, + /// Stride in bytes + stride: usize, + /// Size in pixels + size: Offset, + /// Format of pixels + format: BitmapFormat, + /// Bitmap data is mutable + mutable: bool, + /// DMA operation is pending + dma_pending: Cell, + /// + _phantom: core::marker::PhantomData<&'a ()>, +} + +impl<'a> Bitmap<'a> { + /// Creates a new bitmap referencing a specified buffer. + /// + /// Optionally minimal height can be specified and then the height + /// of the new bitmap is adjusted to the buffer size. + /// + /// Returns None if the buffer is not big enough. + /// + /// The `buff` needs to be properly aligned and big enough + /// to hold a bitmap with the specified format and size + pub fn new( + format: BitmapFormat, + stride: Option, + mut size: Offset, + min_height: Option, + buff: &'a [u8], + ) -> Option { + if size.x < 0 && size.y < 0 { + return None; + } + + let min_stride = match format { + BitmapFormat::MONO1 => (size.x + 7) / 8, + BitmapFormat::MONO1P => 0, + BitmapFormat::MONO4 => (size.x + 1) / 2, + BitmapFormat::MONO8 => size.x, + BitmapFormat::RGB565 => size.x * 2, + BitmapFormat::RGBA8888 => size.x * 4, + } as usize; + + let stride = stride.unwrap_or(min_stride); + + let alignment = match format { + BitmapFormat::MONO1 => 1, + BitmapFormat::MONO1P => 1, + BitmapFormat::MONO4 => 1, + BitmapFormat::MONO8 => 1, + BitmapFormat::RGB565 => 2, + BitmapFormat::RGBA8888 => 4, + }; + + assert!(stride >= min_stride); + assert!(buff.as_ptr() as usize & (alignment - 1) == 0); + assert!(stride & (alignment - 1) == 0); + + let max_height = if stride == 0 { + size.y as usize + } else { + buff.len() / stride + }; + + if size.y as usize > max_height { + if let Some(min_height) = min_height { + if max_height >= min_height as usize { + size.y = max_height as i16; + } else { + return None; + } + } else { + return None; + } + } + + Some(Self { + ptr: buff.as_ptr() as *mut u8, + stride, + size, + format, + mutable: false, + dma_pending: Cell::new(false), + _phantom: PhantomData, + }) + } + + /// Creates a new mutable bitmap referencing a specified buffer. + /// + /// Optionally minimal height can be specified and then the height + /// of the new bitmap is adjusted to the buffer size. + /// + /// Returns None if the buffer is not big enough. + /// + /// The `buff` needs to be properly aligned and big enough + /// to hold a bitmap with the specified format and size + pub fn new_mut( + format: BitmapFormat, + stride: Option, + size: Offset, + min_height: Option, + buff: &'a mut [u8], + ) -> Option { + let mut bitmap = Self::new(format, stride, size, min_height, buff)?; + bitmap.mutable = true; + Some(bitmap) + } + + /// Returns bitmap width in pixels. + pub fn width(&self) -> i16 { + self.size.x + } + + /// Returns bitmap height in pixels. + pub fn height(&self) -> i16 { + self.size.y + } + + /// Returns bitmap width and height in pixels. + pub fn size(&self) -> Offset { + self.size + } + + /// Returns bitmap stride in bytes. + pub fn stride(&self) -> usize { + self.stride + } + + /// Returns bitmap format. + pub fn format(&self) -> BitmapFormat { + self.format + } + + pub fn view(&self) -> BitmapView { + BitmapView::new(self) + } + + /// Returns the specified row as an immutable slice. + /// + /// Returns None if row is out of range. + pub fn row(&self, row: i16) -> Option<&[T]> { + if row >= 0 && row < self.size.y { + self.wait_for_dma(); + let offset = row as usize * (self.stride / core::mem::size_of::()); + Some(unsafe { + core::slice::from_raw_parts( + (self.ptr as *const T).add(offset), + self.stride / core::mem::size_of::(), + ) + }) + } else { + None + } + } + + /// Returns the specified row as a mutable slice. + /// + /// Returns None if row is out of range. + pub fn row_mut(&mut self, row: i16) -> Option<&mut [T]> { + if row >= 0 && row < self.size.y { + self.wait_for_dma(); + let offset = row as usize * (self.stride / core::mem::size_of::()); + Some(unsafe { + core::slice::from_raw_parts_mut( + (self.ptr as *mut T).add(offset), + self.stride / core::mem::size_of::(), + ) + }) + } else { + None + } + } + + /// Returns specified consecutive rows as a mutable slice + /// + /// Returns None if any of requested row is out of range. + pub fn rows_mut(&mut self, row: i16, height: i16) -> Option<&mut [T]> { + if row >= 0 && height > 0 && row < self.size.y && row + height <= self.size.y { + self.wait_for_dma(); + let offset = self.stride * row as usize; + let len = self.stride * height as usize; + + let array = unsafe { + core::slice::from_raw_parts_mut( + self.ptr as *mut T, + self.size.y as usize * self.stride / core::mem::size_of::(), + ) + }; + + Some(&mut array[offset..offset + len]) + } else { + None + } + } + + /// Return raw mut pointer to the specified bitmap row. + /// + /// # Safety + /// + /// `y` must be in range <0; self.height() - 1>. + pub unsafe fn row_ptr(&self, y: u16) -> *mut cty::c_void { + unsafe { self.ptr.add(self.stride() * y as usize) as *mut cty::c_void } + } + + /// Waits until DMA operation is finished + fn wait_for_dma(&self) { + if self.dma_pending.get() { + BitBlt::wait_for_transfer(); + self.dma_pending.set(false); + } + } + + // Mark bitmap as DMA operation is pending + pub fn mark_dma_pending(&self) { + self.dma_pending.set(true); + } +} + +impl<'a> Drop for Bitmap<'a> { + fn drop(&mut self) { + self.wait_for_dma(); + } +} + +pub struct BitmapView<'a> { + pub bitmap: &'a Bitmap<'a>, + pub offset: Offset, + pub fg_color: Color, + pub bg_color: Color, +} + +impl<'a> BitmapView<'a> { + /// Creates a new reference to the bitmap + pub fn new(bitmap: &'a Bitmap) -> Self { + Self { + bitmap, + offset: Offset::zero(), + fg_color: Color::black(), + bg_color: Color::black(), + } + } + + /// Builds a new structure with offset set to the specified value + pub fn with_offset(self, offset: Offset) -> Self { + Self { + offset: offset + self.offset, + ..self + } + } + + /// Builds a new structure with foreground color set to the specified value + pub fn with_fg(self, fg_color: Color) -> Self { + Self { fg_color, ..self } + } + + /// Builds a new structure with background color set to the specified value + pub fn with_bg(self, bg_color: Color) -> Self { + Self { bg_color, ..self } + } + + /// Returns the bitmap width and height in pixels + pub fn size(&self) -> Offset { + self.bitmap.size + } + + /// Returns the bitmap width in pixels + pub fn width(&self) -> i16 { + self.bitmap.width() + } + + /// Returns the bitmap height in pixels + pub fn height(&self) -> i16 { + self.bitmap.height() + } + + /// Returns the bitmap format + pub fn format(&self) -> BitmapFormat { + self.bitmap.format + } + + /// Returns the specified row as an immutable slice. + /// + /// Returns None if row is out of range. + pub fn row(&self, row: i16) -> Option<&[T]> { + self.bitmap.row(row) + } +} diff --git a/core/embed/rust/src/ui/shape/bitmap/mod.rs b/core/embed/rust/src/ui/shape/bitmap/mod.rs new file mode 100644 index 000000000..cae625e0f --- /dev/null +++ b/core/embed/rust/src/ui/shape/bitmap/mod.rs @@ -0,0 +1,6 @@ +pub mod bitmap_base; +pub mod mono8; +pub mod rgb565; +pub mod rgba8888; + +pub use bitmap_base::{Bitmap, BitmapFormat, BitmapView}; diff --git a/core/embed/rust/src/ui/shape/bitmap/mono8.rs b/core/embed/rust/src/ui/shape/bitmap/mono8.rs new file mode 100644 index 000000000..798c66e6a --- /dev/null +++ b/core/embed/rust/src/ui/shape/bitmap/mono8.rs @@ -0,0 +1,48 @@ +use super::{Bitmap, BitmapFormat, BitmapView}; +use crate::{ + trezorhal::bitblt::BitBlt, + ui::{display::Color, geometry::Rect}, +}; + +impl<'a> Bitmap<'a> { + /// Fills a rectangle with the specified color. + /// + /// The function is aplicable only on bitmaps with RGB565 format. + pub fn mono8_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) { + assert!(self.format() == BitmapFormat::MONO8); + if let Some(bitblt) = BitBlt::new_fill(r, clip, color, alpha) { + let bitblt = bitblt.with_dst(self); + unsafe { bitblt.mono8_fill() }; + self.mark_dma_pending(); + } + } + + // + pub fn mono8_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) { + assert!(self.format() == BitmapFormat::MONO8); + if let Some(bitblt) = BitBlt::new_copy(r, clip, src) { + let bitblt = bitblt.with_dst(self); + match src.format() { + BitmapFormat::MONO1P => unsafe { bitblt.mono8_copy_mono1p() }, + BitmapFormat::MONO4 => unsafe { bitblt.mono8_copy_mono4() }, + _ => panic!("Unsupported DMA operation"), + } + self.mark_dma_pending(); + src.bitmap.mark_dma_pending(); + } + } + + pub fn mono8_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) { + assert!(self.format() == BitmapFormat::MONO8); + if let Some(bitblt) = BitBlt::new_copy(r, clip, src) { + let bitblt = bitblt.with_dst(self); + match src.format() { + BitmapFormat::MONO1P => unsafe { bitblt.mono8_blend_mono1p() }, + BitmapFormat::MONO4 => unsafe { bitblt.mono8_blend_mono4() }, + _ => panic!("Unsupported DMA operation"), + } + self.mark_dma_pending(); + src.bitmap.mark_dma_pending(); + } + } +} diff --git a/core/embed/rust/src/ui/shape/bitmap/rgb565.rs b/core/embed/rust/src/ui/shape/bitmap/rgb565.rs new file mode 100644 index 000000000..79fc0f622 --- /dev/null +++ b/core/embed/rust/src/ui/shape/bitmap/rgb565.rs @@ -0,0 +1,47 @@ +use super::{Bitmap, BitmapFormat, BitmapView}; +use crate::{ + trezorhal::bitblt::BitBlt, + ui::{display::Color, geometry::Rect}, +}; + +impl<'a> Bitmap<'a> { + /// Fills a rectangle with the specified color. + /// + /// The function is aplicable only on bitmaps with RGB565 format. + pub fn rgb565_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) { + assert!(self.format() == BitmapFormat::RGB565); + if let Some(bitblt) = BitBlt::new_fill(r, clip, color, alpha) { + let bitblt = bitblt.with_dst(self); + unsafe { bitblt.rgb565_fill() }; + self.mark_dma_pending(); + } + } + + // + pub fn rgb565_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) { + assert!(self.format() == BitmapFormat::RGB565); + if let Some(bitblt) = BitBlt::new_copy(r, clip, src) { + let bitblt = bitblt.with_dst(self); + match src.format() { + BitmapFormat::MONO4 => unsafe { bitblt.rgb565_copy_mono4() }, + BitmapFormat::RGB565 => unsafe { bitblt.rgb565_copy_rgb565() }, + _ => panic!("Unsupported DMA operation"), + } + self.mark_dma_pending(); + src.bitmap.mark_dma_pending(); + } + } + + pub fn rgb565_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) { + assert!(self.format() == BitmapFormat::RGB565); + if let Some(bitblt) = BitBlt::new_copy(r, clip, src) { + let bitblt = bitblt.with_dst(self); + match src.format() { + BitmapFormat::MONO4 => unsafe { bitblt.rgb565_blend_mono4() }, + _ => panic!("Unsupported DMA operation"), + } + self.mark_dma_pending(); + src.bitmap.mark_dma_pending(); + } + } +} diff --git a/core/embed/rust/src/ui/shape/bitmap/rgba8888.rs b/core/embed/rust/src/ui/shape/bitmap/rgba8888.rs new file mode 100644 index 000000000..38d766f9a --- /dev/null +++ b/core/embed/rust/src/ui/shape/bitmap/rgba8888.rs @@ -0,0 +1,47 @@ +use super::{Bitmap, BitmapFormat, BitmapView}; +use crate::{ + trezorhal::bitblt::BitBlt, + ui::{display::Color, geometry::Rect}, +}; + +impl<'a> Bitmap<'a> { + /// Fills a rectangle with the specified color. + /// + /// The function is aplicable only on bitmaps with RGBA888 format. + pub fn rgba8888_fill(&mut self, r: Rect, clip: Rect, color: Color, alpha: u8) { + assert!(self.format() == BitmapFormat::RGBA8888); + if let Some(bitblt) = BitBlt::new_fill(r, clip, color, alpha) { + let bitblt = bitblt.with_dst(self); + unsafe { bitblt.rgba8888_fill() }; + self.mark_dma_pending(); + } + } + + pub fn rgba8888_copy(&mut self, r: Rect, clip: Rect, src: &BitmapView) { + assert!(self.format() == BitmapFormat::RGBA8888); + if let Some(bitblt) = BitBlt::new_copy(r, clip, src) { + let bitblt = bitblt.with_dst(self); + match src.format() { + BitmapFormat::MONO4 => unsafe { bitblt.rgba8888_copy_mono4() }, + BitmapFormat::RGB565 => unsafe { bitblt.rgba8888_copy_rgb565() }, + BitmapFormat::RGBA8888 => unsafe { bitblt.rgba8888_copy_rgba8888() }, + _ => panic!("Unsupported DMA operation"), + } + self.mark_dma_pending(); + src.bitmap.mark_dma_pending(); + } + } + + pub fn rgba8888_blend(&mut self, r: Rect, clip: Rect, src: &BitmapView) { + assert!(self.format() == BitmapFormat::RGBA8888); + if let Some(bitblt) = BitBlt::new_copy(r, clip, src) { + let bitblt = bitblt.with_dst(self); + match src.format() { + BitmapFormat::MONO4 => unsafe { bitblt.rgba8888_blend_mono4() }, + _ => panic!("Unsupported DMA operation"), + } + self.mark_dma_pending(); + src.bitmap.mark_dma_pending(); + } + } +} diff --git a/core/embed/rust/src/ui/shape/blur.rs b/core/embed/rust/src/ui/shape/blur.rs new file mode 100644 index 000000000..8aca42951 --- /dev/null +++ b/core/embed/rust/src/ui/shape/blur.rs @@ -0,0 +1,45 @@ +use crate::ui::geometry::Rect; + +use super::{Canvas, DrawingCache, Renderer, Shape, ShapeClone}; + +use without_alloc::alloc::LocalAllocLeakExt; + +pub struct Blurring { + // Blurred area + area: Rect, + /// Blurring kernel radius + radius: usize, +} + +/// A shape for the blurring of a specified rectangle area. +impl Blurring { + pub fn new(area: Rect, radius: usize) -> Self { + Self { area, radius } + } + + pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) { + renderer.render_shape(self); + } +} + +impl Shape<'_> for Blurring { + fn bounds(&self, _cache: &DrawingCache) -> Rect { + self.area + } + + fn cleanup(&mut self, _cache: &DrawingCache) {} + + fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache) { + canvas.blur_rect(self.area, self.radius, cache); + } +} + +impl<'s> ShapeClone<'s> for Blurring { + fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>> + where + T: LocalAllocLeakExt<'alloc>, + { + let clone = bump.alloc_t::()?; + Some(clone.uninit.init(Blurring { ..self })) + } +} diff --git a/core/embed/rust/src/ui/shape/cache/blur_cache.rs b/core/embed/rust/src/ui/shape/cache/blur_cache.rs new file mode 100644 index 000000000..499ea07aa --- /dev/null +++ b/core/embed/rust/src/ui/shape/cache/blur_cache.rs @@ -0,0 +1,59 @@ +use super::super::algo::{BlurAlgorithm, BlurBuff}; +use crate::ui::geometry::Offset; +use core::cell::UnsafeCell; +use without_alloc::alloc::LocalAllocLeakExt; + +pub struct BlurCache<'a> { + algo: Option>, + buff: &'a UnsafeCell, + tag: u32, +} + +impl<'a> BlurCache<'a> { + pub fn new<'alloc: 'a, T>(bump: &'alloc T) -> Option + where + T: LocalAllocLeakExt<'alloc>, + { + let buff = bump + .alloc_t::>()? + .uninit + .init(UnsafeCell::new([0; 7928])); // TODO !!! 7928 + + Some(Self { + algo: None, + buff, + tag: 0, + }) + } + + pub fn get( + &mut self, + size: Offset, + radius: usize, + tag: Option, + ) -> Result<(&mut BlurAlgorithm<'a>, u32), ()> { + if let Some(tag) = tag { + if self.tag == tag { + return Ok((unwrap!(self.algo.as_mut()), self.tag)); + } + } + + // Drop the existing blurring inbstance holding + // a mutable reference to its scratchpad buffer + self.algo = None; + self.tag += 1; + + // Now there's nobody else holding any reference to our buffer + // so we can get mutable reference and pass it to a new + // instance of the blurring algorithm + let buff = unsafe { &mut *self.buff.get() }; + + self.algo = Some(BlurAlgorithm::new(size, radius, buff)?); + + Ok((unwrap!(self.algo.as_mut()), self.tag)) + } + + pub const fn get_bump_size() -> usize { + core::mem::size_of::>() + } +} diff --git a/core/embed/rust/src/ui/shape/cache/drawing_cache.rs b/core/embed/rust/src/ui/shape/cache/drawing_cache.rs new file mode 100644 index 000000000..1dfb6f88e --- /dev/null +++ b/core/embed/rust/src/ui/shape/cache/drawing_cache.rs @@ -0,0 +1,138 @@ +use super::zlib_cache::ZlibCache; + +#[cfg(feature = "ui_blurring")] +use super::blur_cache::BlurCache; + +#[cfg(feature = "ui_jpeg_decoder")] +use super::jpeg_cache::JpegCache; + +use core::cell::{RefCell, RefMut}; +use without_alloc::alloc::LocalAllocLeakExt; + +const ALIGN_PAD: usize = 8; + +#[cfg(feature = "xframebuff")] +const ZLIB_CACHE_SLOTS: usize = 1; +#[cfg(not(feature = "xframebuff"))] +const ZLIB_CACHE_SLOTS: usize = 3; + +const JPEG_CACHE_SLOTS: usize = 1; +const RENDER_BUFF_SIZE: usize = (240 * 2 * 16) + ALIGN_PAD; +const IMAGE_BUFF_SIZE: usize = 2048 + ALIGN_PAD; + +pub type ImageBuff = [u8; IMAGE_BUFF_SIZE]; +pub type RenderBuff = [u8; RENDER_BUFF_SIZE]; + +pub type ImageBuffRef<'a> = RefMut<'a, ImageBuff>; +pub type RenderBuffRef<'a> = RefMut<'a, RenderBuff>; + +pub struct DrawingCache<'a> { + zlib_cache: RefCell>, + + #[cfg(feature = "ui_jpeg_decoder")] + jpeg_cache: RefCell>, + + #[cfg(feature = "ui_blurring")] + blur_cache: RefCell>, + + #[cfg(not(feature = "xframebuff"))] + render_buff: &'a RefCell, + image_buff: &'a RefCell, +} + +fn alloc_buf<'a, const S: usize, B>(bump: &'a B) -> Option<&'a RefCell<[u8; S]>> +where + B: LocalAllocLeakExt<'a>, +{ + Some( + bump.alloc_t::>()? + .uninit + .init(RefCell::new([0; S])), + ) +} + +impl<'a> DrawingCache<'a> { + pub fn new(bump_a: &'a TA, bump_b: &'a TB) -> Self + where + TA: LocalAllocLeakExt<'a>, + TB: LocalAllocLeakExt<'a>, + { + Self { + zlib_cache: RefCell::new(unwrap!( + ZlibCache::new(bump_a, ZLIB_CACHE_SLOTS), + "ZLIB cache alloc" + )), + #[cfg(feature = "ui_jpeg_decoder")] + jpeg_cache: RefCell::new(unwrap!( + JpegCache::new(bump_a, JPEG_CACHE_SLOTS), + "JPEG cache alloc" + )), + #[cfg(feature = "ui_blurring")] + blur_cache: RefCell::new(unwrap!(BlurCache::new(bump_a), "Blur cache alloc")), + + #[cfg(not(feature = "xframebuff"))] + render_buff: unwrap!(alloc_buf(bump_b), "Render buff alloc"), + image_buff: unwrap!(alloc_buf(bump_b), "Toif buff alloc"), + } + } + + /// Returns an object for decompression of TOIF images + pub fn zlib(&self) -> RefMut> { + self.zlib_cache.borrow_mut() + } + + /// Returns an object for decompression of JPEG images + #[cfg(feature = "ui_jpeg_decoder")] + pub fn jpeg(&self) -> RefMut> { + self.jpeg_cache.borrow_mut() + } + + /// Returns an object providing blurring algorithm + #[cfg(feature = "ui_blurring")] + pub fn blur(&self) -> RefMut> { + self.blur_cache.borrow_mut() + } + + /// Returns a buffer used for ProgressiveRenderer slice + #[cfg(not(feature = "xframebuff"))] + pub fn render_buff(&self) -> Option> { + self.render_buff.try_borrow_mut().ok() + } + + /// Returns a buffer for intended for drawing of + /// QrCode or ToifImage + pub fn image_buff(&self) -> Option> { + self.image_buff.try_borrow_mut().ok() + } + + pub const fn get_bump_a_size() -> usize { + let mut size = 0; + + size += ZlibCache::get_bump_size(ZLIB_CACHE_SLOTS); + + #[cfg(feature = "ui_jpeg_decoder")] + { + size += JpegCache::get_bump_size(JPEG_CACHE_SLOTS); + } + + #[cfg(feature = "ui_blurring")] + { + size += BlurCache::get_bump_size(); + } + + size + } + + pub const fn get_bump_b_size() -> usize { + let mut size = 0; + + #[cfg(not(feature = "xframebuff"))] + { + size += core::mem::size_of::>(); + } + + size += core::mem::size_of::>(); + + size + } +} diff --git a/core/embed/rust/src/ui/shape/cache/jpeg_cache.rs b/core/embed/rust/src/ui/shape/cache/jpeg_cache.rs new file mode 100644 index 000000000..b3c026a85 --- /dev/null +++ b/core/embed/rust/src/ui/shape/cache/jpeg_cache.rs @@ -0,0 +1,372 @@ +use crate::ui::{ + display::tjpgd, + geometry::{Offset, Point, Rect}, + shape::{BasicCanvas, Bitmap, BitmapFormat, BitmapView, Canvas, Rgb565Canvas}, +}; + +use core::cell::UnsafeCell; +use without_alloc::{alloc::LocalAllocLeakExt, FixedVec}; + +// JDEC work buffer size +// +// number of quantization tables (n_qtbl) = 2..4 (typical 2) +// number of huffman tables (n_htbl) = 2..4 (typical 2) +// mcu size = 1 * 1 .. 2 * 2 = 1..4 (typical 4) +// +// hufflut_ac & hufflut_dc are required only if JD_FASTDECODE == 2 (default) +// +// --------------------------------------------------------------------- +// table | size calculation | MIN..MAX | TYP +// --------------------------------------------------------------------- +// qttbl | n_qtbl * size_of(i32) * 64 | 512..1024 | 512 +// huffbits | n_htbl * size_of(u8) * 16 | 32..64 | 32 +// huffcode | n_htbl * size_of(u16) * 256 | 1024..2048 | 1024 +// huffdata | n_htbl * size_of(u8) * 256 | 512..1024 | 512 +// hufflut_ac | n_htbl * size_of(u16) * 1024 | 4096..8192 | 4096 +// hufflut_dc | n_htbl * size_of(u8) * 1024 | 2048..4096 | 2048 +// workbuf | mcu_size * 192 + 64 | 256..832 | 832 +// mcubuf | (mcu_size + 2) * size_of(u16) * 64 | 384..768 | 768 +// inbuff | JD_SZBUF constant | 512..512 | 512 +// ---------------------------------------------------------------|------ +// SUM | | 9376..18560 | 10336 +// ---------------------------------------------------------------|------ + +const JPEG_SCRATCHPAD_SIZE: usize = 10500; // the same const > 10336 as in original code + +// Buffer for a cached row of JPEG MCUs (up to 240x16 RGB565 pixels) +const ALIGN_PAD: usize = 8; +const JPEG_BUFF_SIZE: usize = (240 * 2 * 16) + ALIGN_PAD; + +pub struct JpegCacheSlot<'a> { + // Reference to compressed data + jpeg: &'a [u8], + // value in range 0..3 leads into scale factor 1 << scale + scale: u8, + // Input buffer referencing compressed data + input: Option>, + // JPEG decoder instance + decoder: Option>, + // Scratchpad memory used by the JPEG decoder + // (it's used just by our decoder and nobody else) + scratchpad: &'a UnsafeCell<[u8; JPEG_SCRATCHPAD_SIZE]>, + // horizontal coordinate of cached row or None + // (valid if row_canvas is Some) + row_y: i16, + // Canvas for recently decoded row of MCU's + row_canvas: Option>, + // Buffer for slice canvas + row_buff: &'a UnsafeCell<[u8; JPEG_BUFF_SIZE]>, +} + +impl<'a> JpegCacheSlot<'a> { + fn new<'alloc: 'a, T>(bump: &'alloc T) -> Option + where + T: LocalAllocLeakExt<'alloc>, + { + let scratchpad = bump + .alloc_t::>()? + .uninit + .init(UnsafeCell::new([0; JPEG_SCRATCHPAD_SIZE])); + + let canvas_buff = bump + .alloc_t::>()? + .uninit + .init(UnsafeCell::new([0; JPEG_BUFF_SIZE])); + + Some(Self { + jpeg: &[], + scale: 0, + input: None, + decoder: None, + scratchpad, + row_y: 0, + row_canvas: None, + row_buff: canvas_buff, + }) + } + + fn reset<'i: 'a>(&mut self, jpeg: &'i [u8], scale: u8) -> Result<(), tjpgd::Error> { + // Drop the existing decoder holding + // a mutable reference to the scratchpad and canvas buffer & c + self.decoder = None; + self.row_canvas = None; + + if !jpeg.is_empty() { + // Now there's nobody else holding any reference to our scratchpad buffer + // so we can get a mutable reference and pass it to a new + // instance of the JPEG decoder + let scratchpad = unsafe { &mut *self.scratchpad.get() }; + // Prepare a input buffer + let mut input = tjpgd::BufferInput(jpeg); + // Initialize the decoder by reading headers from input + let mut decoder = tjpgd::JDEC::new(&mut input, scratchpad)?; + // Set decoder scale factor + decoder.set_scale(scale)?; + self.decoder = Some(decoder); + // Save modified input buffer + self.input = Some(input); + } else { + self.input = None; + } + + self.jpeg = jpeg; + self.scale = scale; + Ok(()) + } + + fn is_for<'i: 'a>(&self, jpeg: &'i [u8], scale: u8) -> bool { + jpeg == self.jpeg && scale == self.scale && self.decoder.is_some() + } + + pub fn get_size<'i: 'a>(&mut self, jpeg: &'i [u8], scale: u8) -> Result { + if !self.is_for(jpeg, scale) { + self.reset(jpeg, scale)?; + } + let decoder = unwrap!(self.decoder.as_mut()); // should never fail + let divisor = 1 << self.scale; + Ok(Offset::new( + decoder.width() / divisor, + decoder.height() / divisor, + )) + } + + // left-top origin of output rectangle must be aligned to JPEG MCU size + pub fn decompress_mcu<'i: 'a>( + &mut self, + jpeg: &'i [u8], + scale: u8, + offset: Point, + output: &mut dyn FnMut(Rect, BitmapView) -> bool, + ) -> Result<(), tjpgd::Error> { + // Reset the slot if the JPEG image is different + if !self.is_for(jpeg, scale) { + self.reset(jpeg, scale)?; + } + + // Get coordinates of the next coming MCU + let decoder = unwrap!(self.decoder.as_ref()); // should never fail + let divisor = 1 << self.scale; + let next_mcu = Offset::new( + decoder.next_mcu().0 as i16 / divisor, + decoder.next_mcu().1 as i16 / divisor, + ); + + // Get height of the MCUs (8 or 16pixels) + let mcu_height = decoder.mcu_height() / (1 << self.scale); + + // Reset the decoder if pixel at the offset was already decoded + if offset.y < next_mcu.y || (offset.x < next_mcu.x && offset.y < next_mcu.y + mcu_height) { + self.reset(self.jpeg, scale)?; + } + + let decoder = unwrap!(self.decoder.as_mut()); // should never fail + let input = unwrap!(self.input.as_mut()); // should never fail + let mut output = JpegFnOutput::new(output); + + match decoder.decomp2(input, &mut output) { + Ok(_) | Err(tjpgd::Error::Interrupted) => Ok(()), + Err(e) => Err(e), + } + } + + pub fn decompress_row<'i: 'a>( + &mut self, + jpeg: &'i [u8], + scale: u8, + mut offset_y: i16, + output: &mut dyn FnMut(Rect, BitmapView) -> bool, + ) -> Result<(), tjpgd::Error> { + // Reset the slot if the JPEG image is different + if !self.is_for(jpeg, scale) { + self.reset(jpeg, scale)?; + } + + let mut row_canvas = self.row_canvas.take(); + let mut row_y = self.row_y; + + // Use cached data if possible + if let Some(row_canvas) = row_canvas.as_mut() { + if offset_y >= self.row_y && offset_y < self.row_y + row_canvas.height() { + if !output( + Rect::from_size(row_canvas.size()).translate(Offset::new(0, row_y)), + row_canvas.view(), + ) { + return Ok(()); + } + // Align to the next MCU row + offset_y += row_canvas.height() - offset_y % row_canvas.height(); + } + } else { + // Create a new row for cahing decoded JPEG data + // Now there's nobody else holding any reference to canvas_buff so + // we can get a mutable reference and pass it to a new instance + // of Rgb565Canvas + let canvas_buff = unsafe { &mut *self.row_buff.get() }; + // Prepare canvas as a cache for a row of decoded JPEG MCUs + let decoder = unwrap!(self.decoder.as_ref()); // shoud never fail + let divisor = 1 << self.scale; + row_canvas = Some(unwrap!( + Rgb565Canvas::new( + Offset::new(decoder.width() / divisor, decoder.mcu_height() / divisor), + None, + None, + canvas_buff + ), + "Buffer too small" + )); + } + + self.decompress_mcu( + jpeg, + scale, + Point::new(0, offset_y), + &mut |mcu_r, mcu_bitmap| { + // Get canvas for MCU caching + let row_canvas = unwrap!(row_canvas.as_mut()); // should never fail + + // Calculate coordinates in the row canvas + let dst_r = Rect { + y0: 0, + y1: mcu_r.height(), + ..mcu_r + }; + // Draw a MCU + row_canvas.draw_bitmap(dst_r, mcu_bitmap); + + if mcu_r.x1 < row_canvas.size().x { + // We are not done with the row yet + true + } else { + // We have a complete row, let's pass it to the callee + row_y = mcu_r.y0; + output( + Rect::from_size(row_canvas.size()).translate(Offset::new(0, row_y)), + row_canvas.view(), + ) + } + }, + )?; + + // Store the recently decoded row for future use + self.row_y = row_y; + self.row_canvas = row_canvas; + + Ok(()) + } +} + +struct JpegFnOutput +where + F: FnMut(Rect, BitmapView) -> bool, +{ + output: F, +} + +impl JpegFnOutput +where + F: FnMut(Rect, BitmapView) -> bool, +{ + pub fn new(output: F) -> Self { + Self { output } + } +} + +impl trezor_tjpgdec::JpegOutput for JpegFnOutput +where + F: FnMut(Rect, BitmapView) -> bool, +{ + fn write( + &mut self, + _jd: &tjpgd::JDEC, + rect_origin: (u32, u32), + rect_size: (u32, u32), + pixels: &[u16], + ) -> bool { + // MCU coordinates in source image + let mcu_r = Rect::from_top_left_and_size( + Point::new(rect_origin.0 as i16, rect_origin.1 as i16), + Offset::new(rect_size.0 as i16, rect_size.1 as i16), + ); + + // SAFETY: aligning from [u16] -> [u8] + let (_, pixels, _) = unsafe { pixels.align_to() }; + + // Create readonly bitmap + let mcu_bitmap = unwrap!(Bitmap::new( + BitmapFormat::RGB565, + None, + mcu_r.size(), + None, + pixels, + )); + + // Return true to continue decompression + (self.output)(mcu_r, BitmapView::new(&mcu_bitmap)) + } +} + +pub struct JpegCache<'a> { + slots: FixedVec<'a, JpegCacheSlot<'a>>, +} + +impl<'a> JpegCache<'a> { + pub fn new<'alloc: 'a, T>(bump: &'alloc T, slot_count: usize) -> Option + where + T: LocalAllocLeakExt<'alloc>, + { + assert!(slot_count <= 1); // we support just 1 decoder + + let mut cache = Self { + slots: bump.fixed_vec(slot_count)?, + }; + + for _ in 0..cache.slots.capacity() { + unwrap!(cache.slots.push(JpegCacheSlot::new(bump)?)); // should never fail + } + + Some(cache) + } + + pub fn get_size<'i: 'a>(&mut self, jpeg: &'i [u8], scale: u8) -> Result { + if self.slots.capacity() > 0 { + self.slots[0].get_size(jpeg, scale) + } else { + Err(tjpgd::Error::MemoryPool) + } + } + + pub fn decompress_mcu<'i: 'a>( + &mut self, + jpeg: &'i [u8], + scale: u8, + offset: Point, + output: &mut dyn FnMut(Rect, BitmapView) -> bool, + ) -> Result<(), tjpgd::Error> { + if self.slots.capacity() > 0 { + self.slots[0].decompress_mcu(jpeg, scale, offset, output) + } else { + Err(tjpgd::Error::MemoryPool) + } + } + + pub fn decompress_row<'i: 'a>( + &mut self, + jpeg: &'i [u8], + scale: u8, + offset_y: i16, + output: &mut dyn FnMut(Rect, BitmapView) -> bool, + ) -> Result<(), tjpgd::Error> { + if self.slots.capacity() > 0 { + self.slots[0].decompress_row(jpeg, scale, offset_y, output) + } else { + Err(tjpgd::Error::MemoryPool) + } + } + + pub const fn get_bump_size(slot_count: usize) -> usize { + (core::mem::size_of::() + + core::mem::size_of::>() + + core::mem::size_of::>()) + * slot_count + } +} diff --git a/core/embed/rust/src/ui/shape/cache/mod.rs b/core/embed/rust/src/ui/shape/cache/mod.rs new file mode 100644 index 000000000..0717577ee --- /dev/null +++ b/core/embed/rust/src/ui/shape/cache/mod.rs @@ -0,0 +1,7 @@ +pub mod blur_cache; +pub mod drawing_cache; + +#[cfg(feature = "ui_jpeg_decoder")] +pub mod jpeg_cache; + +pub mod zlib_cache; diff --git a/core/embed/rust/src/ui/shape/cache/zlib_cache.rs b/core/embed/rust/src/ui/shape/cache/zlib_cache.rs new file mode 100644 index 000000000..f1a8194c2 --- /dev/null +++ b/core/embed/rust/src/ui/shape/cache/zlib_cache.rs @@ -0,0 +1,173 @@ +use crate::{ + trezorhal::uzlib::{UzlibContext, UZLIB_WINDOW_SIZE}, + ui::display::toif::Toif, +}; +use core::cell::UnsafeCell; +use without_alloc::{alloc::LocalAllocLeakExt, FixedVec}; + +struct ZlibCacheSlot<'a> { + /// Reference to compressed data + zdata: &'a [u8], + /// Current offset in docempressed data + offset: usize, + /// Decompression context for the current zdata + dc: Option>, + /// Window used by current decompression context. + /// (It's used just by own dc and nobody else.) + window: &'a UnsafeCell<[u8; UZLIB_WINDOW_SIZE]>, +} + +impl<'a> ZlibCacheSlot<'a> { + fn new<'alloc: 'a, T>(bump: &'alloc T) -> Option + where + T: LocalAllocLeakExt<'alloc>, + { + let window = bump + .alloc_t::>()? + .uninit + .init(UnsafeCell::new([0; UZLIB_WINDOW_SIZE])); + + Some(Self { + zdata: &[], + offset: 0, + dc: None, + window, + }) + } + + /// May be called with zdata == &[] to make the slot free + fn reset(&mut self, zdata: &'a [u8]) { + // Drop the existing decompression context holding + // a mutable reference to window buffer + self.dc = None; + + if !zdata.is_empty() { + // Now there's nobody else holding any reference to our window + // so we can get mutable reference and pass it to a new + // instance of the decompression context + let window = unsafe { &mut *self.window.get() }; + + self.dc = Some(UzlibContext::new(zdata, Some(window))); + } + + self.offset = 0; + self.zdata = zdata; + } + + fn uncompress(&mut self, dest_buf: &mut [u8]) -> Result { + if let Some(dc) = self.dc.as_mut() { + match dc.uncompress(dest_buf) { + Ok(done) => { + if done { + self.reset(&[]); + } else { + self.offset += dest_buf.len(); + } + Ok(done) + } + Err(e) => Err(e), + } + } else { + Err(()) + } + } + + fn skip(&mut self, nbytes: usize) -> Result { + if let Some(dc) = self.dc.as_mut() { + match dc.skip(nbytes) { + Ok(done) => { + if done { + self.reset(&[]); + } else { + self.offset += nbytes; + } + + Ok(done) + } + Err(e) => Err(e), + } + } else { + Err(()) + } + } + + fn is_for(&self, zdata: &[u8], offset: usize) -> bool { + self.zdata == zdata && self.offset == offset + } +} + +pub struct ZlibCache<'a> { + slots: FixedVec<'a, ZlibCacheSlot<'a>>, +} + +impl<'a> ZlibCache<'a> { + pub fn new<'alloc: 'a, T>(bump: &'alloc T, slot_count: usize) -> Option + where + T: LocalAllocLeakExt<'alloc>, + { + let mut cache = Self { + slots: bump.fixed_vec(slot_count)?, + }; + + for _ in 0..cache.slots.capacity() { + unwrap!(cache.slots.push(ZlibCacheSlot::new(bump)?)); // should never fail + } + + Some(cache) + } + + fn select_slot_for_reuse(&self) -> Result { + if self.slots.capacity() > 0 { + let mut selected = 0; + for (i, slot) in self.slots.iter().enumerate() { + if slot.dc.is_none() { + selected = i; + break; + } + } + Ok(selected) + } else { + Err(()) + } + } + + pub fn uncompress( + &mut self, + zdata: &'a [u8], + offset: usize, + dest_buf: &mut [u8], + ) -> Result { + let slot = self + .slots + .iter_mut() + .find(|slot| slot.is_for(zdata, offset)); + + if let Some(slot) = slot { + slot.uncompress(dest_buf) + } else { + let selected = self.select_slot_for_reuse()?; + let slot = &mut self.slots[selected]; + slot.reset(zdata); + slot.skip(offset)?; + slot.uncompress(dest_buf) + } + } + + pub fn uncompress_toif( + &mut self, + toif: Toif<'a>, + from_row: i16, + dest_buf: &mut [u8], + ) -> Result<(), ()> { + let from_offset = toif.stride() * from_row as usize; + self.uncompress(toif.zdata(), from_offset, dest_buf)?; + Ok(()) + } + + pub const fn get_bump_size(slot_count: usize) -> usize { + (core::mem::size_of::() + + core::mem::size_of::>() + + 16) + * slot_count + } +} diff --git a/core/embed/rust/src/ui/shape/canvas/common.rs b/core/embed/rust/src/ui/shape/canvas/common.rs new file mode 100644 index 000000000..db7801843 --- /dev/null +++ b/core/embed/rust/src/ui/shape/canvas/common.rs @@ -0,0 +1,887 @@ +use crate::ui::{ + display::Color, + geometry::{Offset, Point, Rect}, +}; + +use super::super::{ + algo::{circle_points, line_points, sin_i16, PI4}, + BitmapView, Viewport, +}; + +#[cfg(feature = "ui_blurring")] +use crate::ui::shape::DrawingCache; + +pub trait BasicCanvas { + /// Returns dimensions of the canvas in pixels. + fn size(&self) -> Offset; + + /// Returns the dimensions of the canvas as a rectangle with + /// the top-left at (0,0). + fn bounds(&self) -> Rect { + Rect::from_size(self.size()) + } + + /// Returns the width of the canvas in pixels. + fn width(&self) -> i16 { + self.size().x + } + + /// Returns the height of the canvas in pixels. + fn height(&self) -> i16 { + self.size().y + } + + /// Gets the current drawing viewport previously set by `set_viewport()` + /// function. + fn viewport(&self) -> Viewport; + + /// Sets the active viewport valid for all subsequent drawing operations. + fn set_viewport(&mut self, vp: Viewport); + + /// Sets the new viewport that's intersection of the + /// current viewport and the `window` rectangle relative + /// to the current viewport. The viewport's origin is + /// set to the top-left corener of the `window`. + fn set_window(&mut self, window: Rect) -> Viewport { + let viewport = self.viewport(); + self.set_viewport(viewport.relative_window(window)); + viewport + } + + /// Sets the new viewport that's intersection of the + /// current viewport and the `clip` rectangle relative + /// to the current viewport. The viewport's origin is + /// not changed. + fn set_clip(&mut self, clip: Rect) -> Viewport { + let viewport = self.viewport(); + self.set_viewport(viewport.relative_clip(clip)); + viewport + } + + /// Draws a filled rectangle with the specified color. + fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8); + + /// Fills the canvas background with the specified color. + fn fill_background(&mut self, color: Color) { + self.fill_rect(self.viewport().clip, color, 255); + } + + /// Draws a bitmap of bitmap into to the rectangle. + fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView); +} + +pub trait Canvas: BasicCanvas { + /// Returns a non-mutable view of the underlying bitmap. + fn view(&self) -> BitmapView; + + /// Draw a pixel at specified coordinates. + fn draw_pixel(&mut self, pt: Point, color: Color); + + /// Draws a single pixel and blends its color with the background. + /// + /// - If alpha == 255, the (foreground) pixel color is used. + /// - If 0 < alpha << 255, pixel and backround colors are blended. + /// - If alpha == 0, the background color is used. + fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8); + + /// Blends a bitmap with the canvas background + fn blend_bitmap(&mut self, r: Rect, src: BitmapView); + + /// Applies a blur effect to the specified rectangle. + /// + /// The blur effect works properly only when the rectangle is not clipped, + /// which is a strong constraint that's hard to be met. The function uses a + /// simple box filter, where the 'radius' argument represents the length + /// of the sides of this filter. + /// + /// It's important to be aware that strong artifacts may appear on images + /// with horizontal/vertical lines. + #[cfg(feature = "ui_blurring")] + fn blur_rect(&mut self, r: Rect, radius: usize, cache: &DrawingCache); + + /// Draws an outline of a rectangle with rounded corners. + fn draw_round_rect(&mut self, r: Rect, radius: i16, color: Color) { + let split = unwrap!(circle_points(radius).last()).v; + + let b = Rect { + y1: r.y0 + radius - split + 1, + ..r + }; + + if self.viewport().contains(b) { + for p in circle_points(radius) { + let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v); + let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v); + if p.v == radius && p.last { + self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, 255); + } else { + self.draw_pixel(pt_l, color); + self.draw_pixel(pt_r, color); + } + } + } + + let b = Rect { + y0: r.y0 + radius - split + 1, + y1: r.y0 + radius + 1, + ..r + }; + + if self.viewport().contains(b) { + for p in circle_points(radius).take_while(|p| p.u < p.v) { + let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u); + let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u); + self.draw_pixel(pt_l, color); + self.draw_pixel(pt_r, color); + } + } + + self.fill_rect( + Rect { + x0: r.x0, + y0: r.y0 + radius + 1, + x1: r.x0 + 1, + y1: r.y1 - radius - 1, + }, + color, + 255, + ); + + self.fill_rect( + Rect { + x0: r.x1 - 1, + y0: r.y0 + radius + 1, + x1: r.x1, + y1: r.y1 - radius - 1, + }, + color, + 255, + ); + + let b = Rect { + y0: r.y1 - radius - 1, + y1: r.y1 - radius - 1 + split, + ..r + }; + + if self.viewport().contains(b) { + for p in circle_points(radius).take_while(|p| p.u < p.v) { + let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u); + let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u); + self.draw_pixel(pt_l, color); + self.draw_pixel(pt_r, color); + } + } + + let b = Rect { + y0: r.y1 - radius - 1 + split, + ..r + }; + + if self.viewport().contains(b) { + for p in circle_points(radius) { + let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v); + let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v); + + if p.v == radius && p.last { + self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, 255); + } else { + self.draw_pixel(pt_l, color); + self.draw_pixel(pt_r, color); + } + } + } + } + + /// Draws filled rectangle with rounded corners. + #[cfg(not(feature = "ui_antialiasing"))] + fn fill_round_rect(&mut self, r: Rect, radius: i16, color: Color, alpha: u8) { + let split = unwrap!(circle_points(radius).last()).v; + + let b = Rect { + y1: r.y0 + radius - split + 1, + ..r + }; + + if self.viewport().contains(b) { + for p in circle_points(radius) { + if p.last { + let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v); + let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v); + self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha); + } + } + } + + let b = Rect { + y0: r.y0 + radius - split + 1, + y1: r.y0 + radius + 1, + ..r + }; + + if self.viewport().contains(b) { + for p in circle_points(radius).take_while(|p| p.u < p.v) { + let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u); + let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u); + self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha); + } + } + + self.fill_rect( + Rect { + x0: r.x0, + y0: r.y0 + radius + 1, + x1: r.x1, + y1: r.y1 - radius - 1, + }, + color, + alpha, + ); + + let b = Rect { + y0: r.y1 - radius - 1, + y1: r.y1 - radius - 1 + split, + ..r + }; + + if self.viewport().contains(b) { + for p in circle_points(radius).take_while(|p| p.u < p.v) { + let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u); + let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u); + self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha); + } + } + + let b = Rect { + y0: r.y1 - radius - 1 + split, + ..r + }; + + if self.viewport().contains(b) { + for p in circle_points(radius) { + if p.last { + let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v); + let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v); + self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha); + } + } + } + } + + /// Draws filled rectangle with antialiased rounded corners. + #[cfg(feature = "ui_antialiasing")] + fn fill_round_rect(&mut self, r: Rect, radius: i16, color: Color, alpha: u8) { + let split = unwrap!(circle_points(radius).last()).v; + + let b = Rect { + y1: r.y0 + radius - split + 1, + ..r + }; + + let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 }; + + if self.viewport().contains(b) { + for p in circle_points(radius) { + let pt_l = Point::new(r.x0 + radius - p.u, r.y0 + radius - p.v); + let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y0 + radius - p.v); + self.blend_pixel(pt_l, color, alpha_mul(p.frac)); + self.blend_pixel(pt_r, color, alpha_mul(p.frac)); + + if p.first { + let inner = Rect::new(pt_l.onright(), pt_r.under()); + self.fill_rect(inner, color, alpha); + } + } + } + + let b = Rect { + y0: r.y0 + radius - split + 1, + y1: r.y0 + radius + 1, + ..r + }; + + if self.viewport().contains(b) { + for p in circle_points(radius).take_while(|p| p.u < p.v) { + let pt_l = Point::new(r.x0 + radius - p.v, r.y0 + radius - p.u); + let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y0 + radius - p.u); + self.blend_pixel(pt_l, color, alpha_mul(p.frac)); + self.blend_pixel(pt_r, color, alpha_mul(p.frac)); + + let inner = Rect::new(pt_l.onright(), pt_r.under()); + self.fill_rect(inner, color, alpha); + } + } + + self.fill_rect( + Rect { + x0: r.x0, + y0: r.y0 + radius + 1, + x1: r.x1, + y1: r.y1 - radius - 1, + }, + color, + alpha, + ); + + let b = Rect { + y0: r.y1 - radius - 1, + y1: r.y1 - radius - 1 + split, + ..r + }; + + if self.viewport().contains(b) { + for p in circle_points(radius).take_while(|p| p.u < p.v) { + let pt_l = Point::new(r.x0 + radius - p.v, r.y1 - radius - 1 + p.u); + let pt_r = Point::new(r.x1 - radius + p.v - 1, r.y1 - radius - 1 + p.u); + self.blend_pixel(pt_l, color, alpha_mul(p.frac)); + self.blend_pixel(pt_r, color, alpha_mul(p.frac)); + + let b = Rect::new(pt_l.onright(), pt_r.under()); + self.fill_rect(b, color, alpha); + } + } + + let b = Rect { + y0: r.y1 - radius - 1 + split, + ..r + }; + + if self.viewport().contains(b) { + for p in circle_points(radius) { + let pt_l = Point::new(r.x0 + radius - p.u, r.y1 - radius - 1 + p.v); + self.blend_pixel(pt_l, color, alpha_mul(p.frac)); + let pt_r = Point::new(r.x1 - radius + p.u - 1, r.y1 - radius - 1 + p.v); + self.blend_pixel(pt_r, color, alpha_mul(p.frac)); + + if p.first { + let b = Rect::new(pt_l.onright(), pt_r.under()); + self.fill_rect(b, color, alpha); + } + } + } + } + + // Draws circle with the specified center and the radius. + #[cfg(not(feature = "ui_antialiasing"))] + fn draw_circle(&mut self, center: Point, radius: i16, color: Color) { + let split = unwrap!(circle_points(radius).last()).v; + + let r = Rect::new( + Point::new(center.x - radius, center.y - radius), + Point::new(center.x + radius + 1, center.y - split + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius) { + let pt_l = Point::new(center.x - p.u, center.y - p.v); + let pt_r = Point::new(center.x + p.u, center.y - p.v); + self.draw_pixel(pt_l, color); + self.draw_pixel(pt_r, color); + } + } + + let r = Rect::new( + Point::new(center.x - radius, center.y - split), + Point::new(center.x + radius + 1, center.y + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius).take_while(|p| p.u < p.v) { + let pt_l = Point::new(center.x - p.v, center.y - p.u); + let pt_r = Point::new(center.x + p.v, center.y - p.u); + self.draw_pixel(pt_l, color); + self.draw_pixel(pt_r, color); + } + } + + let r = Rect::new( + Point::new(center.x - radius, center.y + 1), + Point::new(center.x + radius + 1, center.y + split + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) { + let pt_l = Point::new(center.x - p.v, center.y + p.u); + let pt_r = Point::new(center.x + p.v, center.y + p.u); + self.draw_pixel(pt_l, color); + self.draw_pixel(pt_r, color); + } + } + + let r = Rect::new( + Point::new(center.x - radius, center.y + split), + Point::new(center.x + radius + 1, center.y + radius + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius) { + let pt_l = Point::new(center.x - p.u, center.y + p.v); + let pt_r = Point::new(center.x + p.u, center.y + p.v); + self.draw_pixel(pt_l, color); + self.draw_pixel(pt_r, color); + } + } + } + + /// Draws antialiased circle with the specified center and the radius. + /*#[cfg(feature = "ui_antialiasing")] + fn draw_circle(&mut self, center: Point, radius: i16, color: Color) { + let split = unwrap!(circle_points(radius).last()).v; + + let r = Rect::new( + Point::new(center.x - radius, center.y - radius), + Point::new(center.x + radius + 1, center.y - split + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius) { + let pt_l = Point::new(center.x - p.u, center.y - p.v); + self.blend_pixel(pt_l, color, p.frac); + self.blend_pixel(pt_l.under(), color, 255 - p.frac); + let pt_r = Point::new(center.x + p.u, center.y - p.v); + self.blend_pixel(pt_r, color, p.frac); + self.blend_pixel(pt_r.under(), color, 255 - p.frac); + } + } + + let r = Rect::new( + Point::new(center.x - radius, center.y - split), + Point::new(center.x + radius + 1, center.y + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius).take_while(|p| p.u < p.v) { + let pt_l = Point::new(center.x - p.v, center.y - p.u); + self.blend_pixel(pt_l, color, p.frac); + self.blend_pixel(pt_l.onright(), color, 255 - p.frac); + let pt_r = Point::new(center.x + p.v, center.y - p.u); + self.blend_pixel(pt_r, color, p.frac); + self.blend_pixel(pt_r.onleft(), color, 255 - p.frac); + } + } + + let r = Rect::new( + Point::new(center.x - radius, center.y + 1), + Point::new(center.x + radius + 1, center.y + split + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) { + let pt_l = Point::new(center.x - p.v, center.y + p.u); + self.blend_pixel(pt_l, color, p.frac); + self.blend_pixel(pt_l.onright(), color, 255 - p.frac); + let pt_r = Point::new(center.x + p.v, center.y + p.u); + self.blend_pixel(pt_r, color, p.frac); + self.blend_pixel(pt_r.onleft(), color, 255 - p.frac); + } + } + + let r = Rect::new( + Point::new(center.x - radius, center.y + split), + Point::new(center.x + radius + 1, center.y + radius + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius) { + let pt_l = Point::new(center.x - p.u, center.y + p.v); + self.blend_pixel(pt_l, color, p.frac); + self.blend_pixel(pt_l.above(), color, 255 - p.frac); + let pt_r = Point::new(center.x + p.u, center.y + p.v); + self.blend_pixel(pt_r, color, p.frac); + self.blend_pixel(pt_r.above(), color, 255 - p.frac); + } + } + }*/ + + /// Draws filled circle with the specified center and the radius. + #[cfg(not(feature = "ui_antialiasing"))] + fn fill_circle(&mut self, center: Point, radius: i16, color: Color) { + let split = unwrap!(circle_points(radius).last()).v; + let alpha = 255; + + let r = Rect::new( + Point::new(center.x - radius, center.y - radius), + Point::new(center.x + radius + 1, center.y - split + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius) { + if p.last { + let pt_l = Point::new(center.x - p.u, center.y - p.v); + let pt_r = Point::new(center.x + p.u, center.y - p.v); + self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha); + } + } + } + + let r = Rect::new( + Point::new(center.x - radius, center.y - split), + Point::new(center.x + radius + 1, center.y + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius).take_while(|p| p.u < p.v) { + let pt_l = Point::new(center.x - p.v, center.y - p.u); + let pt_r = Point::new(center.x + p.v, center.y - p.u); + self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha); + } + } + + let r = Rect::new( + Point::new(center.x - radius, center.y + 1), + Point::new(center.x + radius + 1, center.y + split + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) { + let pt_l = Point::new(center.x - p.v, center.y + p.u); + let pt_r = Point::new(center.x + p.v, center.y + p.u); + self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha); + } + } + + let r = Rect::new( + Point::new(center.x - radius, center.y + split), + Point::new(center.x + radius + 1, center.y + radius + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius) { + if p.last { + let pt_l = Point::new(center.x - p.u, center.y + p.v); + let pt_r = Point::new(center.x + p.u, center.y + p.v); + self.fill_rect(Rect::new(pt_l, pt_r.onright().under()), color, alpha); + } + } + } + } + + /// Draws antialiased filled circle with the specified center and the + /// radius. + #[cfg(feature = "ui_antialiasing")] + fn fill_circle(&mut self, center: Point, radius: i16, color: Color) { + let split = unwrap!(circle_points(radius).last()).v; + + let alpha = 255; + let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 }; + + let r = Rect::new( + Point::new(center.x - radius, center.y - radius), + Point::new(center.x + radius + 1, center.y - split + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius) { + let pt_l = Point::new(center.x - p.u, center.y - p.v); + let pt_r = Point::new(center.x + p.u, center.y - p.v); + self.blend_pixel(pt_l, color, alpha_mul(p.frac)); + if pt_l != pt_r { + self.blend_pixel(pt_r, color, alpha_mul(p.frac)); + } + + if p.first { + let r = Rect::new(pt_l.onright(), pt_r.under()); + self.fill_rect(r, color, alpha); + } + } + } + + let r = Rect::new( + Point::new(center.x - radius, center.y - split), + Point::new(center.x + radius + 1, center.y + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius).take_while(|p| p.u < p.v) { + let pt_l = Point::new(center.x - p.v, center.y - p.u); + let pt_r = Point::new(center.x + p.v, center.y - p.u); + self.blend_pixel(pt_l, color, alpha_mul(p.frac)); + self.blend_pixel(pt_r, color, alpha_mul(p.frac)); + + let r = Rect::new(pt_l.onright(), pt_r.under()); + self.fill_rect(r, color, alpha); + } + } + + let r = Rect::new( + Point::new(center.x - radius, center.y + 1), + Point::new(center.x + radius + 1, center.y + split + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius).skip(1).take_while(|p| p.u < p.v) { + let pt_l = Point::new(center.x - p.v, center.y + p.u); + let pt_r = Point::new(center.x + p.v, center.y + p.u); + self.blend_pixel(pt_l, color, alpha_mul(p.frac)); + self.blend_pixel(pt_r, color, alpha_mul(p.frac)); + + let r = Rect::new(pt_l.onright(), pt_r.under()); + self.fill_rect(r, color, alpha); + } + } + + let r = Rect::new( + Point::new(center.x - radius, center.y + split), + Point::new(center.x + radius + 1, center.y + radius + 1), + ); + + if self.viewport().contains(r) { + for p in circle_points(radius) { + let pt_l = Point::new(center.x - p.u, center.y + p.v); + let pt_r = Point::new(center.x + p.u, center.y + p.v); + if pt_l != pt_r { + self.blend_pixel(pt_l, color, alpha_mul(p.frac)); + } + self.blend_pixel(pt_r, color, alpha_mul(p.frac)); + + if p.first { + let r = Rect::new(pt_l.onright(), pt_r.under()); + self.fill_rect(r, color, alpha); + } + } + } + } + + /// Fills circle sector with a specified color. + fn fill_sector( + &mut self, + center: Point, + radius: i16, + mut start: i16, + mut end: i16, + color: Color, + ) { + start = (PI4 * 8 + start % (PI4 * 8)) % (PI4 * 8); + end = (PI4 * 8 + end % (PI4 * 8)) % (PI4 * 8); + + let alpha = 255; + let alpha_mul = |a: u8| -> u8 { ((a as u16 * alpha as u16) / 255) as u8 }; + + if start != end { + // The algorithm fills everything except the middle point ;-) + self.draw_pixel(center, color); + } + + for octant in 0..8 { + let angle = octant * PI4; + + // Function for calculation of 'u' coordinate inside the circle octant + // radius * sin(angle) + let sin = |angle: i16| -> i16 { sin_i16(angle, radius) }; + + // Calculate the octant's bounding rectangle + let p = Point::new(sin(PI4) + 1, -radius - 1).rot(octant); + let r = Rect::new(center, p + center.into()).normalize(); + + // Skip octant if not visible + if !self.viewport().contains(r) { + continue; + } + + // Function for filling a line between two endpoints with antialiasing. + // The function is special for each octant using 4 different axes of symmetry + let filler = &mut |p1: Option, p1_frac, p2: Point, p2_frac| { + let p2: Point = center + p2.rot(octant).into(); + self.blend_pixel(p2, color, alpha_mul(p2_frac)); + if let Some(p1) = p1 { + let p1: Point = center + p1.rot(octant).into(); + let ofs = Point::new(-1, 0).rot(octant); + self.blend_pixel(p1 + ofs.into(), color, alpha_mul(p1_frac)); + if ofs.x + ofs.y < 0 { + if ofs.x != 0 { + self.fill_rect(Rect::new(p1, p2.under()), color, alpha); + } else { + self.fill_rect(Rect::new(p1, p2.onright()), color, alpha); + } + } else { + let p1 = p1 + ofs.into(); + let p2 = p2 + ofs.into(); + if ofs.x != 0 { + self.fill_rect(Rect::new(p2, p1.under()), color, alpha); + } else { + self.fill_rect(Rect::new(p2, p1.onright()), color, alpha); + } + } + } + }; + + let corr = if octant & 1 == 0 { + // The clockwise octant + |angle| angle + } else { + // The anticlockwise octant + |angle| PI4 - angle + }; + + if start <= end { + // Octant may contain 0 or 1 sector + if start < angle + PI4 && end > angle { + if start <= angle && end >= angle + PI4 { + // Fill all pixels in the octant + fill_octant(radius, 0, sin(PI4), filler); + } else { + // Partial fill + let u1 = if start <= angle { + sin(corr(0)) + } else { + sin(corr(start - angle)) + }; + let u2 = if end <= angle + PI4 { + sin(corr(end - angle)) + } else { + sin(corr(PI4)) + }; + + fill_octant(radius, u1, u2, filler); + } + } + } else { + // Octant may contain 0, 1 or 2 sectors + if end >= angle + PI4 || start <= angle { + // Fill all pixels in the octant + fill_octant(radius, 0, sin(PI4), filler); + } else { + // Partial fill + if (end > angle) && (end < angle + PI4) { + // Fill up to `end` + fill_octant(radius, sin(corr(0)), sin(corr(end - angle)), filler); + } + if start < angle + PI4 { + // Fill all from `start` + fill_octant(radius, sin(corr(start - angle)), sin(corr(PI4)), filler); + } + } + } + } + } +} + +/// Calculates endpoints of a single octant of a circle +/// +/// Used internally by `Canvas::fill_sector()`. +fn fill_octant( + radius: i16, + mut u1: i16, + mut u2: i16, + fill: &mut impl FnMut(Option, u8, Point, u8), +) { + // Starting end ending points on + if u1 > u2 { + (u1, u2) = (u2, u1); + } + + let mut iter = circle_points(radius).skip(u1 as usize); + + // Intersection of the p1 line and the circle + let p1_start = unwrap!(iter.next()); + + // Intersection of the p1 line and the circle + let mut p2_start = p1_start; + + for p in iter.by_ref() { + if p.u > u2 { + break; + } + p2_start = p; + } + + // Flag if we draw section up to 45degs + let join_flag = iter.next().is_none(); + + // Process area between a p1 line and the circle + let mut p1_iter = line_points(p1_start.v, p1_start.u, 0); + let mut first = true; + let mut skip = 0; + + for c in circle_points(radius) + .skip(p1_start.u as usize) + .take((p2_start.u - p1_start.u) as usize) + { + let p2_coord = Point::new(c.u, -c.v); + + if c.first || first { + let p1 = unwrap!(p1_iter.next()); + let p1_coord = Point::new(p1_start.u - p1.v, -p1_start.v + p1.u); + first = false; + + fill(Some(p1_coord), p1.frac, p2_coord, c.frac); + } else { + fill(None, 0, p2_coord, c.frac); + } + + skip = if c.last { 0 } else { 1 }; + } + + // Process area between a p1 and p2 lines + let p2_iter = line_points(p2_start.v, p2_start.u, 0).skip(skip); + for (p1, p2) in p1_iter.zip(p2_iter) { + let p1_coord = Point::new(p1_start.u - p1.v, -p1_start.v + p1.u); + let p2_coord = Point::new(p2_start.u - p2.v, -p2_start.v + p2.u); + let p2_frac = if join_flag { 255 } else { 255 - p2.frac }; + fill(Some(p1_coord), p1.frac, p2_coord, p2_frac); + } +} + +impl Rect { + /// Normalizes the rectangle coordinates. + /// + /// Returns a new `Rect` with potentially swapped left/right, + /// top/bottom coordinates, ensuring that `x0`, `y0` represents + /// the top-left corner and `x1`, `y1` represents the bottom-right corner. + pub fn normalize(&self) -> Self { + Rect { + x0: core::cmp::min(self.x0, self.x1), + y0: core::cmp::min(self.y0, self.y1), + x1: core::cmp::max(self.x0, self.x1), + y1: core::cmp::max(self.y0, self.y1), + } + } +} + +impl Point { + fn onleft(self) -> Self { + Self { + x: self.x - 1, + ..self + } + } + + fn onright(self) -> Self { + Self { + x: self.x + 1, + ..self + } + } + + fn above(self) -> Self { + Self { + y: self.y - 1, + ..self + } + } + + fn under(self) -> Self { + Self { + y: self.y + 1, + ..self + } + } + + fn rot(self, octant: i16) -> Self { + let mut result = self; + + if (octant + 1) & 2 != 0 { + result = Point::new(-result.y, -result.x); + } + + if octant & 4 != 0 { + result = Point::new(-result.x, result.y); + } + + if (octant + 2) & 4 != 0 { + result = Point::new(result.x, -result.y); + } + + result + } +} diff --git a/core/embed/rust/src/ui/shape/canvas/mod.rs b/core/embed/rust/src/ui/shape/canvas/mod.rs new file mode 100644 index 000000000..9e4507d6e --- /dev/null +++ b/core/embed/rust/src/ui/shape/canvas/mod.rs @@ -0,0 +1,11 @@ +mod common; +mod mono8; +mod rgb565; +mod rgba8888; +mod viewport; + +pub use common::{BasicCanvas, Canvas}; +pub use mono8::Mono8Canvas; +pub use rgb565::Rgb565Canvas; +pub use rgba8888::Rgba8888Canvas; +pub use viewport::Viewport; diff --git a/core/embed/rust/src/ui/shape/canvas/mono8.rs b/core/embed/rust/src/ui/shape/canvas/mono8.rs new file mode 100644 index 000000000..405dc8f77 --- /dev/null +++ b/core/embed/rust/src/ui/shape/canvas/mono8.rs @@ -0,0 +1,105 @@ +use crate::ui::{ + display::Color, + geometry::{Offset, Point, Rect}, +}; + +use super::{ + super::{Bitmap, BitmapFormat, BitmapView}, + BasicCanvas, Canvas, Viewport, +}; + +#[cfg(feature = "ui_blurring")] +use super::super::DrawingCache; + +/// A struct representing 8-bit monochromatic canvas +pub struct Mono8Canvas<'a> { + bitmap: Bitmap<'a>, + viewport: Viewport, +} + +impl<'a> Mono8Canvas<'a> { + /// Creates a new canvas with the specified size and buffer. + /// + /// Optionally minimal height can be specified and then the height + /// of the new bitmap is adjusted to the buffer size. + /// + /// Returns None if the buffer is not big enough. + pub fn new( + size: Offset, + stride: Option, + min_height: Option, + buff: &'a mut [u8], + ) -> Option { + let bitmap = Bitmap::new_mut(BitmapFormat::MONO8, stride, size, min_height, buff)?; + let viewport = Viewport::from_size(bitmap.size()); + Some(Self { bitmap, viewport }) + } + + /// Returns the specified row as a mutable slice. + /// + /// Returns None if row is out of range. + pub fn row_mut(&mut self, row: i16) -> Option<&mut [u8]> { + self.bitmap.row_mut(row) + } +} + +impl<'a> BasicCanvas for Mono8Canvas<'a> { + fn viewport(&self) -> Viewport { + self.viewport + } + + fn set_viewport(&mut self, viewport: Viewport) { + self.viewport = viewport.absolute_clip(self.bounds()); + } + + fn size(&self) -> Offset { + self.bitmap.size() + } + + fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8) { + let r = r.translate(self.viewport.origin); + self.bitmap.mono8_fill(r, self.viewport.clip, color, alpha); + } + + fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) { + let r = r.translate(self.viewport.origin); + self.bitmap.mono8_copy(r, self.viewport.clip, &bitmap); + } +} + +impl<'a> Canvas for Mono8Canvas<'a> { + fn view(&self) -> BitmapView { + BitmapView::new(&self.bitmap) + } + + fn draw_pixel(&mut self, pt: Point, color: Color) { + let pt = pt + self.viewport.origin; + if self.viewport.clip.contains(pt) { + if let Some(row) = self.row_mut(pt.y) { + row[pt.x as usize] = color.luminance() as u8; + } + } + } + + fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8) { + let pt = pt + self.viewport.origin; + if self.viewport.clip.contains(pt) { + if let Some(row) = self.row_mut(pt.y) { + let pixel = &mut row[pt.x as usize]; + let fg_color = color.luminance() as u16; + let bg_color = *pixel as u16; + *pixel = ((fg_color * alpha as u16 + bg_color * (255 - alpha) as u16) / 255) as u8; + } + } + } + + fn blend_bitmap(&mut self, r: Rect, src: BitmapView) { + let r = r.translate(self.viewport.origin); + self.bitmap.mono8_blend(r, self.viewport.clip, &src); + } + + #[cfg(feature = "ui_blurring")] + fn blur_rect(&mut self, _r: Rect, _radius: usize, _cache: &DrawingCache) { + // Not implemented + } +} diff --git a/core/embed/rust/src/ui/shape/canvas/rgb565.rs b/core/embed/rust/src/ui/shape/canvas/rgb565.rs new file mode 100644 index 000000000..e197135de --- /dev/null +++ b/core/embed/rust/src/ui/shape/canvas/rgb565.rs @@ -0,0 +1,128 @@ +use crate::ui::{ + display::Color, + geometry::{Offset, Point, Rect}, +}; + +use super::{ + super::{Bitmap, BitmapFormat, BitmapView}, + BasicCanvas, Canvas, Viewport, +}; + +#[cfg(feature = "ui_blurring")] +use super::super::DrawingCache; + +/// A struct representing 16-bit (RGB565) color canvas +pub struct Rgb565Canvas<'a> { + bitmap: Bitmap<'a>, + viewport: Viewport, +} + +impl<'a> Rgb565Canvas<'a> { + /// Creates a new canvas with the specified size and buffer. + /// + /// Optionally minimal height can be specified and then the height + /// of the new bitmap is adjusted to the buffer size. + /// + /// Returns None if the buffer is not big enough. + pub fn new( + size: Offset, + stride: Option, + min_height: Option, + buff: &'a mut [u8], + ) -> Option { + let bitmap = Bitmap::new_mut(BitmapFormat::RGB565, stride, size, min_height, buff)?; + let viewport = Viewport::from_size(bitmap.size()); + Some(Self { bitmap, viewport }) + } + + /// Returns the specified row as a mutable slice. + /// + /// Returns None if row is out of range. + pub fn row_mut(&mut self, row: i16) -> Option<&mut [u16]> { + self.bitmap.row_mut(row) + } +} + +impl<'a> BasicCanvas for Rgb565Canvas<'a> { + fn size(&self) -> Offset { + self.bitmap.size() + } + + fn viewport(&self) -> Viewport { + self.viewport + } + + fn set_viewport(&mut self, viewport: Viewport) { + self.viewport = viewport.absolute_clip(self.bounds()); + } + + fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8) { + let r = r.translate(self.viewport.origin); + self.bitmap.rgb565_fill(r, self.viewport.clip, color, alpha); + } + + fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) { + let r = r.translate(self.viewport.origin); + self.bitmap.rgb565_copy(r, self.viewport.clip, &bitmap); + } +} + +impl<'a> Canvas for Rgb565Canvas<'a> { + fn view(&self) -> BitmapView { + BitmapView::new(&self.bitmap) + } + + fn draw_pixel(&mut self, pt: Point, color: Color) { + let pt = pt + self.viewport.origin; + if self.viewport.clip.contains(pt) { + if let Some(row) = self.row_mut(pt.y) { + row[pt.x as usize] = color.into(); + } + } + } + + fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8) { + let pt = pt + self.viewport.origin; + if self.viewport.clip.contains(pt) { + if let Some(row) = self.row_mut(pt.y) { + let pixel = &mut row[pt.x as usize]; + let bg_color: Color = (*pixel).into(); + *pixel = bg_color.blend(color, alpha).into(); + } + } + } + + fn blend_bitmap(&mut self, r: Rect, src: BitmapView) { + let r = r.translate(self.viewport.origin); + self.bitmap.rgb565_blend(r, self.viewport.clip, &src); + } + + #[cfg(feature = "ui_blurring")] + fn blur_rect(&mut self, r: Rect, radius: usize, cache: &DrawingCache) { + let clip = r.translate(self.viewport.origin).clamp(self.viewport.clip); + + let ofs = radius as i16; + + if clip.width() > 2 * ofs - 1 && clip.height() > 2 * ofs - 1 { + let mut blur_cache = cache.blur(); + let (blur, _) = unwrap!( + blur_cache.get(clip.size(), radius, None), + "Too small blur buffer" + ); + + loop { + if let Some(y) = blur.push_ready() { + let row = unwrap!(self.row_mut(y + clip.y0)); // can't panic + blur.push(&row[clip.x0 as usize..clip.x1 as usize]); + } + if let Some(y) = blur.pop_ready() { + let row = unwrap!(self.row_mut(y + clip.y0)); // can't panic + blur.pop(&mut row[clip.x0 as usize..clip.x1 as usize], None); + if y + 1 >= clip.height() { + break; + } + } + } + } + } +} diff --git a/core/embed/rust/src/ui/shape/canvas/rgba8888.rs b/core/embed/rust/src/ui/shape/canvas/rgba8888.rs new file mode 100644 index 000000000..3f0d7baf2 --- /dev/null +++ b/core/embed/rust/src/ui/shape/canvas/rgba8888.rs @@ -0,0 +1,120 @@ +use crate::ui::{ + display::Color, + geometry::{Offset, Point, Rect}, +}; + +use super::{ + super::{Bitmap, BitmapFormat, BitmapView}, + BasicCanvas, Canvas, Viewport, +}; + +#[cfg(feature = "ui_blurring")] +use super::super::DrawingCache; + +/// A struct representing 32-bit (RGBA8888) color canvas +pub struct Rgba8888Canvas<'a> { + bitmap: Bitmap<'a>, + viewport: Viewport, +} + +impl<'a> Rgba8888Canvas<'a> { + /// Creates a new canvas with the specified size and buffer. + /// + /// Optionally minimal height can be specified and then the height + /// of the new bitmap is adjusted to the buffer size. + /// + /// Returns None if the buffer is not big enough. + pub fn new( + size: Offset, + stride: Option, + min_height: Option, + buff: &'a mut [u8], + ) -> Option { + let bitmap = Bitmap::new_mut(BitmapFormat::RGBA8888, stride, size, min_height, buff)?; + let viewport = Viewport::from_size(bitmap.size()); + Some(Self { bitmap, viewport }) + } + + /// Returns the specified row as a mutable slice. + /// + /// Returns None if row is out of range. + pub fn row_mut(&mut self, row: i16) -> Option<&mut [u32]> { + self.bitmap.row_mut(row) + } +} + +impl<'a> BasicCanvas for Rgba8888Canvas<'a> { + fn size(&self) -> Offset { + self.bitmap.size() + } + + fn viewport(&self) -> Viewport { + self.viewport + } + + fn set_viewport(&mut self, viewport: Viewport) { + self.viewport = viewport.absolute_clip(self.bounds()); + } + + fn fill_rect(&mut self, r: Rect, color: Color, alpha: u8) { + let r = r.translate(self.viewport.origin); + self.bitmap + .rgba8888_fill(r, self.viewport.clip, color, alpha); + } + + fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) { + let r = r.translate(self.viewport.origin); + self.bitmap.rgba8888_copy(r, self.viewport.clip, &bitmap); + } +} + +impl<'a> Canvas for Rgba8888Canvas<'a> { + fn view(&self) -> BitmapView { + BitmapView::new(&self.bitmap) + } + + fn draw_pixel(&mut self, pt: Point, color: Color) { + let pt = pt + self.viewport.origin; + if self.viewport.clip.contains(pt) { + if let Some(row) = self.row_mut(pt.y) { + row[pt.x as usize] = color.to_u32(); + } + } + } + + fn blend_pixel(&mut self, pt: Point, color: Color, alpha: u8) { + let pt = pt + self.viewport.origin; + if self.viewport.clip.contains(pt) { + if let Some(row) = self.row_mut(pt.y) { + let bg = row[pt.x as usize]; + + let bg_r = ((bg & 0x00FF0000) >> 16) as u16; + let bg_g = ((bg & 0x0000FF00) >> 8) as u16; + let bg_b = (bg & 0x000000FF) as u16; + + let fg_r = color.r() as u16; + let fg_g = color.g() as u16; + let fg_b = color.b() as u16; + + let fg_mul = alpha as u16; + let bg_mul = (255 - alpha) as u16; + + let r = ((fg_r * fg_mul + bg_r * bg_mul) / 255) as u32; + let g = ((fg_g * fg_mul + bg_g * bg_mul) / 255) as u32; + let b = ((fg_b * fg_mul + bg_b * bg_mul) / 255) as u32; + + row[pt.x as usize] = (0xFF << 24) | (r << 16) | (g << 8) | b; + } + } + } + + fn blend_bitmap(&mut self, r: Rect, src: BitmapView) { + let r = r.translate(self.viewport.origin); + self.bitmap.rgba8888_blend(r, self.viewport.clip, &src); + } + + #[cfg(feature = "ui_blurring")] + fn blur_rect(&mut self, _r: Rect, _radius: usize, _cache: &DrawingCache) { + // TODO + } +} diff --git a/core/embed/rust/src/ui/shape/canvas/viewport.rs b/core/embed/rust/src/ui/shape/canvas/viewport.rs new file mode 100644 index 000000000..aca7f0b65 --- /dev/null +++ b/core/embed/rust/src/ui/shape/canvas/viewport.rs @@ -0,0 +1,103 @@ +use crate::ui::geometry::{Offset, Rect}; + +/// The Viewport concept is foundation for clipping and translating +/// during drawing on the general canvas. +/// +/// The Viewport structure comprises a rectangle representing the +/// clipping area and a drawing origin (or offset), which is applied +/// to all coordinates passed to the drawing functions. +/// +/// Two coordination systems exist - "absolute" and "relative." +/// +/// In the "absolute" coordinate system, (0, 0) is at the left-top of +/// a referenced canvas (device or bitmap). +/// +/// Relative coordinates are with respect to the viewport origin. +/// The relative coordinate (0, 0) is located at (viewport.origin.x, +/// viewport.origin.y). +/// +/// Conversion between "absolute" and "relative" coordinates is straightforward: +/// +/// pt_absolute = pt_relative.translate(viewport.origin) +/// +/// pt_relative = pt_absolute.translate(-viewport.origin) +/// +/// The Viewport's clipping area and origin are always in "absolute" +/// coordinates. Canvas objects utilize the viewport to translate "relative" +/// coordinates passed to drawing functions into "absolute" coordinates that +/// correspond to the target device or bitmap. + +#[derive(Copy, Clone)] +pub struct Viewport { + /// Clipping rectangle relative to the canvas top-left corner + pub clip: Rect, + /// Offset applied to all coordinates before clipping + pub origin: Offset, +} + +impl Viewport { + /// Creates a new viewport with specified clip rectangle and origin at + /// (0,0). + pub fn new(clip: Rect) -> Self { + Self { + clip, + origin: Offset::zero(), + } + } + + /// Creates a new viewport with specified size and origin at (0,0). + pub fn from_size(size: Offset) -> Self { + Self { + clip: Rect::from_size(size), + origin: Offset::zero(), + } + } + + /// Checks if the viewport intersects with the specified rectangle + /// given in relative coordinates. + pub fn contains(&self, r: Rect) -> bool { + !r.translate(self.origin).clamp(self.clip).is_empty() + } + + pub fn translate(self, offset: Offset) -> Self { + Self { + clip: self.clip.translate(offset), + origin: self.origin + offset, + } + } + + /// Creates a new viewport with the new origin given in + /// absolute coordinates. + pub fn with_origin(self, origin: Offset) -> Self { + Self { origin, ..self } + } + + /// Creates a clip of the viewport containing only the specified rectangle + /// given in absolute coordinates. The origin of the new viewport + /// remains unchanged. + pub fn absolute_clip(self, r: Rect) -> Self { + Self { + clip: r.clamp(self.clip), + ..self + } + } + + /// Creates a clip of the viewport containing only the specified rectangle + /// given in relative coordinates. The origin of the new viewport + /// remains unchanged. + pub fn relative_clip(self, r: Rect) -> Self { + Self { + clip: r.translate(self.origin).clamp(self.clip), + ..self + } + } + + /// Creates a clip of the viewport containing only the specified rectangle + /// given in relative coordinates. The origin of the new viewport + /// is set to the top-left corner of the rectangle. + pub fn relative_window(&self, r: Rect) -> Self { + let clip = r.translate(self.origin).clamp(self.clip); + let origin = self.origin + (clip.top_left() - self.clip.top_left()); + Self { clip, origin } + } +} diff --git a/core/embed/rust/src/ui/shape/circle.rs b/core/embed/rust/src/ui/shape/circle.rs new file mode 100644 index 000000000..e390a26ae --- /dev/null +++ b/core/embed/rust/src/ui/shape/circle.rs @@ -0,0 +1,139 @@ +use crate::ui::{ + display::Color, + geometry::{Point, Rect}, +}; + +use super::{Canvas, DrawingCache, Renderer, Shape, ShapeClone}; + +use without_alloc::alloc::LocalAllocLeakExt; + +/// A shape for rendering various types of circles or circle sectors. +pub struct Circle { + center: Point, + radius: i16, + fg_color: Option, + bg_color: Option, + thickness: i16, + start_angle: Option, + end_angle: Option, +} + +impl Circle { + pub fn new(center: Point, radius: i16) -> Self { + Self { + center, + radius, + fg_color: None, + bg_color: None, + thickness: 1, + start_angle: None, + end_angle: None, + } + } + + pub fn with_fg(self, fg_color: Color) -> Self { + Self { + fg_color: Some(fg_color), + ..self + } + } + + pub fn with_bg(self, bg_color: Color) -> Self { + Self { + bg_color: Some(bg_color), + ..self + } + } + + pub fn with_thickness(self, thickness: i16) -> Self { + Self { thickness, ..self } + } + + pub fn with_start_angle(self, from_angle: i16) -> Self { + Self { + start_angle: Some(from_angle), + ..self + } + } + + pub fn with_end_angle(self, to_angle: i16) -> Self { + Self { + end_angle: Some(to_angle), + ..self + } + } + + pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) { + renderer.render_shape(self); + } +} + +impl Shape<'_> for Circle { + fn bounds(&self, _cache: &DrawingCache) -> Rect { + let c = self.center; + let r = self.radius; + Rect::new( + Point::new(c.x - r, c.y - r), + Point::new(c.x + r + 1, c.y + r + 1), + ) + } + + fn cleanup(&mut self, _cache: &DrawingCache) {} + + fn draw(&mut self, canvas: &mut dyn Canvas, _cache: &DrawingCache) { + // NOTE: drawing of circles without a background and with a thickness > 1 + // is not supported. If we needed it, we would have to + // introduce RgbCanvas::draw_ring() function. + + // TODO: panic! in unsupported scenarious + let th = match self.fg_color { + Some(_) => self.thickness, + None => 0, + }; + + if self.start_angle.is_none() && self.end_angle.is_none() { + if th == 1 { + if let Some(color) = self.bg_color { + canvas.fill_circle(self.center, self.radius, color); + } + if let Some(color) = self.fg_color { + #[cfg(not(feature = "ui_antialiasing"))] + canvas.draw_circle(self.center, self.radius, color); + #[cfg(feature = "ui_antialiasing")] + canvas.fill_circle(self.center, self.radius, color); + } + } else { + if let Some(color) = self.fg_color { + if th > 0 { + canvas.fill_circle(self.center, self.radius, color); + } + } + if let Some(color) = self.bg_color { + canvas.fill_circle(self.center, self.radius - th, color); + } + } + } else { + let start = self.start_angle.unwrap_or(0); + let end = self.end_angle.unwrap_or(360); + + if let Some(color) = self.fg_color { + if th > 0 { + canvas.fill_sector(self.center, self.radius, start, end, color); + } + } + if let Some(color) = self.bg_color { + canvas.fill_sector(self.center, self.radius - th, start, end, color); + } + } + } +} + +impl<'s> ShapeClone<'s> for Circle { + fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>> + where + T: LocalAllocLeakExt<'alloc>, + { + let clone = bump.alloc_t::()?; + Some(clone.uninit.init(Circle { ..self })) + } +} diff --git a/core/embed/rust/src/ui/shape/display/fake_display.rs b/core/embed/rust/src/ui/shape/display/fake_display.rs new file mode 100644 index 000000000..329a838c9 --- /dev/null +++ b/core/embed/rust/src/ui/shape/display/fake_display.rs @@ -0,0 +1,12 @@ +use crate::ui::{ + display::Color, + geometry::Rect, + shape::{DirectRenderer, Mono8Canvas}, +}; + +pub fn render_on_display<'a, F>(_clip: Option, _bg_color: Option, _func: F) +where + F: FnOnce(&mut DirectRenderer<'_, 'a, Mono8Canvas<'a>>), +{ + panic!("Not implemented") +} diff --git a/core/embed/rust/src/ui/shape/display/fb_mono8.rs b/core/embed/rust/src/ui/shape/display/fb_mono8.rs new file mode 100644 index 000000000..d717de43c --- /dev/null +++ b/core/embed/rust/src/ui/shape/display/fb_mono8.rs @@ -0,0 +1,55 @@ +use crate::ui::{ + display::Color, + geometry::{Offset, Rect}, + shape::{BasicCanvas, DirectRenderer, DrawingCache, Mono8Canvas, Viewport}, +}; + +use crate::trezorhal::display; + +use static_alloc::Bump; + +/// 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. +/// +/// `clip` specifies a rectangle area that the user will draw to. +/// If no clip is specified, the entire display area is used. +/// +/// `bg_color` specifies a background color with which the clip is filled before +/// the drawing starts. If the background color is None, the background +/// is undefined, and the user has to fill it themselves. +pub fn render_on_display<'a, F>(clip: Option, bg_color: Option, func: F) +where + F: FnOnce(&mut DirectRenderer<'_, 'a, Mono8Canvas<'a>>), +{ + const BUMP_SIZE: usize = DrawingCache::get_bump_a_size() + DrawingCache::get_bump_b_size(); + + static mut BUMP: Bump<[u8; BUMP_SIZE]> = Bump::uninit(); + + let bump = unsafe { &mut *core::ptr::addr_of_mut!(BUMP) }; + { + let width = display::DISPLAY_RESX as i16; + let height = display::DISPLAY_RESY as i16; + + bump.reset(); + + let cache = DrawingCache::new(bump, bump); + + let (fb, fb_stride) = display::get_frame_buffer(); + + let mut canvas = unwrap!(Mono8Canvas::new( + Offset::new(width, height), + Some(fb_stride), + None, + fb + )); + + if let Some(clip) = clip { + canvas.set_viewport(Viewport::new(clip)); + } + + let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache); + + func(&mut target); + } +} diff --git a/core/embed/rust/src/ui/shape/display/fb_rgb565.rs b/core/embed/rust/src/ui/shape/display/fb_rgb565.rs new file mode 100644 index 000000000..e8734958a --- /dev/null +++ b/core/embed/rust/src/ui/shape/display/fb_rgb565.rs @@ -0,0 +1,62 @@ +use crate::ui::{ + display::Color, + geometry::{Offset, Rect}, + shape::{BasicCanvas, DirectRenderer, DrawingCache, Rgb565Canvas, Viewport}, +}; + +use crate::trezorhal::display; + +use static_alloc::Bump; + +/// 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. +/// +/// `clip` specifies a rectangle area that the user will draw to. +/// If no clip is specified, the entire display area is used. +/// +/// `bg_color` specifies a background color with which the clip is filled before +/// the drawing starts. If the background color is None, the background +/// is undefined, and the user has to fill it themselves. +pub fn render_on_display<'a, F>(clip: Option, bg_color: Option, func: F) +where + F: FnOnce(&mut DirectRenderer<'_, 'a, Rgb565Canvas<'a>>), +{ + const BUMP_A_SIZE: usize = DrawingCache::get_bump_a_size(); + const BUMP_B_SIZE: usize = DrawingCache::get_bump_b_size(); + + #[cfg_attr(not(target_os = "macos"), link_section = ".no_dma_buffers")] + static mut BUMP_A: Bump<[u8; BUMP_A_SIZE]> = Bump::uninit(); + + #[cfg_attr(not(target_os = "macos"), link_section = ".buf")] + static mut BUMP_B: Bump<[u8; BUMP_B_SIZE]> = Bump::uninit(); + + let bump_a = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_A) }; + let bump_b = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_B) }; + { + let width = display::DISPLAY_RESX as i16; + let height = display::DISPLAY_RESY as i16; + + bump_a.reset(); + bump_b.reset(); + + let cache = DrawingCache::new(bump_a, bump_b); + + let (fb, fb_stride) = display::get_frame_buffer(); + + let mut canvas = unwrap!(Rgb565Canvas::new( + Offset::new(width, height), + Some(fb_stride), + None, + fb + )); + + if let Some(clip) = clip { + canvas.set_viewport(Viewport::new(clip)); + } + + let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache); + + 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 new file mode 100644 index 000000000..ec6babfc8 --- /dev/null +++ b/core/embed/rust/src/ui/shape/display/fb_rgba8888.rs @@ -0,0 +1,62 @@ +use crate::ui::{ + display::Color, + geometry::{Offset, Rect}, + shape::{BasicCanvas, DirectRenderer, DrawingCache, Rgba8888Canvas, Viewport}, +}; + +use static_alloc::Bump; + +use crate::trezorhal::display; + +/// 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. +/// +/// `clip` specifies a rectangle area that the user will draw to. +/// If no clip is specified, the entire display area is used. +/// +/// `bg_color` specifies a background color with which the clip is filled before +/// the drawing starts. If the background color is None, the background +/// is undefined, and the user has to fill it themselves. +pub fn render_on_display<'a, F>(clip: Option, bg_color: Option, func: F) +where + F: FnOnce(&mut DirectRenderer<'_, 'a, Rgba8888Canvas<'a>>), +{ + const BUMP_A_SIZE: usize = DrawingCache::get_bump_a_size(); + const BUMP_B_SIZE: usize = DrawingCache::get_bump_b_size(); + + #[cfg_attr(not(target_os = "macos"), link_section = ".no_dma_buffers")] + static mut BUMP_A: Bump<[u8; BUMP_A_SIZE]> = Bump::uninit(); + + #[cfg_attr(not(target_os = "macos"), link_section = ".buf")] + static mut BUMP_B: Bump<[u8; BUMP_B_SIZE]> = Bump::uninit(); + + let bump_a = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_A) }; + let bump_b = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_B) }; + { + let width = display::DISPLAY_RESX as i16; + let height = display::DISPLAY_RESY as i16; + + bump_a.reset(); + bump_b.reset(); + + let cache = DrawingCache::new(bump_a, bump_b); + + let (fb, fb_stride) = display::get_frame_buffer(); + + let mut canvas = unwrap!(Rgba8888Canvas::new( + Offset::new(width, height), + Some(fb_stride), + None, + fb + )); + + if let Some(clip) = clip { + canvas.set_viewport(Viewport::new(clip)); + } + + let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache); + + func(&mut target); + } +} diff --git a/core/embed/rust/src/ui/shape/display/memory.md b/core/embed/rust/src/ui/shape/display/memory.md new file mode 100644 index 000000000..ec7093dac --- /dev/null +++ b/core/embed/rust/src/ui/shape/display/memory.md @@ -0,0 +1,58 @@ +## Memory usage comparison + +## Legacy solution + +**Memory with DMA access** + +``` +buffer_line_16bpp @.buf 1440 +buffer_line_4bpp @.buf 360 +buffer_text @.buf 4320 +------------------------------------------------- + 6120 +``` + +**Memory without DMA access** + +``` +buffer_jpeg @.no_dma 7680 +buffer_jpeg_work @.no_dma 10500 +buffer_blurring @.no_dma 14400 +buffer_blurring_totals @.no_dma 1440 +zlib context+window @.stack 2308 +------------------------------------------------- + 36328 +``` + +## New drawing library + +The memory usage is configurable, so the two options are considered.\ + +MIN variant is slower, but consumes less memory. OPT variant should +be sufficient for all purposes. + + +**Memory with DMA access** + +``` + MIN OPT +ProgressiveRenderer.slice @.buf 480 7680 +ProgressiveRenderer.scratch @.buf 480 2048 +--------------------------------------------------------- + 960 9728 +``` + +**Memory without DMA access** + +``` +ProgressiveRenderer.list @.stack 512 2048 +zlib decompression context @.no_dma 2308 6924 +jpeg decompressor @.no_dma 10500 10500 +partial jpeg image @.no_dma 7680 7680 +blurring window/totals @.no_dma 7920 7920 +------------------------------------------------------------------ + 28920 35072 +``` + + + diff --git a/core/embed/rust/src/ui/shape/display/mod.rs b/core/embed/rust/src/ui/shape/display/mod.rs new file mode 100644 index 000000000..e4be78b5b --- /dev/null +++ b/core/embed/rust/src/ui/shape/display/mod.rs @@ -0,0 +1,40 @@ +#[cfg(all(feature = "xframebuffer", feature = "display_mono"))] +pub mod fb_mono8; +#[cfg(all(feature = "xframebuffer", feature = "display_mono"))] +pub use fb_mono8::render_on_display; + +#[cfg(all(not(feature = "xframebuffer"), feature = "display_rgb565"))] +pub mod nofb_rgb565; +#[cfg(all(not(feature = "xframebuffer"), feature = "display_rgb565"))] +pub use nofb_rgb565::render_on_display; + +#[cfg(all( + feature = "xframebuffer", + feature = "display_rgb565", + not(feature = "display_rgba8888") +))] +pub mod fb_rgb565; +#[cfg(all( + feature = "xframebuffer", + feature = "display_rgb565", + not(feature = "display_rgba8888") +))] +pub use fb_rgb565::render_on_display; + +#[cfg(all( + feature = "xframebuffer", + feature = "display_rgba8888", + not(feature = "display_rgb565") +))] +pub mod fb_rgba8888; +#[cfg(all( + feature = "xframebuffer", + feature = "display_rgba8888", + not(feature = "display_rgb565") +))] +pub use fb_rgba8888::render_on_display; + +#[cfg(not(feature = "new_rendering"))] +pub mod fake_display; +#[cfg(not(feature = "new_rendering"))] +pub use fake_display::render_on_display; diff --git a/core/embed/rust/src/ui/shape/display/nofb_rgb565.rs b/core/embed/rust/src/ui/shape/display/nofb_rgb565.rs new file mode 100644 index 000000000..2fda844ed --- /dev/null +++ b/core/embed/rust/src/ui/shape/display/nofb_rgb565.rs @@ -0,0 +1,110 @@ +use crate::trezorhal::{bitblt::BitBlt, display}; + +use crate::ui::{ + display::Color, + geometry::{Offset, Rect}, +}; + +use super::super::{ + BasicCanvas, BitmapFormat, BitmapView, DrawingCache, ProgressiveRenderer, Viewport, +}; + +use static_alloc::Bump; + +// Maximum number of shapes on a single screen +const SHAPE_MAX_COUNT: usize = 45; +// Memory reserved for ProgressiveRenderes shape storage +const SHAPE_MEM_SIZE: usize = 5 * 1024; +// Memory not accessible by DMA +const BUMP_A_SIZE: usize = DrawingCache::get_bump_a_size() + SHAPE_MEM_SIZE; +// Memory accessible by DMA +const BUMP_B_SIZE: usize = DrawingCache::get_bump_b_size(); + +/// 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. +/// +/// `clip` specifies a rectangle area that the user will draw to. +/// If no clip is specified, the entire display area is used. +/// +/// `bg_color` specifies a background color with which the clip is filled before +/// the drawing starts. If the background color is None, the background +/// is undefined, and the user has to fill it themselves. +pub fn render_on_display<'a, F>(clip: Option, bg_color: Option, func: F) +where + F: FnOnce(&mut ProgressiveRenderer<'_, 'a, Bump<[u8; BUMP_A_SIZE]>, DisplayCanvas>), +{ + #[cfg_attr(not(target_os = "macos"), link_section = ".no_dma_buffers")] + static mut BUMP_A: Bump<[u8; BUMP_A_SIZE]> = Bump::uninit(); + + #[cfg_attr(not(target_os = "macos"), link_section = ".buf")] + static mut BUMP_B: Bump<[u8; BUMP_B_SIZE]> = Bump::uninit(); + + let bump_a = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_A) }; + let bump_b = unsafe { &mut *core::ptr::addr_of_mut!(BUMP_B) }; + { + bump_a.reset(); + bump_b.reset(); + + let cache = DrawingCache::new(bump_a, bump_b); + let mut canvas = DisplayCanvas::new(); + + if let Some(clip) = clip { + canvas.set_viewport(Viewport::new(clip)); + } + + let mut target = + ProgressiveRenderer::new(&mut canvas, bg_color, &cache, bump_a, SHAPE_MAX_COUNT); + + func(&mut target); + + target.render(16); + } +} + +/// A simple display canvas allowing just two bitblt operations: +/// 'fill_rect' and 'draw_bitmap` needed by `ProgressiveRenderer`. +pub struct DisplayCanvas { + size: Offset, + viewport: Viewport, +} + +impl DisplayCanvas { + pub fn new() -> Self { + let size = Offset::new(display::DISPLAY_RESX as i16, display::DISPLAY_RESY as i16); + let viewport = Viewport::from_size(size); + Self { size, viewport } + } +} + +impl BasicCanvas for DisplayCanvas { + fn viewport(&self) -> Viewport { + self.viewport + } + + fn set_viewport(&mut self, viewport: Viewport) { + self.viewport = viewport.absolute_clip(self.bounds()); + } + + fn size(&self) -> Offset { + self.size + } + + fn fill_rect(&mut self, r: Rect, color: Color, _alpha: u8) { + let r = r.translate(self.viewport.origin); + if let Some(bitblt) = BitBlt::new_fill(r, self.viewport.clip, color, 255) { + unsafe { bitblt.display_fill() }; + } + } + + fn draw_bitmap(&mut self, r: Rect, bitmap: BitmapView) { + let r = r.translate(self.viewport.origin); + if let Some(bitblt) = BitBlt::new_copy(r, self.viewport.clip, &bitmap) { + match bitmap.format() { + BitmapFormat::RGB565 => unsafe { bitblt.display_copy_rgb565() }, + _ => panic!("Unsupported DMA operation"), + } + bitmap.bitmap.mark_dma_pending(); + } + } +} diff --git a/core/embed/rust/src/ui/shape/drawlib-rust-objects.drawio.svg b/core/embed/rust/src/ui/shape/drawlib-rust-objects.drawio.svg new file mode 100644 index 000000000..972961e04 --- /dev/null +++ b/core/embed/rust/src/ui/shape/drawlib-rust-objects.drawio.svg @@ -0,0 +1,4 @@ + + + +

<<trait>>
Shape


+ bounds(&DrawingCache) -> Offset

+ draw(&mut dyn Canvas, &DrawingCache)
+ cleanup(&DrawingCache)


<<trait>>...

<<trait>>
ShapeClone


+ clone_at_pool(&LocalAllocExt) -> Option<&dyn Shape>

<<trait>>...

<<trait>>
Renderer


+ render_shape(Shape + ShapeClone)

+ viewport() -> Viewport


+ set_viewport(Viewport)
+ set_window(window: Rect) -> Viewport

+ set_clip(clip: Rect) -> Viewport


+ in_window(Rect, &dyn Fn(&mut Self))

+ in_clip(Rect, &dyn Fn(&mut Self))

+ with_origin(Rect, &dyn Fn(&mut Self))



<<trait>>...

<<struct>>
DirectRenderer


- canvas: &mut C: Canvas
- cache: DrawingCache


+ new(&mut C: Canvas, bg_color: Option<Color>, &DrawingCache)

<<struct>>...

<<struct>>
ProgressiveRenderer


- canvas: &mut C: BasicCanvas
- bump: &LocalAllocExt

- shapes: FixedVec<ShapeHolder>

- viewport: Viewport

- bg_color: Option<Color>

- cache: DrawingCache



+ new(&mug C: BasicCanvvas, bg_color: Option<Color>, &DrawingCavhas,  &LocalAllocExt, max_shapes: usize) -> Self
+ render(lines: usize)


<<struct>>...

<<struct>>
ShapeHolder


+ shape: &mut dyn Shape
+ viewport: Viewport


<<struct>>...
Relation
Relation
0..n
0..n
1
1

<<struct>>
DrawingCache



+ new() -> Self

+ zlib() -> RefMut<ZlibCache>

+ jpeg() -> RefMut<JpegCache>

+ blur() -> RefMut<BlurCache>

+ image_buff() -> Option<RefMut<ImageBuffRef>>

+ render_buff() -> Option<RefMut<RenderBuffRef>>


<<struct>>...
1
1
1
1

<<struct>>
Viewport


+ origin: Offset
+ clip: Rect


+ from_size(Offset) -> Self

+ contains(Rect) -> bool

+ translate(Offset) -> Viewport

+ with_origin(Offset) -> Viewport

+ absolute_clip(Rect) -> Viewport

+ relative_clip(Rect) -> Viewport

+ relative_window(Rect) -> Viewport


<<struct>>...
Extends
Extends
1
1
1
1

<<struct>>
ZlibCache


- slots: [ZlibCacheSlot]


+ new(&bump, slot_count: u32) -> Option<Self>

+ uncompress(zdata: &[u8], offset: usize, dest_buf: &mut [u8]) -> Result<bool, ())

<<struct>>...

<<struct>>
ZlibCacheSlot


- zdata_tag: *const u8

- offset: usize

- dc: Option<UzlibContext>


+ empty() -> Self

+ initialize(zdata: &[u8])

+ reset()

+ uncompress(dest_buf: &mut [u8]) 

+ skip(nbytes: usize)

+ is_for(zdata: & [u8]) -> bool


<<struct>>...

<<struct>>
JpegCache


- slots: [JpegCacheSlot]


+ new(&bump, slot_count: u32) -> Option<Self>

<<struct>>...

<<struct>>
JpegCacheSlot


- jpeg: & [u8]
- input: tjpgd::BufferInput

- decoder: tjpgd::JDEC


+ new(jpeg: &[u8]) -> Result<Self, Error>

+ jpeg_size() -> Offset

+ is_for(jpeg: &[u8]) -> bool


<<struct>>...
1
1
1
1
Relation
Relation
0..n
0..n
1
1
Relation
Relation
0..1
0..1
1
1

<<struct>>
BlurCache


- algo: Option<BlurAlgorithm>
- buff: &UnsafeCell<BlurBuff>

- tag: u32


+ new(&bump)

+ get(size: Offset, radius: usize, tag: Option<u32>) -> Result<&mut BlurAlgorithm<'a>, u32), ()>

<<struct>>...

<<trait>>
BasicCanvas


+ size() -> Offset

+ bounds() -> Rect

+ width() -> i16

+ height() -> i16


+ viewport() -> Viewport

+ set_viewport(Viewport)

+ set_window(Rect) -> Viewport

+ set_clip(Rect) -> Viewport


+ fill_rect(Rect, Color)

+ fill_background(Color)

+ draw_bitmap(Rect, BitmapRef)







<<trait>>...

<<trait>>
Canvas


+ view() -> BitmapView


+ draw_pixel(Point, Color)

+ blend_pixel(Point, Color, u8)


+ blend_bitmap(Rect, BitmapView)

+ blur_rect(Rect, radius)


+ draw_round_rect(Rect, radius, Color)

+ fill_round_rect(Rect, radius, color)


+ draw_circle(Point, radius, Color)

+ fill_circle(Point, radius, Color)


+ fill_sector(Point, i16, i16, i16, Color)

<<trait>>...
Extends
Extends

<<struct>>
Rgb565Canvas


- bitmap: Bitmap

- viewport: Viewport


+ new(size: Offset, Option<i16>, &mut [u8]) -> Option<Self>

+ row_mut(row: i16) -> Option<&mut [u16])


<<struct>>...

<<struct>>
Rgba8888Canvas


- bitmap: Bitmap

- viewport: Viewport


+ new(size: Offset, Option<i16>, &mut [u8) -> Option<Self>

+ row_mut(row: i16) -> Option<&mut [u32])


<<struct>>...
Use
Use

<<struct>>
Bitmap


- ptr: *mut u8

- stride: usize

- size: Offset

- format: BitmapFormat

- mutable: bool

- dma_pending: bool


+ new(BitmapFormat, Option<usize>, Offset,  Option<i16>, & [u8]) -> Self

+ new_mut(BitmapFormat, Option<usize>, Offset,  Option<i16>, &mut  [u8]) -> Self


+ size() -> Offset

+ width() -> i16

+ height() -> i16

+ stride() -> usize

+ view() -> BitmapView


+ row<T>(i16) -> Option<&[T}>

+ row_mut<T>(i16) -> Option<&mut [T}>

+ rows_mut<T>(i16, i16) -> Option<&mut [T}>


+ rgb565_fill(Rect, Rect, Color)

+ rgb565_copy(Rect, Rect, &BitmapView)

+ rgb565_blend(Rect, Rect, &BitmapView)


+ rgba8888_fill(Rect, Rect, Color)

+ rgba8888_copy(Rect, Rect, &BitmapView)

+ rgba8888_blend(Rect, Rect, &BitmapView)


+ mono8_fill(Rect, Rect, Color)

+ mono8_copy(Rect, Rect, &BitmapView)

+ mono8_blend(Rect, Rect, &BitmapView)




<<struct>>...

<<struct>>
BitmapView


- bitmap: &Bitmap

- fg_color: Color
- bg_color: Color

- offset: Offset



+ new(&Bitmap) -> Self

+ with_fg(Color) -> Self

+ with_bg(Color) -> Self

+ with_offset(Offset) -> Self


+ size() -> Offset

+ width() -> i16

+ height() -> i16

+ format() -> BitmapFormat

+ row<T>(i16) -> Option<&[T]>

<<struct>>...

<<struct>>
BitBlt = ffi::gl_bitblt_t


+ new_fill(Rect, Rect, color) -> Option<Self>

+ new_copy(Rect, Rect, &BitmapView)

+ with_dst(&Bitmap)


+ rgb565_fill()

+ rgb565_copy_mono4()

+ rgb565_copy_rgb565()

+ rgb565_blend_mono4()


+ rgba8888_fill()

+ rgba8888_copy_mono4()

+ rgba8888_copy_rgb565()

+ rgba8888_copy_rgba8888()

+ rgba8888_blend_mono4()


+ mono8_fill()

+ mono8_copy_mono1p()

+ mono8_copy_mono4()

+ mono8_blend_mono1p()

+ mono8_blend_mono4()




<<struct>>...
Shape objects
Shape objects

<<struct>>
shape::Bar


- area: Rect

- fg_color: Option<Color>
- bg_color: Option<Color>

- thickness: i16

- radius: i16


+ new(Rect) -> Self

+ with_fg(Color) -> Self

+ with_bg(Color) -> Self

+ with_radius(i16) -> Self

+ with_thickness(i16) -> Self


+ render(&mut impl Renderer)

<<struct>>...

<<struct>>
shape::Circle


- center:  Point

- fg_color: Option<Color>
- bg_color: Option<Color>

- thickness: i16

- radius: i16

- start_angle: Option<i16>

- end_angle: Option<i16>


+ new(center: Point,radius: i16) -> Self

+ with_fg(Color) -> Self

+ with_bg(Color) -> Self

+ with_thickness(i16) -> Self

+ with_start_angle(i16) -> Self

+ with_end_angle(i16) -> Self



+ render(&mut impl Renderer)

<<struct>>...

<<struct>>
shape::Text


- area: Rect

- text: &str

- color: Color

- font: Font


+ new(area: Rect, &str) -> Self

+ with_fg(Color) -> Self

+ with_font(Font) -> Self





+ render(&mut impl Renderer)

<<struct>>...

<<struct>>
shape::ToifImage


- pos: Point

- toif: Toif

- fg_color: Color

- bg_color: Option<Color>


+ new(area: Rect, Toif) -> Self

+ with_fg(Color) -> Self

+ with_bg(Color) -> Self





+ render(&mut impl Renderer)

<<struct>>...

<<struct>>
shape::JpegImage


- pos: Point

- jpeg: &[u8]

- scale: u8

- blur_radius: usize


+ new(area: Rect, &[u8]) -> Self

+ with_scale(u8) -> Self

+ with_blur(usize) -> Self





+ render(&mut impl Renderer)

<<struct>>...

<<struct>>
shape::QrCode


- area: Rect

- qr_modules: [u8; ..]

- qr_size: i16

- fg_color: Color

- bg_color: Option<Color>


+ new(pos: Point, &str) -> Self






+ render(&mut impl Renderer)

<<struct>>...

<<struct>>
shape::Blurring


- area: Rect

- radius: usize


+ new(area: Rect, usize) -> Self









+ render(&mut impl Renderer)

<<struct>>...
1
1

<<struct>>
Mono8Canvas


- bitmap: Bitmap

- viewport: Viewport


+ new(size: Offset, Option<i16>, &mut [u8) -> Option<Self>

+ row_mut(row: i16) -> Option<&mut [u32])


<<struct>>...
\ No newline at end of file diff --git a/core/embed/rust/src/ui/shape/drawlib-toplevel-arch.drawio.svg b/core/embed/rust/src/ui/shape/drawlib-toplevel-arch.drawio.svg new file mode 100644 index 000000000..05e7faaee --- /dev/null +++ b/core/embed/rust/src/ui/shape/drawlib-toplevel-arch.drawio.svg @@ -0,0 +1,4 @@ + + + +
SHAPE
SHAPE
RENDERER
RENDERER
CANVAS
CANVAS
BitBlt (bitblt.rs)

2D bit-block transfer routines 
(Rust API)
BitBlt (bitblt.rs)...
gl_bitblt

2D bit-block transfer routines
(C API)
gl_bitblt...
dma2d_bitblt

Hardware accelerated bitblt operations
dma2d_bitblt...
display_driver

Display initialization, framebuffer implementation, backlight, orientation, synchronization

display_driver...
Bit-blt API
for simple access or non-fb drivers
Bit-blt API...
gl_draw

Simple drawing routines
(C API)
gl_draw...
Canvas
Canvas
BasicCanvas
BasicCanvas
LIB
(C)
LIB...
HAL
(C)
HAL...
TrezorHal (RUST)
TrezorH...
High-level abstraction for drawing implements simple objects (such as Bar, Circle, ImageToif, ImageJpeg) that can be renderered on the display through the lower layer. 
High-level abstraction for drawing implements simple objects (such as Bar, C...
Provides two distinguished mechanisms for rendering shape on the display: DirectRenderer for drawing using display drivers exposing framebuffer, and ProgressiveRenderer for drawing on displays without framebuffer in MCU RAM providing just simple bit-blt operations.
Provides two distinguished mechanisms for rendering shape on the display: Dire...
Several implementations of the Canvas trait (Rgb565Canvas, RGBA8888Canvas, Mono8Canvas) for different types of framebuffers/displays.
Several implementations of the Canvas trait (Rgb565Canvas, RGBA8888Canvas...
direct drawing without rust lib
direct drawing without rust...
UI (RUST)
UI (RUS...
\ No newline at end of file diff --git a/core/embed/rust/src/ui/shape/jpeg.rs b/core/embed/rust/src/ui/shape/jpeg.rs new file mode 100644 index 000000000..58aa1ee4c --- /dev/null +++ b/core/embed/rust/src/ui/shape/jpeg.rs @@ -0,0 +1,196 @@ +use crate::ui::geometry::{Alignment2D, Offset, Point, Rect}; + +use super::{Bitmap, BitmapFormat, BitmapView, Canvas, DrawingCache, Renderer, Shape, ShapeClone}; + +use without_alloc::alloc::LocalAllocLeakExt; + +/// A shape for rendering compressed JPEG images. +pub struct JpegImage<'a> { + /// Image position + pos: Point, + // Image position alignment + align: Alignment2D, + /// JPEG data + jpeg: &'a [u8], + /// Scale factor (default 0) + scale: u8, + /// Blurring radius or 0 if no blurring required (default 0) + blur_radius: usize, + /// Dimming of blurred image in range of 0..255 (default 255) + dim: u8, + /// Set if blurring is pending + /// (used only during image drawing). + blur_tag: Option, +} + +impl<'a> JpegImage<'a> { + pub fn new(pos: Point, jpeg: &'a [u8]) -> Self { + JpegImage { + pos, + align: Alignment2D::TOP_LEFT, + scale: 0, + dim: 255, + blur_radius: 0, + jpeg, + blur_tag: None, + } + } + + pub fn with_align(self, align: Alignment2D) -> Self { + Self { align, ..self } + } + + pub fn with_scale(self, scale: u8) -> Self { + assert!(scale <= 3); + Self { scale, ..self } + } + + pub fn with_blur(self, blur_radius: usize) -> Self { + Self { + blur_radius, + ..self + } + } + + pub fn with_dim(self, dim: u8) -> Self { + Self { dim, ..self } + } + + pub fn render(self, renderer: &mut impl Renderer<'a>) { + renderer.render_shape(self); + } +} + +impl<'a> Shape<'a> for JpegImage<'a> { + fn bounds(&self, cache: &DrawingCache<'a>) -> Rect { + let size = unwrap!(cache.jpeg().get_size(self.jpeg, self.scale), "Invalid JPEG"); + Rect::from_top_left_and_size(size.snap(self.pos, self.align), size) + } + + fn cleanup(&mut self, _cache: &DrawingCache<'a>) { + self.blur_tag = None; + } + + /* + // Faster implementation suitable for DirectRenderer without blurring support + // (but is terribly slow on ProgressiveRenderer if slices are not aligned + // to JPEG MCUs ) + fn draw(&mut self, canvas: &mut dyn RgbCanvasEx, cache: &DrawingCache<'a>) { + let bounds = self.bounds(cache); + let clip = canvas.viewport().relative_clip(bounds).clip; + + // translate clip to JPEG relative coordinates + let clip = clip.translate(-canvas.viewport().origin); + let clip = clip.translate((-bounds.top_left()).into()); + + unwrap!( + cache.jpeg().decompress_mcu( + self.jpeg, + self.scale, + clip.top_left(), + &mut |mcu_r, mcu_bitmap| { + // Draw single MCU + canvas.draw_bitmap(mcu_r.translate(bounds.top_left().into()), mcu_bitmap); + // Return true if we are not done yet + mcu_r.x1 < clip.x1 || mcu_r.y1 < clip.y1 + } + ), + "Invalid JPEG" + ); + }*/ + + // This is a little bit slower implementation suitable for ProgressiveRenderer + fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) { + let bounds = self.bounds(cache); + let clip = canvas.viewport().relative_clip(bounds).clip; + + // Translate clip to JPEG relative coordinates + let clip = clip.translate(-canvas.viewport().origin); + let clip = clip.translate((-bounds.top_left()).into()); + + if self.blur_radius == 0 { + // Draw JPEG without blurring + + // Routine for drawing single JPEG MCU + let draw_mcu = &mut |row_r: Rect, row_bitmap: BitmapView| { + // Draw a row of decoded MCUs + canvas.draw_bitmap(row_r.translate(bounds.top_left().into()), row_bitmap); + // Return true if we are not done yet + row_r.y1 < clip.y1 + }; + + unwrap!( + cache + .jpeg() + .decompress_row(self.jpeg, self.scale, clip.y0, draw_mcu), + "Invalid JPEG" + ); + } else { + // Draw JPEG with blurring effect + let jpeg_size = self.bounds(cache).size(); + + // Get a single line working bitmap + let buff = &mut unwrap!(cache.image_buff(), "No image buffer"); + let mut slice = unwrap!( + Bitmap::new( + BitmapFormat::RGB565, + None, + Offset::new(jpeg_size.x, 1), + None, + &buff[..] + ), + "Too small buffer" + ); + + // Get the blurring algorithm instance + let mut blur_cache = cache.blur(); + let (blur, blur_tag) = + unwrap!(blur_cache.get(jpeg_size, self.blur_radius, self.blur_tag)); + self.blur_tag = Some(blur_tag); + + if let Some(y) = blur.push_ready() { + // A function for drawing a row of JPEG MCUs + let draw_row = &mut |row_r: Rect, jpeg_slice: BitmapView| { + loop { + if let Some(y) = blur.push_ready() { + if y < row_r.y1 { + // should never fail + blur.push(unwrap!(jpeg_slice.row(y - row_r.y0))); + } else { + return true; // need more data + } + } + + if let Some(y) = blur.pop_ready() { + blur.pop(unwrap!(slice.row_mut(0)), Some(self.dim)); // should never fail + let dst_r = Rect::from_top_left_and_size(bounds.top_left(), jpeg_size) + .translate(Offset::new(0, y)); + canvas.draw_bitmap(dst_r, slice.view()); + + if y + 1 >= clip.y1 { + return false; // we are done + } + } + } + }; + + unwrap!( + cache + .jpeg() + .decompress_row(self.jpeg, self.scale, y, draw_row), + "Invalid JPEG" + ); + } + } + } +} + +impl<'a> ShapeClone<'a> for JpegImage<'a> { + fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'a>> + where + T: LocalAllocLeakExt<'alloc>, + { + let clone = bump.alloc_t::()?; + Some(clone.uninit.init(JpegImage { ..self })) + } +} diff --git a/core/embed/rust/src/ui/shape/mod.rs b/core/embed/rust/src/ui/shape/mod.rs new file mode 100644 index 000000000..9fc9d7fe2 --- /dev/null +++ b/core/embed/rust/src/ui/shape/mod.rs @@ -0,0 +1,33 @@ +mod algo; +mod bar; +mod base; +mod bitmap; +#[cfg(feature = "ui_blurring")] +mod blur; +mod cache; +mod canvas; +mod circle; +mod display; +#[cfg(feature = "ui_jpeg_decoder")] +mod jpeg; +mod qrcode; +mod render; +mod text; +mod toif; + +pub use algo::PI4; +pub use bar::Bar; +pub use base::{Shape, ShapeClone}; +pub use bitmap::{Bitmap, BitmapFormat, BitmapView}; +#[cfg(feature = "ui_blurring")] +pub use blur::Blurring; +pub use cache::drawing_cache::DrawingCache; +pub use canvas::{BasicCanvas, Canvas, Mono8Canvas, Rgb565Canvas, Rgba8888Canvas, Viewport}; +pub use circle::Circle; +pub use display::render_on_display; +#[cfg(feature = "ui_jpeg_decoder")] +pub use jpeg::JpegImage; +pub use qrcode::QrImage; +pub use render::{DirectRenderer, ProgressiveRenderer, Renderer}; +pub use text::Text; +pub use toif::ToifImage; diff --git a/core/embed/rust/src/ui/shape/qrcode.rs b/core/embed/rust/src/ui/shape/qrcode.rs new file mode 100644 index 000000000..c9f956eb2 --- /dev/null +++ b/core/embed/rust/src/ui/shape/qrcode.rs @@ -0,0 +1,169 @@ +use crate::ui::{ + display::Color, + geometry::{Offset, Rect}, +}; + +use qrcodegen::QrCode; + +use super::{ + algo::line_points, Bitmap, BitmapFormat, Canvas, DrawingCache, Renderer, Shape, ShapeClone, +}; + +use without_alloc::alloc::LocalAllocLeakExt; + +const MAX_QRCODE_BYTES: usize = 400; + +/// A shape for `QrCode` rendering. +pub struct QrImage { + /// Destination rectangle + area: Rect, + /// QR code bitmap + qr_modules: [u8; MAX_QRCODE_BYTES], + /// Size of QR code bitmap in bytes + qr_size: i16, + /// Foreground color + fg_color: Color, + /// Optional background color + bg_color: Option, +} + +impl QrImage { + pub fn new(area: Rect, qrcode: &QrCode) -> Self { + if area.width() < qrcode.size() as i16 || area.height() < qrcode.size() as i16 { + panic!("Too small area"); + } + + let mut result = QrImage { + area, + qr_size: qrcode.size() as i16, + qr_modules: [0u8; MAX_QRCODE_BYTES], + fg_color: Color::white(), + bg_color: None, + }; + + // Copy content of QR code to the qrmodules buffer + for y in 0..result.qr_size { + for x in 0..result.qr_size { + result.set_module(x, y, qrcode.get_module(x as i32, y as i32)); + } + } + + result + } + + fn set_module(&mut self, x: i16, y: i16, value: bool) { + // Every row starts at byte aligned address + let row_offset = (y * (self.qr_size + 7) / 8) as usize; + let row = &mut self.qr_modules[row_offset..]; + let col_offset = (x / 8) as usize; + let col_bit = 1 << (x & 0x7); + if value { + row[col_offset] |= col_bit; + } else { + row[col_offset] &= col_bit ^ 0xFF; + } + } + + fn get_module(&self, x: i16, y: i16) -> bool { + // Every row starts at byte aligned address + let row_offset = (y * (self.qr_size + 7) / 8) as usize; + let row = &self.qr_modules[row_offset..]; + let col_offset = (x / 8) as usize; + let col_bit = 1 << (x & 0x7); + (row[col_offset] & col_bit) != 0 + } + + pub fn with_fg(self, fg_color: Color) -> Self { + Self { fg_color, ..self } + } + + pub fn with_bg(self, bg_color: Color) -> Self { + Self { + bg_color: Some(bg_color), + ..self + } + } + + pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) { + renderer.render_shape(self); + } + + fn draw_row(&self, slice_row: &mut [u8], qr_y: i16) { + slice_row.iter_mut().for_each(|b| *b = 0); + + let mut qr_module = false; + + for p in line_points(self.area.x1 - self.area.x0, self.qr_size, 0) { + if p.first { + qr_module = self.get_module(p.v, qr_y); + } + if !qr_module { + if p.u & 0x01 == 0 { + slice_row[(p.u / 2) as usize] |= 0x0F; + } else { + slice_row[(p.u / 2) as usize] |= 0xF0; + } + } + } + } +} + +impl Shape<'_> for QrImage { + fn bounds(&self, _cache: &DrawingCache) -> Rect { + self.area + } + + fn cleanup(&mut self, _cache: &DrawingCache) {} + + fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache) { + let buff = &mut unwrap!(cache.image_buff(), "No TOIF buffer"); + + let mut slice = unwrap!( + Bitmap::new_mut( + BitmapFormat::MONO4, + None, + Offset::new(self.area.width(), 1), + Some(1), + &mut buff[..] + ), + "Too small buffer" + ); + + let clip = canvas.viewport().relative_clip(self.bounds(cache)).clip; + + // translate clip to the relative coordinates + let clip = clip.translate(-canvas.viewport().origin); + let clip = clip.translate((-self.area.top_left()).into()); + + for p in line_points(self.area.y1 - self.area.y0, self.qr_size, clip.y0) + .take(clip.height() as usize) + { + if p.first { + self.draw_row(slice.row_mut(0).unwrap(), p.v); + } + + let r = Rect { + y0: self.area.y0 + p.u, + y1: self.area.y0 + p.u + 1, + ..self.area + }; + + let slice_view = slice.view().with_fg(self.fg_color); + + match self.bg_color { + Some(bg_color) => canvas.draw_bitmap(r, slice_view.with_bg(bg_color)), + None => canvas.blend_bitmap(r, slice_view), + } + } + } +} + +impl<'s> ShapeClone<'s> for QrImage { + fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>> + where + T: LocalAllocLeakExt<'alloc>, + { + let clone = bump.alloc_t::()?; + Some(clone.uninit.init(QrImage { ..self })) + } +} diff --git a/core/embed/rust/src/ui/shape/render.rs b/core/embed/rust/src/ui/shape/render.rs new file mode 100644 index 000000000..dd5191dd5 --- /dev/null +++ b/core/embed/rust/src/ui/shape/render.rs @@ -0,0 +1,250 @@ +use crate::ui::{ + display::Color, + geometry::{Offset, Point, Rect}, +}; + +use super::{BasicCanvas, Canvas, DrawingCache, Rgb565Canvas, Shape, ShapeClone, Viewport}; + +use without_alloc::{alloc::LocalAllocLeakExt, FixedVec}; + +// ========================================================================== +// trait Renderer +// ========================================================================== + +/// All renders must implement Renderer trait +/// Renderers can immediately use the draw() method of the passed shape or +/// may store it (using the boxed() method) and draw it later +pub trait Renderer<'a> { + fn viewport(&self) -> Viewport; + + fn set_viewport(&mut self, viewport: Viewport); + + fn set_window(&mut self, window: Rect) -> Viewport { + let viewport = self.viewport(); + self.set_viewport(viewport.relative_window(window)); + viewport + } + + fn set_clip(&mut self, clip: Rect) -> Viewport { + let viewport = self.viewport(); + self.set_viewport(viewport.relative_clip(clip)); + viewport + } + + fn render_shape(&mut self, shape: S) + where + S: Shape<'a> + ShapeClone<'a>; + + fn in_window(&mut self, r: Rect, inner: &dyn Fn(&mut Self)) { + let original = self.set_window(r); + inner(self); + self.set_viewport(original); + } + + fn in_clip(&mut self, r: Rect, inner: &dyn Fn(&mut Self)) { + let original = self.set_clip(r); + inner(self); + self.set_viewport(original); + } + + fn with_origin(&mut self, origin: Offset, inner: &dyn Fn(&mut Self)) { + let original = self.viewport(); + self.set_viewport(self.viewport().with_origin(origin)); + inner(self); + self.set_viewport(original); + } +} + +// ========================================================================== +// struct DirectRenderer +// ========================================================================== + +/// A simple implementation of a Renderer that draws directly onto the CanvasEx +pub struct DirectRenderer<'a, 'alloc, C> +where + C: Canvas, +{ + /// Target canvas + canvas: &'a mut C, + /// Drawing cache (decompression context, scratch-pad memory) + cache: &'a DrawingCache<'alloc>, +} + +impl<'a, 'alloc, C> DirectRenderer<'a, 'alloc, C> +where + C: Canvas, +{ + /// Creates a new DirectRenderer instance with the given canvas + pub fn new( + canvas: &'a mut C, + bg_color: Option, + cache: &'a DrawingCache<'alloc>, + ) -> Self { + if let Some(color) = bg_color { + canvas.fill_background(color); + } + + // TODO: consider storing original canvas.viewport + // and restoring it by drop() function + + Self { canvas, cache } + } +} + +impl<'a, 'alloc, C> Renderer<'alloc> for DirectRenderer<'a, 'alloc, C> +where + C: Canvas, +{ + fn viewport(&self) -> Viewport { + self.canvas.viewport() + } + + fn set_viewport(&mut self, viewport: Viewport) { + self.canvas.set_viewport(viewport); + } + + fn render_shape(&mut self, mut shape: S) + where + S: Shape<'alloc> + ShapeClone<'alloc>, + { + if self.canvas.viewport().contains(shape.bounds(self.cache)) { + shape.draw(self.canvas, self.cache); + shape.cleanup(self.cache); + } + } +} + +// ========================================================================== +// struct ProgressiveRenderer +// ========================================================================== + +struct ShapeHolder<'a> { + shape: &'a mut dyn Shape<'a>, + viewport: Viewport, +} + +/// A more advanced Renderer implementation that supports deferred rendering. +pub struct ProgressiveRenderer<'a, 'alloc, T, C> +where + T: LocalAllocLeakExt<'alloc>, + C: BasicCanvas, +{ + /// Target canvas + canvas: &'a mut C, + /// Bump for cloning shapes + bump: &'alloc T, + /// List of rendered shapes + shapes: FixedVec<'alloc, ShapeHolder<'alloc>>, + /// Current viewport + viewport: Viewport, + // Default background color + bg_color: Option, + /// Drawing cache (decompression context, scratch-pad memory) + cache: &'a DrawingCache<'alloc>, +} + +impl<'a, 'alloc, T, C> ProgressiveRenderer<'a, 'alloc, T, C> +where + T: LocalAllocLeakExt<'alloc>, + C: BasicCanvas, +{ + /// Creates a new ProgressiveRenderer instance + pub fn new( + canvas: &'a mut C, + bg_color: Option, + cache: &'a DrawingCache<'alloc>, + bump: &'alloc T, + max_shapes: usize, + ) -> Self { + let viewport = canvas.viewport(); + Self { + canvas, + bump, + shapes: unwrap!(bump.fixed_vec(max_shapes), "No shape memory"), + viewport, + bg_color, + cache, + } + } + + /// Renders stored shapes onto the specified canvas + pub fn render(&mut self, lines: usize) { + let canvas_clip = self.canvas.viewport().clip; + let canvas_origin = self.canvas.viewport().origin; + + let buff = &mut unwrap!(self.cache.render_buff(), "No render buffer"); + + let mut slice = unwrap!( + Rgb565Canvas::new( + Offset::new(canvas_clip.width(), lines as i16), + None, + Some(1), + &mut buff[..], + ), + "No render memory" + ); + + for y in (canvas_clip.y0..canvas_clip.y1).step_by(lines) { + // Calculate the coordinates of the slice we will draw into + let slice_r = Rect::new( + // slice_r is in absolute coordinates + Point::new(canvas_clip.x0, y), + Point::new(canvas_clip.x1, y + lines as i16), + ) + .translate(-canvas_origin); + + // Clear the slice background + if let Some(color) = self.bg_color { + slice.set_viewport(Viewport::from_size(slice_r.size())); + slice.fill_background(color); + } + + // Draw all shapes that overlaps the slice + for holder in self.shapes.iter_mut() { + let shape_viewport = holder.viewport.absolute_clip(slice_r); + let shape_bounds = holder.shape.bounds(self.cache); + + // Is the shape overlapping the current slice? + if shape_viewport.contains(shape_bounds) { + slice.set_viewport(shape_viewport.translate((-slice_r.top_left()).into())); + holder.shape.draw(&mut slice, self.cache); + + if shape_bounds.y1 + shape_viewport.origin.y <= shape_viewport.clip.y1 { + // The shape will never be drawn again + holder.shape.cleanup(self.cache); + } + } + } + self.canvas.draw_bitmap(slice_r, slice.view()); + } + } +} + +impl<'a, 'alloc, T, C> Renderer<'alloc> for ProgressiveRenderer<'a, 'alloc, T, C> +where + T: LocalAllocLeakExt<'alloc>, + C: BasicCanvas, +{ + fn viewport(&self) -> Viewport { + self.viewport + } + + fn set_viewport(&mut self, viewport: Viewport) { + self.viewport = viewport.absolute_clip(self.canvas.bounds()); + } + + fn render_shape(&mut self, shape: S) + where + S: Shape<'alloc> + ShapeClone<'alloc>, + { + // Is the shape visible? + if self.viewport.contains(shape.bounds(self.cache)) { + // Clone the shape & push it to the list + let holder = ShapeHolder { + shape: unwrap!(shape.clone_at_bump(self.bump), "No shape memory"), + viewport: self.viewport, + }; + unwrap!(self.shapes.push(holder), "Shape list full"); + } + } +} diff --git a/core/embed/rust/src/ui/shape/text.rs b/core/embed/rust/src/ui/shape/text.rs new file mode 100644 index 000000000..cc83da8b2 --- /dev/null +++ b/core/embed/rust/src/ui/shape/text.rs @@ -0,0 +1,134 @@ +use crate::ui::{ + display::{Color, Font}, + geometry::{Alignment, Offset, Point, Rect}, +}; + +use super::{BitmapView, Canvas, DrawingCache, Renderer, Shape, ShapeClone}; + +use without_alloc::alloc::LocalAllocLeakExt; + +/// A shape for text strings rendering. +pub struct Text<'a> { + // Text position + pos: Point, + // Text string + text: &'a str, + // Text color + color: Color, + // Text font + font: Font, + // Horizontal alignment + align: Alignment, + // Final bounds calculated when rendered + bounds: Rect, +} + +impl<'a> Text<'a> { + /// Creates a `shape::Text` structure with a specified + /// text (`str`) and the top-left corner (`pos`). + pub fn new(pos: Point, text: &'a str) -> Self { + Self { + pos, + text, + color: Color::white(), + font: Font::NORMAL, + align: Alignment::Start, + bounds: Rect::zero(), + } + } + + pub fn with_fg(self, color: Color) -> Self { + Self { color, ..self } + } + + pub fn with_font(self, font: Font) -> Self { + Self { font, ..self } + } + + pub fn with_align(self, align: Alignment) -> Self { + Self { align, ..self } + } + + pub fn render<'r>(mut self, renderer: &mut impl Renderer<'r>) { + self.bounds = self.calc_bounds(); + renderer.render_shape(self); + } + + fn aligned_pos(&self) -> Point { + match self.align { + Alignment::Start => self.pos, + Alignment::Center => Point::new( + self.font.horz_center(self.pos.x, self.pos.x, self.text), + self.pos.y, + ), + Alignment::End => Point::new(self.pos.x - self.font.text_width(self.text), self.pos.y), + } + } + + fn calc_bounds(&self) -> Rect { + let pos = self.aligned_pos(); + let (ascent, descent) = self.font.visible_text_height_ex(self.text); + Rect { + x0: pos.x, + y0: pos.y - ascent, + x1: pos.x + self.font.text_width(self.text), + y1: pos.y + descent, + } + } +} + +impl<'a> Shape<'_> for Text<'a> { + fn bounds(&self, _cache: &DrawingCache) -> Rect { + self.bounds + } + + fn cleanup(&mut self, _cache: &DrawingCache) {} + + fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache) { + let mut r = self.bounds(cache); + let max_ascent = self.pos.y - r.y0; + + // TODO: optimize text clipping, use canvas.viewport() + + for ch in self.text.chars() { + if r.x0 >= r.x1 { + break; + } + + let glyph = self.font.get_glyph(ch); + let glyph_bitmap = glyph.bitmap(); + let glyph_view = BitmapView::new(&glyph_bitmap) + .with_fg(self.color) + .with_offset(Offset::new( + -glyph.bearing_x, + -(max_ascent - glyph.bearing_y), + )); + + canvas.blend_bitmap(r, glyph_view); + r.x0 += glyph.adv; + } + } +} + +impl<'a, 's> ShapeClone<'s> for Text<'a> { + fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>> + where + T: LocalAllocLeakExt<'alloc>, + { + let clone = bump.alloc_t::()?; + let text = bump.copy_str(self.text)?; + Some(clone.uninit.init(Text { text, ..self })) + } +} + +impl Font { + fn visible_text_height_ex(&self, text: &str) -> (i16, i16) { + let (mut ascent, mut descent) = (0, 0); + for c in text.chars() { + let glyph = self.get_glyph(c); + ascent = ascent.max(glyph.bearing_y); + descent = descent.max(glyph.height - glyph.bearing_y); + } + (ascent, descent) + } +} diff --git a/core/embed/rust/src/ui/shape/toif.rs b/core/embed/rust/src/ui/shape/toif.rs new file mode 100644 index 000000000..f01dde5cf --- /dev/null +++ b/core/embed/rust/src/ui/shape/toif.rs @@ -0,0 +1,174 @@ +use crate::ui::{ + display::{toif::Toif, Color}, + geometry::{Alignment2D, Offset, Point, Rect}, +}; + +use super::{Bitmap, BitmapFormat, Canvas, DrawingCache, Renderer, Shape, ShapeClone}; + +use without_alloc::alloc::LocalAllocLeakExt; + +/// A shape for rendering compressed TOIF images. +pub struct ToifImage<'a> { + /// Image position + pos: Point, + // Image position alignment + align: Alignment2D, + // Image data + toif: Toif<'a>, + // Foreground color + fg_color: Color, + // Optional background color + bg_color: Option, +} + +impl<'a> ToifImage<'a> { + pub fn new(pos: Point, toif: Toif<'a>) -> Self { + Self { + pos, + align: Alignment2D::TOP_LEFT, + toif, + fg_color: Color::white(), + bg_color: None, + } + } + + pub fn with_align(self, align: Alignment2D) -> Self { + Self { align, ..self } + } + + pub fn with_fg(self, fg_color: Color) -> Self { + Self { fg_color, ..self } + } + + pub fn with_bg(self, bg_color: Color) -> Self { + Self { + bg_color: Some(bg_color), + ..self + } + } + + pub fn render(self, renderer: &mut impl Renderer<'a>) { + renderer.render_shape(self); + } + + fn draw_grayscale(&self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) { + // TODO: introduce new viewport/shape function for this calculation + let bounds = self.bounds(cache); + let viewport = canvas.viewport(); + let mut clip = self + .bounds(cache) + .clamp(viewport.clip.translate(-viewport.origin)) + .translate((-bounds.top_left()).into()); + + let buff = &mut unwrap!(cache.image_buff(), "No image buffer"); + let mut slice = unwrap!( + Bitmap::new_mut( + BitmapFormat::MONO4, + None, + self.toif.size(), + Some(1), + &mut buff[..] + ), + "Too small buffer" + ); + + while !clip.is_empty() { + let height = core::cmp::min(slice.height(), clip.height()); + unwrap!( + cache.zlib().uncompress_toif( + self.toif, + clip.y0, + unwrap!(slice.rows_mut(0, height)), // should never fail + ), + "Invalid TOIF" + ); + + let r = clip.translate(bounds.top_left().into()); + + let slice_view = slice + .view() + .with_fg(self.fg_color) + .with_offset(Offset::new(r.x0 - bounds.top_left().x, 0)); + + match self.bg_color { + Some(bg_color) => canvas.draw_bitmap(r, slice_view.with_bg(bg_color)), + None => canvas.blend_bitmap(r, slice_view), + } + + clip.y0 += height; + } + } + + fn draw_rgb(&self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) { + // TODO: introduce new viewport/shape function for this calculation + let bounds = self.bounds(cache); + let viewport = canvas.viewport(); + let mut clip = self + .bounds(cache) + .clamp(viewport.clip.translate(-viewport.origin)) + .translate((-bounds.top_left()).into()); + + let buff = &mut unwrap!(cache.image_buff(), "No image buffer"); + let mut slice = unwrap!( + Bitmap::new_mut( + BitmapFormat::RGB565, + None, + self.toif.size(), + Some(1), + &mut buff[..] + ), + "Too small buffer" + ); + + while !clip.is_empty() { + let height = core::cmp::min(slice.height(), clip.height()); + + if let Some(row_bytes) = slice.rows_mut(0, height) { + // always true + unwrap!( + cache.zlib().uncompress_toif(self.toif, clip.y0, row_bytes,), + "Invalid TOIF" + ); + } + + let r = clip.translate(bounds.top_left().into()); + + let slice_view = slice + .view() + .with_offset(Offset::new(r.x0 - bounds.top_left().x, 0)); + + canvas.draw_bitmap(r, slice_view); + + clip.y0 += height; + } + } +} + +impl<'a> Shape<'a> for ToifImage<'a> { + fn bounds(&self, _cache: &DrawingCache<'a>) -> Rect { + let size = Offset::new(self.toif.width(), self.toif.height()); + Rect::from_top_left_and_size(size.snap(self.pos, self.align), size) + } + + fn cleanup(&mut self, _cache: &DrawingCache<'a>) { + // TODO: inform the cache that we won't use the zlib slot anymore + } + + fn draw(&mut self, canvas: &mut dyn Canvas, cache: &DrawingCache<'a>) { + if self.toif.is_grayscale() { + self.draw_grayscale(canvas, cache); + } else { + self.draw_rgb(canvas, cache); + } + } +} + +impl<'a> ShapeClone<'a> for ToifImage<'a> { + fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'a>> + where + T: LocalAllocLeakExt<'alloc>, + { + let clone = bump.alloc_t::()?; + Some(clone.uninit.init(ToifImage { ..self })) + } +} diff --git a/core/embed/rust/src/ui/util.rs b/core/embed/rust/src/ui/util.rs index f3e4f33fb..6fac941de 100644 --- a/core/embed/rust/src/ui/util.rs +++ b/core/embed/rust/src/ui/util.rs @@ -179,6 +179,12 @@ macro_rules! include_icon { }; } +/// Splits a version stored as a u32 into four numbers +/// starting with the major version. +pub fn version_split(version: u32) -> [u8; 4] { + version.to_le_bytes() +} + #[cfg(test)] mod tests { use crate::strutil; From d909275c6dbe700e7af9d09789da5fe76580e5c5 Mon Sep 17 00:00:00 2001 From: cepetr Date: Fri, 19 Apr 2024 09:16:14 +0200 Subject: [PATCH 04/14] fixup! feat(core): introduce new drawing library --- core/embed/rust/src/ui/shape/canvas/common.rs | 3 ++- core/embed/rust/src/ui/shape/display/fake_display.rs | 5 ++--- core/embed/rust/src/ui/shape/display/fb_mono8.rs | 8 ++++---- core/embed/rust/src/ui/shape/display/fb_rgb565.rs | 8 ++++---- core/embed/rust/src/ui/shape/display/fb_rgba8888.rs | 8 ++++---- core/embed/rust/src/ui/shape/display/nofb_rgb565.rs | 6 +++--- core/embed/rust/src/ui/shape/render.rs | 8 +++++--- 7 files changed, 24 insertions(+), 22 deletions(-) diff --git a/core/embed/rust/src/ui/shape/canvas/common.rs b/core/embed/rust/src/ui/shape/canvas/common.rs index db7801843..3da371875 100644 --- a/core/embed/rust/src/ui/shape/canvas/common.rs +++ b/core/embed/rust/src/ui/shape/canvas/common.rs @@ -63,7 +63,8 @@ pub trait BasicCanvas { /// Fills the canvas background with the specified color. fn fill_background(&mut self, color: Color) { - self.fill_rect(self.viewport().clip, color, 255); + let vp = self.viewport(); + self.fill_rect(vp.clip.translate(-vp.origin), color, 255); } /// Draws a bitmap of bitmap into to the rectangle. diff --git a/core/embed/rust/src/ui/shape/display/fake_display.rs b/core/embed/rust/src/ui/shape/display/fake_display.rs index 329a838c9..380455312 100644 --- a/core/embed/rust/src/ui/shape/display/fake_display.rs +++ b/core/embed/rust/src/ui/shape/display/fake_display.rs @@ -1,10 +1,9 @@ use crate::ui::{ display::Color, - geometry::Rect, - shape::{DirectRenderer, Mono8Canvas}, + shape::{DirectRenderer, Mono8Canvas, Viewport}, }; -pub fn render_on_display<'a, F>(_clip: Option, _bg_color: Option, _func: F) +pub fn render_on_display<'a, F>(_viewport: Option, _bg_color: Option, _func: F) where F: FnOnce(&mut DirectRenderer<'_, 'a, Mono8Canvas<'a>>), { diff --git a/core/embed/rust/src/ui/shape/display/fb_mono8.rs b/core/embed/rust/src/ui/shape/display/fb_mono8.rs index d717de43c..aa0a454d4 100644 --- a/core/embed/rust/src/ui/shape/display/fb_mono8.rs +++ b/core/embed/rust/src/ui/shape/display/fb_mono8.rs @@ -1,6 +1,6 @@ use crate::ui::{ display::Color, - geometry::{Offset, Rect}, + geometry::Offset, shape::{BasicCanvas, DirectRenderer, DrawingCache, Mono8Canvas, Viewport}, }; @@ -18,7 +18,7 @@ use static_alloc::Bump; /// `bg_color` specifies a background color with which the clip is filled before /// the drawing starts. If the background color is None, the background /// is undefined, and the user has to fill it themselves. -pub fn render_on_display<'a, F>(clip: Option, bg_color: Option, func: F) +pub fn render_on_display<'a, F>(viewport: Option, bg_color: Option, func: F) where F: FnOnce(&mut DirectRenderer<'_, 'a, Mono8Canvas<'a>>), { @@ -44,8 +44,8 @@ where fb )); - if let Some(clip) = clip { - canvas.set_viewport(Viewport::new(clip)); + if let Some(viewport) = viewport { + canvas.set_viewport(viewport); } let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache); 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 e8734958a..c229d980a 100644 --- a/core/embed/rust/src/ui/shape/display/fb_rgb565.rs +++ b/core/embed/rust/src/ui/shape/display/fb_rgb565.rs @@ -1,6 +1,6 @@ use crate::ui::{ display::Color, - geometry::{Offset, Rect}, + geometry::Offset, shape::{BasicCanvas, DirectRenderer, DrawingCache, Rgb565Canvas, Viewport}, }; @@ -18,7 +18,7 @@ use static_alloc::Bump; /// `bg_color` specifies a background color with which the clip is filled before /// the drawing starts. If the background color is None, the background /// is undefined, and the user has to fill it themselves. -pub fn render_on_display<'a, F>(clip: Option, bg_color: Option, func: F) +pub fn render_on_display<'a, F>(viewport: Option, bg_color: Option, func: F) where F: FnOnce(&mut DirectRenderer<'_, 'a, Rgb565Canvas<'a>>), { @@ -51,8 +51,8 @@ where fb )); - if let Some(clip) = clip { - canvas.set_viewport(Viewport::new(clip)); + if let Some(viewport) = viewport { + canvas.set_viewport(viewport); } let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache); 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 ec6babfc8..deabf0bf7 100644 --- a/core/embed/rust/src/ui/shape/display/fb_rgba8888.rs +++ b/core/embed/rust/src/ui/shape/display/fb_rgba8888.rs @@ -1,6 +1,6 @@ use crate::ui::{ display::Color, - geometry::{Offset, Rect}, + geometry::Offset, shape::{BasicCanvas, DirectRenderer, DrawingCache, Rgba8888Canvas, Viewport}, }; @@ -18,7 +18,7 @@ use crate::trezorhal::display; /// `bg_color` specifies a background color with which the clip is filled before /// the drawing starts. If the background color is None, the background /// is undefined, and the user has to fill it themselves. -pub fn render_on_display<'a, F>(clip: Option, bg_color: Option, func: F) +pub fn render_on_display<'a, F>(viewport: Option, bg_color: Option, func: F) where F: FnOnce(&mut DirectRenderer<'_, 'a, Rgba8888Canvas<'a>>), { @@ -51,8 +51,8 @@ where fb )); - if let Some(clip) = clip { - canvas.set_viewport(Viewport::new(clip)); + if let Some(viewport) = viewport { + canvas.set_viewport(Viewport::new(viewport)); } let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache); diff --git a/core/embed/rust/src/ui/shape/display/nofb_rgb565.rs b/core/embed/rust/src/ui/shape/display/nofb_rgb565.rs index 2fda844ed..c518265b3 100644 --- a/core/embed/rust/src/ui/shape/display/nofb_rgb565.rs +++ b/core/embed/rust/src/ui/shape/display/nofb_rgb565.rs @@ -30,7 +30,7 @@ const BUMP_B_SIZE: usize = DrawingCache::get_bump_b_size(); /// `bg_color` specifies a background color with which the clip is filled before /// the drawing starts. If the background color is None, the background /// is undefined, and the user has to fill it themselves. -pub fn render_on_display<'a, F>(clip: Option, bg_color: Option, func: F) +pub fn render_on_display<'a, F>(viewport: Option, bg_color: Option, func: F) where F: FnOnce(&mut ProgressiveRenderer<'_, 'a, Bump<[u8; BUMP_A_SIZE]>, DisplayCanvas>), { @@ -49,8 +49,8 @@ where let cache = DrawingCache::new(bump_a, bump_b); let mut canvas = DisplayCanvas::new(); - if let Some(clip) = clip { - canvas.set_viewport(Viewport::new(clip)); + if let Some(viewport) = viewport { + canvas.set_viewport(viewport); } let mut target = diff --git a/core/embed/rust/src/ui/shape/render.rs b/core/embed/rust/src/ui/shape/render.rs index dd5191dd5..6748ab64c 100644 --- a/core/embed/rust/src/ui/shape/render.rs +++ b/core/embed/rust/src/ui/shape/render.rs @@ -170,7 +170,10 @@ where /// Renders stored shapes onto the specified canvas pub fn render(&mut self, lines: usize) { let canvas_clip = self.canvas.viewport().clip; - let canvas_origin = self.canvas.viewport().origin; + + if canvas_clip.is_empty() { + return; + } let buff = &mut unwrap!(self.cache.render_buff(), "No render buffer"); @@ -190,8 +193,7 @@ where // slice_r is in absolute coordinates Point::new(canvas_clip.x0, y), Point::new(canvas_clip.x1, y + lines as i16), - ) - .translate(-canvas_origin); + ); // Clear the slice background if let Some(color) = self.bg_color { From 924cdb34d8c16b5ec383b8061874f0f06f3922b0 Mon Sep 17 00:00:00 2001 From: cepetr Date: Wed, 17 Apr 2024 15:40:23 +0200 Subject: [PATCH 05/14] fixup! feat(core): introduce new drawing library --- core/embed/rust/src/ui/shape/canvas/common.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/embed/rust/src/ui/shape/canvas/common.rs b/core/embed/rust/src/ui/shape/canvas/common.rs index 3da371875..4b043a99b 100644 --- a/core/embed/rust/src/ui/shape/canvas/common.rs +++ b/core/embed/rust/src/ui/shape/canvas/common.rs @@ -815,11 +815,21 @@ fn fill_octant( // Process area between a p1 and p2 lines let p2_iter = line_points(p2_start.v, p2_start.u, 0).skip(skip); + let mut first = true; for (p1, p2) in p1_iter.zip(p2_iter) { let p1_coord = Point::new(p1_start.u - p1.v, -p1_start.v + p1.u); let p2_coord = Point::new(p2_start.u - p2.v, -p2_start.v + p2.u); - let p2_frac = if join_flag { 255 } else { 255 - p2.frac }; + let p2_frac = if first { + p2_start.frac + } else { + if join_flag { + 255 + } else { + 255 - p2.frac + } + }; fill(Some(p1_coord), p1.frac, p2_coord, p2_frac); + first = false; } } From 03457b5d454046ccc6b667d38be4f344265e459e Mon Sep 17 00:00:00 2001 From: cepetr Date: Tue, 16 Apr 2024 15:53:42 +0200 Subject: [PATCH 06/14] feat(core): integrate new drawing library [no changelog] --- core/Makefile | 5 + core/SConscript.unix | 2 +- core/embed/bootloader/bootui.c | 47 +++- core/embed/bootloader/bootui.h | 18 +- core/embed/bootloader/emulator.c | 1 + core/embed/bootloader/main.c | 8 +- core/embed/bootloader/memory_stm32f4.ld | 2 + core/embed/rust/rust_ui.h | 1 + core/embed/rust/src/ui/api/bootloader_c.rs | 18 ++ core/embed/rust/src/ui/api/common_c.rs | 6 +- core/embed/rust/src/ui/component/base.rs | 28 +++ core/embed/rust/src/ui/component/border.rs | 9 +- core/embed/rust/src/ui/component/connect.rs | 17 +- core/embed/rust/src/ui/component/empty.rs | 4 +- core/embed/rust/src/ui/component/image.rs | 17 ++ core/embed/rust/src/ui/component/label.rs | 5 + core/embed/rust/src/ui/component/map.rs | 6 +- core/embed/rust/src/ui/component/marquee.rs | 43 +++- core/embed/rust/src/ui/component/maybe.rs | 8 + core/embed/rust/src/ui/component/pad.rs | 8 + core/embed/rust/src/ui/component/painter.rs | 14 ++ core/embed/rust/src/ui/component/placed.rs | 18 ++ core/embed/rust/src/ui/component/qr_code.rs | 35 +++ .../rust/src/ui/component/text/formatted.rs | 7 +- .../rust/src/ui/component/text/layout.rs | 72 ++++++ .../rust/src/ui/component/text/paragraphs.rs | 48 ++++ core/embed/rust/src/ui/component/text/util.rs | 59 +++++ core/embed/rust/src/ui/component/timeout.rs | 3 + core/embed/rust/src/ui/display/tjpgd.rs | 77 +++++++ core/embed/rust/src/ui/layout/obj.rs | 26 ++- core/embed/rust/src/ui/layout/simplified.rs | 40 +++- .../src/ui/model_mercury/bootloader/intro.rs | 10 + .../src/ui/model_mercury/bootloader/menu.rs | 9 + .../src/ui/model_mercury/bootloader/mod.rs | 178 ++++++++++++++- .../ui/model_mercury/bootloader/welcome.rs | 31 +++ .../ui/model_mercury/component/bl_confirm.rs | 36 +++ .../src/ui/model_mercury/component/button.rs | 107 +++++++++ .../src/ui/model_mercury/component/error.rs | 17 ++ .../src/ui/model_mercury/component/frame.rs | 7 + .../src/ui/model_mercury/component/loader.rs | 46 +++- .../src/ui/model_mercury/component/result.rs | 33 +++ .../model_mercury/component/welcome_screen.rs | 25 ++- .../rust/src/ui/model_mercury/screens.rs | 20 ++ .../rust/src/ui/model_tr/bootloader/intro.rs | 23 ++ .../rust/src/ui/model_tr/bootloader/menu.rs | 29 ++- .../rust/src/ui/model_tr/bootloader/mod.rs | 149 ++++++++++++- .../src/ui/model_tr/bootloader/welcome.rs | 28 ++- .../ui/model_tr/component/address_details.rs | 11 + .../src/ui/model_tr/component/bl_confirm.rs | 28 +++ .../rust/src/ui/model_tr/component/button.rs | 84 +++++++ .../model_tr/component/button_controller.rs | 26 +++ .../ui/model_tr/component/changing_text.rs | 41 ++++ .../model_tr/component/coinjoin_progress.rs | 59 ++++- .../rust/src/ui/model_tr/component/error.rs | 28 +++ .../rust/src/ui/model_tr/component/flow.rs | 18 ++ .../src/ui/model_tr/component/flow_pages.rs | 5 + .../rust/src/ui/model_tr/component/frame.rs | 12 + .../ui/model_tr/component/hold_to_confirm.rs | 5 + .../src/ui/model_tr/component/homescreen.rs | 182 +++++++++++++-- .../component/input_methods/choice.rs | 139 ++++++++++++ .../component/input_methods/choice_item.rs | 102 +++++++++ .../component/input_methods/number_input.rs | 5 + .../component/input_methods/passphrase.rs | 6 + .../model_tr/component/input_methods/pin.rs | 7 + .../component/input_methods/simple_choice.rs | 5 + .../component/input_methods/wordlist.rs | 6 + .../rust/src/ui/model_tr/component/loader.rs | 75 ++++++- .../rust/src/ui/model_tr/component/page.rs | 7 + .../src/ui/model_tr/component/progress.rs | 28 ++- .../rust/src/ui/model_tr/component/result.rs | 15 ++ .../src/ui/model_tr/component/scrollbar.rs | 50 +++++ .../src/ui/model_tr/component/share_words.rs | 56 ++++- .../src/ui/model_tr/component/show_more.rs | 6 + .../rust/src/ui/model_tr/component/title.rs | 47 +++- .../ui/model_tr/component/welcome_screen.rs | 26 +++ .../src/ui/model_tr/cshape/dotted_line.rs | 87 ++++++++ .../src/ui/model_tr/cshape/loader_circular.rs | 116 ++++++++++ .../src/ui/model_tr/cshape/loader_small.rs | 88 ++++++++ .../src/ui/model_tr/cshape/loader_starry.rs | 105 +++++++++ core/embed/rust/src/ui/model_tr/cshape/mod.rs | 9 + core/embed/rust/src/ui/model_tr/layout.rs | 16 +- core/embed/rust/src/ui/model_tr/mod.rs | 1 + core/embed/rust/src/ui/model_tr/screens.rs | 29 ++- .../rust/src/ui/model_tt/bootloader/intro.rs | 10 + .../rust/src/ui/model_tt/bootloader/menu.rs | 9 + .../rust/src/ui/model_tt/bootloader/mod.rs | 170 +++++++++++++- .../src/ui/model_tt/bootloader/welcome.rs | 28 ++- .../ui/model_tt/component/address_details.rs | 9 + .../src/ui/model_tt/component/bl_confirm.rs | 36 +++ .../rust/src/ui/model_tt/component/button.rs | 107 +++++++++ .../model_tt/component/coinjoin_progress.rs | 39 +++- .../rust/src/ui/model_tt/component/dialog.rs | 12 + .../rust/src/ui/model_tt/component/error.rs | 17 ++ .../rust/src/ui/model_tt/component/fido.rs | 32 +++ .../rust/src/ui/model_tt/component/frame.rs | 8 + .../ui/model_tt/component/homescreen/mod.rs | 211 +++++++++++++++++- .../ui/model_tt/component/keyboard/bip39.rs | 49 +++- .../ui/model_tt/component/keyboard/common.rs | 23 ++ .../model_tt/component/keyboard/mnemonic.rs | 14 ++ .../model_tt/component/keyboard/passphrase.rs | 52 ++++- .../src/ui/model_tt/component/keyboard/pin.rs | 93 +++++++- .../ui/model_tt/component/keyboard/slip39.rs | 69 +++++- .../model_tt/component/keyboard/word_count.rs | 7 + .../rust/src/ui/model_tt/component/loader.rs | 50 ++++- .../src/ui/model_tt/component/number_input.rs | 30 ++- .../rust/src/ui/model_tt/component/page.rs | 36 ++- .../src/ui/model_tt/component/progress.rs | 45 +++- .../rust/src/ui/model_tt/component/result.rs | 33 +++ .../rust/src/ui/model_tt/component/scroll.rs | 45 ++++ .../src/ui/model_tt/component/simple_page.rs | 13 ++ .../rust/src/ui/model_tt/component/swipe.rs | 3 + .../ui/model_tt/component/welcome_screen.rs | 47 +++- core/embed/rust/src/ui/model_tt/constant.rs | 5 + core/embed/rust/src/ui/model_tt/screens.rs | 29 ++- core/embed/rust/src/ui/ui_features.rs | 9 + 115 files changed, 4158 insertions(+), 112 deletions(-) create mode 100644 core/embed/rust/src/ui/model_tr/cshape/dotted_line.rs create mode 100644 core/embed/rust/src/ui/model_tr/cshape/loader_circular.rs create mode 100644 core/embed/rust/src/ui/model_tr/cshape/loader_small.rs create mode 100644 core/embed/rust/src/ui/model_tr/cshape/loader_starry.rs create mode 100644 core/embed/rust/src/ui/model_tr/cshape/mod.rs diff --git a/core/Makefile b/core/Makefile index ae74b037f..5c1dba804 100644 --- a/core/Makefile +++ b/core/Makefile @@ -248,6 +248,11 @@ build_bootloader_emu: ## build the unix bootloader emulator $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \ CMAKELISTS="$(CMAKELISTS)" NEW_RENDERING="$(NEW_RENDERING)" $(BOOTLOADER_EMU_BUILD_DIR)/bootloader.elf +build_bootloader_emu_debug: ## build the unix bootloader emulator + $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \ + CMAKELISTS="$(CMAKELISTS)" NEW_RENDERING="$(NEW_RENDERING)" TREZOR_EMULATOR_DEBUGGABLE=1 \ + $(BOOTLOADER_EMU_BUILD_DIR)/bootloader.elf + build_prodtest: ## build production test firmware $(SCONS) CFLAGS="$(CFLAGS)" PRODUCTION="$(PRODUCTION)" TREZOR_MODEL="$(TREZOR_MODEL)" \ CMAKELISTS="$(CMAKELISTS)" BOOTLOADER_DEVEL="$(BOOTLOADER_DEVEL)" $(PRODTEST_BUILD_DIR)/prodtest.bin diff --git a/core/SConscript.unix b/core/SConscript.unix index 0a848b301..bed716ab0 100644 --- a/core/SConscript.unix +++ b/core/SConscript.unix @@ -866,6 +866,7 @@ def cargo_build(): features.append('universal_fw') features.append('ui') features.append('translations') + if PYOPT == '0': features.append('debug') if DMA2D: @@ -880,7 +881,6 @@ def cargo_build(): if TREZOR_MODEL in ('R', '1'): features.append('button') - if NEW_RENDERING: features.append('new_rendering') if TREZOR_MODEL in ('T',): diff --git a/core/embed/bootloader/bootui.c b/core/embed/bootloader/bootui.c index 0033a5e1e..ae570c84f 100644 --- a/core/embed/bootloader/bootui.c +++ b/core/embed/bootloader/bootui.c @@ -22,9 +22,11 @@ #include TREZOR_BOARD #include "bootui.h" +#include "colors.h" #include "display.h" #include "display_draw.h" #include "display_utils.h" +#include "fonts/fonts.h" #ifdef TREZOR_EMULATOR #include "emulator.h" #else @@ -70,13 +72,17 @@ static void format_ver(const char *format, uint32_t version, char *buffer, // boot UI +#ifndef NEW_RENDERING static uint16_t boot_background; +#endif + static bool initial_setup = true; void ui_set_initial_setup(bool initial) { initial_setup = initial; } -void ui_screen_boot(const vendor_header *const vhdr, - const image_header *const hdr) { +#ifndef NEW_RENDERING +static void ui_screen_boot_old(const vendor_header *const vhdr, + const image_header *const hdr) { const int show_string = ((vhdr->vtrust & VTRUST_STRING) == 0); if ((vhdr->vtrust & VTRUST_RED) == 0) { boot_background = COLOR_BL_FAIL; @@ -128,9 +134,11 @@ void ui_screen_boot(const vendor_header *const vhdr, display_pixeldata_dirty(); display_refresh(); } +#endif -void ui_screen_boot_wait(int wait_seconds) { - char wait_str[16]; +#ifndef NEW_RENDERING +static void ui_screen_boot_wait(int wait_seconds) { + char wait_str[32]; mini_snprintf(wait_str, sizeof(wait_str), "starting in %d s", wait_seconds); display_bar(0, BOOT_WAIT_Y_TOP, DISPLAY_RESX, BOOT_WAIT_HEIGHT, boot_background); @@ -139,6 +147,7 @@ void ui_screen_boot_wait(int wait_seconds) { display_pixeldata_dirty(); display_refresh(); } +#endif #if defined USE_TOUCH #include "touch.h" @@ -180,7 +189,8 @@ void ui_click(void) { #error "No input method defined" #endif -void ui_screen_boot_click(void) { +#ifndef NEW_RENDERING +static 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); @@ -188,6 +198,33 @@ void ui_screen_boot_click(void) { display_refresh(); ui_click(); } +#endif + +#ifdef NEW_RENDERING +void ui_screen_boot(const vendor_header *const vhdr, + const image_header *const hdr, int wait) { + bool show_string = ((vhdr->vtrust & VTRUST_STRING) == 0); + const char *vendor_str = show_string ? vhdr->vstr : NULL; + const size_t vendor_str_len = show_string ? vhdr->vstr_len : 0; + bool red_screen = ((vhdr->vtrust & VTRUST_RED) == 0); + uint32_t vimg_len = *(uint32_t *)(vhdr->vimg + 8); + + screen_boot(red_screen, vendor_str, vendor_str_len, hdr->version, vhdr->vimg, + vimg_len, wait); +} +#else // NEW_RENDERING + +void ui_screen_boot(const vendor_header *const vhdr, + const image_header *const hdr, int wait) { + if (wait == 0) { + ui_screen_boot_old(vhdr, hdr); + } else if (wait > 0) { + ui_screen_boot_wait(wait); + } else { + ui_screen_boot_click(); + } +} +#endif // welcome UI diff --git a/core/embed/bootloader/bootui.h b/core/embed/bootloader/bootui.h index c63a6ffa3..e7415b950 100644 --- a/core/embed/bootloader/bootui.h +++ b/core/embed/bootloader/bootui.h @@ -34,10 +34,22 @@ typedef enum { SCREEN_WELCOME = 5, } screen_t; +// Displays a warning screeen before jumping to the untrusted firmware +// +// Shows vendor image, vendor string and firmware version +// and optional message to the user (see `wait` argument) +// +// `wait` argument specifies a message to the user +// 0 do not show any message +// > 0 show a message like "starting in %d s" +// < 0 show a message like "press button to continue" void ui_screen_boot(const vendor_header* const vhdr, - const image_header* const hdr); -void ui_screen_boot_wait(int wait_seconds); -void ui_screen_boot_click(void); + const image_header* const hdr, int wait); + +// Waits until the user confirms the untrusted firmware +// +// Implementation is device specific - it wait's until +// the user presses a button, touches the display void ui_click(void); void ui_screen_welcome(void); diff --git a/core/embed/bootloader/emulator.c b/core/embed/bootloader/emulator.c index 221741212..5b36e1fe9 100644 --- a/core/embed/bootloader/emulator.c +++ b/core/embed/bootloader/emulator.c @@ -110,6 +110,7 @@ __attribute__((noreturn)) void display_error_and_die(const char *message, } __attribute__((noreturn)) int main(int argc, char **argv) { + display_init(); flash_init(); flash_otp_init(); diff --git a/core/embed/bootloader/main.c b/core/embed/bootloader/main.c index 180bef943..60d418c4f 100644 --- a/core/embed/bootloader/main.c +++ b/core/embed/bootloader/main.c @@ -72,7 +72,6 @@ #include "emulator.h" #else #include "compiler_traits.h" -#include "mini_printf.h" #include "mpu.h" #include "platform.h" #endif @@ -332,13 +331,13 @@ void real_jump_to_firmware(void) { // if all VTRUST flags are unset = ultimate trust => skip the procedure if ((vhdr.vtrust & VTRUST_ALL) != VTRUST_ALL) { ui_fadeout(); - ui_screen_boot(&vhdr, hdr); + ui_screen_boot(&vhdr, hdr, 0); ui_fadein(); int delay = (vhdr.vtrust & VTRUST_WAIT) ^ VTRUST_WAIT; if (delay > 1) { while (delay > 0) { - ui_screen_boot_wait(delay); + ui_screen_boot(&vhdr, hdr, delay); hal_delay(1000); delay--; } @@ -347,7 +346,8 @@ void real_jump_to_firmware(void) { } if ((vhdr.vtrust & VTRUST_CLICK) == 0) { - ui_screen_boot_click(); + ui_screen_boot(&vhdr, hdr, -1); + ui_click(); } ui_screen_boot_stage_1(false); diff --git a/core/embed/bootloader/memory_stm32f4.ld b/core/embed/bootloader/memory_stm32f4.ld index 366280814..392c9a1d8 100644 --- a/core/embed/bootloader/memory_stm32f4.ld +++ b/core/embed/bootloader/memory_stm32f4.ld @@ -67,6 +67,8 @@ SECTIONS { .buf : ALIGN(4) { *(.buf*); . = ALIGN(4); + *(.no_dma_buffers*); + . = ALIGN(4); } >SRAM .boot_args : ALIGN(8) { diff --git a/core/embed/rust/rust_ui.h b/core/embed/rust/rust_ui.h index d2cc626f9..897c6a331 100644 --- a/core/embed/rust/rust_ui.h +++ b/core/embed/rust/rust_ui.h @@ -1,5 +1,6 @@ #include #include "common.h" + #include "rust_ui_bootloader.h" #include "rust_ui_common.h" diff --git a/core/embed/rust/src/ui/api/bootloader_c.rs b/core/embed/rust/src/ui/api/bootloader_c.rs index 9eac86cd2..7628d46d2 100644 --- a/core/embed/rust/src/ui/api/bootloader_c.rs +++ b/core/embed/rust/src/ui/api/bootloader_c.rs @@ -101,6 +101,24 @@ extern "C" fn screen_boot_stage_1(fading: bool) { ModelUI::screen_boot_stage_1(fading) } +#[no_mangle] +#[cfg(feature = "new_rendering")] +extern "C" fn screen_boot( + warning: bool, + vendor_str: *const cty::c_char, + vendor_str_len: usize, + version: u32, + vendor_img: *const cty::c_void, + vendor_img_len: usize, + wait: i32, +) { + let vendor_str = unsafe { from_c_array(vendor_str, vendor_str_len) }; + let vendor_img = + unsafe { core::slice::from_raw_parts(vendor_img as *const u8, vendor_img_len) }; + + ModelUI::screen_boot(warning, vendor_str, version, vendor_img, wait); +} + #[no_mangle] extern "C" fn screen_wipe_progress(progress: u16, initialize: bool) { ModelUI::screen_wipe_progress(progress, initialize) diff --git a/core/embed/rust/src/ui/api/common_c.rs b/core/embed/rust/src/ui/api/common_c.rs index 51a427188..4f0b0b55b 100644 --- a/core/embed/rust/src/ui/api/common_c.rs +++ b/core/embed/rust/src/ui/api/common_c.rs @@ -1,11 +1,13 @@ //! Reexporting the `screens` module according to the //! current feature (Trezor model) +use crate::ui::ui_features::{ModelUI, UIFeaturesCommon}; + +#[cfg(not(feature = "new_rendering"))] use crate::ui::{ component::image::Image, display::{Color, Icon}, geometry::{Alignment2D, Point}, - ui_features::{ModelUI, UIFeaturesCommon}, }; use crate::ui::util::from_c_str; @@ -29,6 +31,7 @@ extern "C" fn screen_boot_stage_2() { } #[no_mangle] +#[cfg(not(feature = "new_rendering"))] extern "C" fn display_icon( x: cty::int16_t, y: cty::int16_t, @@ -48,6 +51,7 @@ extern "C" fn display_icon( } #[no_mangle] +#[cfg(not(feature = "new_rendering"))] extern "C" fn display_image( x: cty::int16_t, y: cty::int16_t, diff --git a/core/embed/rust/src/ui/component/base.rs b/core/embed/rust/src/ui/component/base.rs index 6703440b7..ab8a38fc4 100644 --- a/core/embed/rust/src/ui/component/base.rs +++ b/core/embed/rust/src/ui/component/base.rs @@ -9,6 +9,7 @@ use crate::{ component::{maybe::PaintOverlapping, MsgMap}, display::{self, Color}, geometry::{Offset, Rect}, + shape::Renderer, }, }; @@ -61,6 +62,8 @@ pub trait Component { /// the `Child` wrapper. fn paint(&mut self); + fn render<'s>(&'s self, _target: &mut impl Renderer<'s>); + #[cfg(feature = "ui_bounds")] /// Report current paint bounds of this component. Used for debugging. fn bounds(&self, _sink: &mut dyn FnMut(Rect)) {} @@ -154,6 +157,10 @@ where } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.component.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.component.bounds(sink) @@ -254,6 +261,10 @@ where self.inner.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.inner.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.inner.bounds(sink) @@ -292,6 +303,11 @@ where self.1.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.0.render(target); + self.1.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.0.bounds(sink); @@ -341,6 +357,12 @@ where self.2.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.0.render(target); + self.1.render(target); + self.2.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.0.bounds(sink); @@ -368,6 +390,12 @@ where } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + if let Some(ref c) = self { + c.render(target) + } + } + fn place(&mut self, bounds: Rect) -> Rect { match self { Some(ref mut c) => c.place(bounds), diff --git a/core/embed/rust/src/ui/component/border.rs b/core/embed/rust/src/ui/component/border.rs index e61cf381a..47941ae62 100644 --- a/core/embed/rust/src/ui/component/border.rs +++ b/core/embed/rust/src/ui/component/border.rs @@ -1,5 +1,8 @@ use super::{Component, Event, EventCtx}; -use crate::ui::geometry::{Insets, Rect}; +use crate::ui::{ + geometry::{Insets, Rect}, + shape::Renderer, +}; pub struct Border { border: Insets, @@ -39,6 +42,10 @@ where self.inner.paint() } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.inner.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.inner.bounds(sink); diff --git a/core/embed/rust/src/ui/component/connect.rs b/core/embed/rust/src/ui/component/connect.rs index 5faa38924..3d7897bdf 100644 --- a/core/embed/rust/src/ui/component/connect.rs +++ b/core/embed/rust/src/ui/component/connect.rs @@ -3,7 +3,8 @@ use crate::{ ui::{ component::{Component, Event, EventCtx, Never, Pad}, display::{self, Color, Font}, - geometry::{Offset, Rect}, + geometry::{Alignment, Offset, Rect}, + shape::{self, Renderer}, }, }; @@ -55,6 +56,20 @@ impl Component for Connect { ) }); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let font = Font::NORMAL; + + self.bg.render(target); + + self.message.map(|t| { + shape::Text::new(self.bg.area.center() + Offset::y(font.text_height() / 2), t) + .with_fg(self.fg) + .with_font(font) + .with_align(Alignment::Center) + .render(target); + }); + } } #[cfg(feature = "micropython")] diff --git a/core/embed/rust/src/ui/component/empty.rs b/core/embed/rust/src/ui/component/empty.rs index ce2a3b883..15fb017dc 100644 --- a/core/embed/rust/src/ui/component/empty.rs +++ b/core/embed/rust/src/ui/component/empty.rs @@ -1,5 +1,5 @@ use super::{Component, Event, EventCtx, Never}; -use crate::ui::geometry::Rect; +use crate::ui::{geometry::Rect, shape::Renderer}; pub struct Empty; @@ -15,6 +15,8 @@ impl Component for Empty { } fn paint(&mut self) {} + + fn render<'s>(&'s self, _target: &mut impl Renderer<'s>) {} } #[cfg(feature = "ui_debug")] diff --git a/core/embed/rust/src/ui/component/image.rs b/core/embed/rust/src/ui/component/image.rs index 41c1a6883..959ccf2c4 100644 --- a/core/embed/rust/src/ui/component/image.rs +++ b/core/embed/rust/src/ui/component/image.rs @@ -6,6 +6,8 @@ use crate::ui::{ Color, Icon, }, geometry::{Alignment2D, Offset, Point, Rect}, + shape, + shape::Renderer, }; #[derive(PartialEq, Eq, Clone, Copy)] @@ -48,6 +50,12 @@ impl Component for Image { self.draw(self.area.center(), Alignment2D::CENTER); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + shape::ToifImage::new(self.area.center(), self.toif) + .with_align(Alignment2D::CENTER) + .render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(Rect::from_center_and_size( @@ -130,6 +138,15 @@ impl Component for BlendedImage { self.paint_image(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + shape::ToifImage::new(self.bg_top_left, self.bg.toif) + .with_fg(self.bg_color) + .render(target); + shape::ToifImage::new(self.bg_top_left + self.fg_offset, self.fg.toif) + .with_fg(self.fg_color) + .render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(Rect::from_top_left_and_size( diff --git a/core/embed/rust/src/ui/component/label.rs b/core/embed/rust/src/ui/component/label.rs index 55364dcc9..4618e21d3 100644 --- a/core/embed/rust/src/ui/component/label.rs +++ b/core/embed/rust/src/ui/component/label.rs @@ -4,6 +4,7 @@ use crate::{ component::{Component, Event, EventCtx, Never}, display::Font, geometry::{Alignment, Insets, Offset, Point, Rect}, + shape::Renderer, }, }; @@ -114,6 +115,10 @@ impl Component for Label<'_> { self.text.map(|c| self.layout.render_text(c)); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.text.map(|c| self.layout.render_text2(c, target)); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.layout.bounds) diff --git a/core/embed/rust/src/ui/component/map.rs b/core/embed/rust/src/ui/component/map.rs index 867d51f59..76100cbf5 100644 --- a/core/embed/rust/src/ui/component/map.rs +++ b/core/embed/rust/src/ui/component/map.rs @@ -1,5 +1,5 @@ use super::{Component, Event, EventCtx}; -use crate::ui::geometry::Rect; +use crate::ui::{geometry::Rect, shape::Renderer}; pub struct MsgMap { inner: T, @@ -31,6 +31,10 @@ where self.inner.paint() } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.inner.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.inner.bounds(sink); diff --git a/core/embed/rust/src/ui/component/marquee.rs b/core/embed/rust/src/ui/component/marquee.rs index c2f39a081..d6a91386c 100644 --- a/core/embed/rust/src/ui/component/marquee.rs +++ b/core/embed/rust/src/ui/component/marquee.rs @@ -4,9 +4,9 @@ use crate::{ ui::{ animation::Animation, component::{Component, Event, EventCtx, Never, TimerToken}, - display, - display::{Color, Font}, - geometry::Rect, + display::{self, Color, Font}, + geometry::{Offset, Rect}, + shape::{self, Renderer}, util::animation_disabled, }, }; @@ -123,6 +123,19 @@ impl Marquee { self.text .map(|t| display::marquee(self.area, t, offset, self.font, self.fg, self.bg)); } + + pub fn render_anim<'s>(&'s self, target: &mut impl Renderer<'s>, offset: i16) { + target.in_window(self.area, &|target| { + let text_height = self.font.text_height(); + let pos = self.area.top_left() + Offset::new(offset, text_height - 1); + self.text.map(|t| { + shape::Text::new(pos, t) + .with_font(self.font) + .with_fg(self.fg) + .render(target); + }); + }); + } } impl Component for Marquee { @@ -214,6 +227,30 @@ impl Component for Marquee { } } } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let now = Instant::now(); + + match self.state { + State::Initial => { + self.render_anim(target, 0); + } + State::PauseRight => { + self.render_anim(target, self.min_offset); + } + State::PauseLeft => { + self.render_anim(target, self.max_offset); + } + _ => { + let progress = self.progress(now); + if let Some(done) = progress { + self.render_anim(target, done); + } else { + self.render_anim(target, 0); + } + } + } + } } #[cfg(feature = "ui_debug")] diff --git a/core/embed/rust/src/ui/component/maybe.rs b/core/embed/rust/src/ui/component/maybe.rs index 739f1ab82..bbc843bca 100644 --- a/core/embed/rust/src/ui/component/maybe.rs +++ b/core/embed/rust/src/ui/component/maybe.rs @@ -2,6 +2,7 @@ use crate::ui::{ component::{Component, ComponentExt, Event, EventCtx, Pad}, display::{self, Color}, geometry::Rect, + shape::Renderer, }; pub struct Maybe { @@ -94,6 +95,13 @@ where } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.pad.render(target); + if self.visible { + self.inner.render(target); + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.pad.area); diff --git a/core/embed/rust/src/ui/component/pad.rs b/core/embed/rust/src/ui/component/pad.rs index b26d90d77..fa6b5b065 100644 --- a/core/embed/rust/src/ui/component/pad.rs +++ b/core/embed/rust/src/ui/component/pad.rs @@ -1,6 +1,8 @@ use crate::ui::{ display::{self, Color}, geometry::Rect, + shape, + shape::Renderer, }; pub struct Pad { @@ -52,4 +54,10 @@ impl Pad { display::rect_fill(self.area, self.color); } } + + pub fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + shape::Bar::new(self.area) + .with_bg(self.color) + .render(target); + } } diff --git a/core/embed/rust/src/ui/component/painter.rs b/core/embed/rust/src/ui/component/painter.rs index 9d6b18e37..d361ef177 100644 --- a/core/embed/rust/src/ui/component/painter.rs +++ b/core/embed/rust/src/ui/component/painter.rs @@ -4,6 +4,8 @@ use crate::ui::{ component::{image::Image, Component, Event, EventCtx, Never}, display, geometry::{Alignment2D, Rect}, + shape, + shape::Renderer, }; pub struct Painter { @@ -39,6 +41,15 @@ where (self.func)(self.area); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let area = self.area; + shape::Bar::new(area) + .with_thickness(1) + .with_fg(display::Color::white()) + .render(target); + shape::Text::new(area.top_left(), "Paint").render(target); // !@# replace + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.area) @@ -64,7 +75,10 @@ pub fn jpeg_painter<'a>( scale: u8, ) -> Painter { let off = Offset::new(size.x / (2 << scale), size.y / (2 << scale)); + #[cfg(not(feature = "new_rendering"))] let f = move |area: Rect| display::tjpgd::jpeg(image(), area.center() - off, scale); + #[cfg(feature = "new_rendering")] + let f = move |area: Rect| {}; Painter::new(f) } diff --git a/core/embed/rust/src/ui/component/placed.rs b/core/embed/rust/src/ui/component/placed.rs index c4a758045..18fcc1e18 100644 --- a/core/embed/rust/src/ui/component/placed.rs +++ b/core/embed/rust/src/ui/component/placed.rs @@ -1,6 +1,7 @@ use crate::ui::{ component::{Component, Event, EventCtx}, geometry::{Alignment, Alignment2D, Axis, Grid, GridCellSpan, Insets, Offset, Rect}, + shape::Renderer, }; pub struct GridPlaced { @@ -63,6 +64,10 @@ where fn paint(&mut self) { self.inner.paint() } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.inner.render(target); + } } #[cfg(feature = "ui_debug")] @@ -106,6 +111,10 @@ where fn paint(&mut self) { self.inner.paint() } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.inner.render(target); + } } #[cfg(feature = "ui_debug")] @@ -178,6 +187,10 @@ where fn paint(&mut self) { self.inner.paint() } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.inner.render(target); + } } #[cfg(feature = "ui_debug")] @@ -269,6 +282,11 @@ where self.first.paint(); self.second.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.first.render(target); + self.second.render(target); + } } #[cfg(feature = "ui_debug")] diff --git a/core/embed/rust/src/ui/component/qr_code.rs b/core/embed/rust/src/ui/component/qr_code.rs index e99236e3a..6b13e116d 100644 --- a/core/embed/rust/src/ui/component/qr_code.rs +++ b/core/embed/rust/src/ui/component/qr_code.rs @@ -8,6 +8,8 @@ use crate::{ constant, display::{pixeldata, pixeldata_dirty, rect_fill_rounded, set_window, Color}, geometry::{Insets, Offset, Rect}, + shape, + shape::Renderer, }, }; @@ -141,6 +143,39 @@ impl Component for Qr { Self::draw(&qr, area, self.border, scale); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let mut outbuffer = [0u8; QR_MAX_VERSION.buffer_len()]; + let mut tempbuffer = [0u8; QR_MAX_VERSION.buffer_len()]; + + let qr = QrCode::encode_text( + self.text.as_ref(), + &mut tempbuffer, + &mut outbuffer, + QrCodeEcc::Medium, + Version::MIN, + QR_MAX_VERSION, + None, + true, + ); + let qr = unwrap!(qr); + + let scale = (self.area.width().min(self.area.height()) - self.border) / (qr.size() as i16); + let side = scale * qr.size() as i16; + let qr_area = Rect::from_center_and_size(self.area.center(), Offset::uniform(side)); + + if self.border > 0 { + shape::Bar::new(qr_area.expand(self.border)) + .with_bg(LIGHT) + .with_radius(CORNER_RADIUS as i16 + 1) // !@# + 1 to fix difference on TR + .render(target); + } + + shape::QrImage::new(qr_area, &qr) + .with_fg(LIGHT) + .with_bg(DARK) + .render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.area) diff --git a/core/embed/rust/src/ui/component/text/formatted.rs b/core/embed/rust/src/ui/component/text/formatted.rs index 48d97811e..52e1803c3 100644 --- a/core/embed/rust/src/ui/component/text/formatted.rs +++ b/core/embed/rust/src/ui/component/text/formatted.rs @@ -1,10 +1,11 @@ use crate::ui::{ component::{Component, Event, EventCtx, Never, Paginate}, geometry::{Alignment, Offset, Rect}, + shape::Renderer, }; use super::{ - layout::{LayoutFit, LayoutSink, TextNoOp, TextRenderer}, + layout::{LayoutFit, LayoutSink, TextNoOp, TextRenderer, TextRenderer2}, op::OpTextLayout, }; @@ -133,6 +134,10 @@ impl Component for FormattedText { self.layout_content(&mut TextRenderer); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.layout_content(&mut TextRenderer2::new(target)); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.op_layout.layout.bounds) diff --git a/core/embed/rust/src/ui/component/text/layout.rs b/core/embed/rust/src/ui/component/text/layout.rs index 5c86089df..682d41425 100644 --- a/core/embed/rust/src/ui/component/text/layout.rs +++ b/core/embed/rust/src/ui/component/text/layout.rs @@ -2,6 +2,8 @@ use crate::ui::{ display, display::{toif::Icon, Color, Font, GlyphMetrics}, geometry::{Alignment, Alignment2D, Dimensions, Offset, Point, Rect}, + shape, + shape::Renderer, }; const ELLIPSIS: &str = "..."; @@ -235,6 +237,15 @@ impl TextLayout { self.layout_text(text, &mut self.initial_cursor(), &mut TextRenderer) } + /// Draw as much text as possible on the current screen. + pub fn render_text2<'s>(&self, text: &str, target: &mut impl Renderer<'s>) -> LayoutFit { + self.layout_text( + text, + &mut self.initial_cursor(), + &mut TextRenderer2::new(target), + ) + } + /// Loop through the `text` and try to fit it on the current screen, /// reporting events to `sink`, which may do something with them (e.g. draw /// on screen). @@ -530,6 +541,67 @@ impl LayoutSink for TextRenderer { } } +pub struct TextRenderer2<'a, 's, R>(pub &'a mut R, core::marker::PhantomData<&'s ()>) +where + R: Renderer<'s>; + +impl<'a, 's, R> TextRenderer2<'a, 's, R> +where + R: Renderer<'s>, +{ + pub fn new(target: &'a mut R) -> Self { + Self(target, core::marker::PhantomData) + } +} + +impl<'a, 's, R> LayoutSink for TextRenderer2<'a, 's, R> +where + R: Renderer<'s>, +{ + fn text(&mut self, cursor: Point, layout: &TextLayout, text: &str) { + shape::Text::new(cursor, text) + .with_font(layout.style.text_font) + .with_fg(layout.style.text_color) + .render(self.0); + } + + fn hyphen(&mut self, cursor: Point, layout: &TextLayout) { + shape::Text::new(cursor, "-") + .with_font(layout.style.text_font) + .with_fg(layout.style.hyphen_color) + .render(self.0); + } + + fn ellipsis(&mut self, cursor: Point, layout: &TextLayout) { + if let Some((icon, margin)) = layout.style.ellipsis_icon { + let bottom_left = cursor + Offset::x(margin); + shape::ToifImage::new(bottom_left, icon.toif) + .with_align(Alignment2D::BOTTOM_LEFT) + .with_fg(layout.style.ellipsis_color) + .render(self.0); + } else { + shape::Text::new(cursor, ELLIPSIS) + .with_font(layout.style.text_font) + .with_fg(layout.style.ellipsis_color) + .render(self.0); + } + } + + fn prev_page_ellipsis(&mut self, cursor: Point, layout: &TextLayout) { + if let Some((icon, _margin)) = layout.style.prev_page_ellipsis_icon { + shape::ToifImage::new(cursor, icon.toif) + .with_align(Alignment2D::BOTTOM_LEFT) + .with_fg(layout.style.ellipsis_color) + .render(self.0); + } else { + shape::Text::new(cursor, ELLIPSIS) + .with_font(layout.style.text_font) + .with_fg(layout.style.ellipsis_color) + .render(self.0); + } + } +} + #[cfg(feature = "ui_debug")] pub mod trace { use crate::{trace::ListTracer, ui::geometry::Point}; diff --git a/core/embed/rust/src/ui/component/text/paragraphs.rs b/core/embed/rust/src/ui/component/text/paragraphs.rs index 1fd658c13..698bc1dfd 100644 --- a/core/embed/rust/src/ui/component/text/paragraphs.rs +++ b/core/embed/rust/src/ui/component/text/paragraphs.rs @@ -8,6 +8,8 @@ use crate::{ geometry::{ Alignment, Alignment2D, Dimensions, Insets, LinearPlacement, Offset, Point, Rect, }, + shape, + shape::Renderer, }, }; @@ -185,6 +187,17 @@ where ) } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + Self::foreach_visible( + &self.source, + &self.visible, + self.offset, + &mut |layout, content| { + layout.render_text2(content, target); + }, + ) + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.area); @@ -593,6 +606,19 @@ impl Checklist { layout.style.background_color, ); } + + fn render_icon<'s>( + &self, + layout: &TextLayout, + icon: Icon, + offset: Offset, + target: &mut impl Renderer<'s>, + ) { + let top_left = Point::new(self.area.x0, layout.bounds.y0); + shape::ToifImage::new(top_left + offset, icon.toif) + .with_fg(layout.style.text_color) + .render(target); + } } impl<'a, T> Component for Checklist @@ -632,6 +658,28 @@ where } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.paragraphs.render(target); + + let current_visible = self.current.saturating_sub(self.paragraphs.offset.par); + for layout in self.paragraphs.visible.iter().take(current_visible) { + self.render_icon( + &layout.layout(&self.paragraphs.source), + self.icon_done, + self.done_offset, + target, + ); + } + if let Some(layout) = self.paragraphs.visible.iter().nth(current_visible) { + self.render_icon( + &layout.layout(&self.paragraphs.source), + self.icon_current, + self.current_offset, + target, + ); + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.area); diff --git a/core/embed/rust/src/ui/component/text/util.rs b/core/embed/rust/src/ui/component/text/util.rs index b417539ca..69ddd4de4 100644 --- a/core/embed/rust/src/ui/component/text/util.rs +++ b/core/embed/rust/src/ui/component/text/util.rs @@ -3,6 +3,7 @@ use crate::{ ui::{ display::{Color, Font}, geometry::{Alignment, Rect}, + shape::Renderer, }, }; @@ -37,6 +38,33 @@ pub fn text_multiline( } } +/// Draws longer multiline texts inside an area. +/// Splits lines on word boundaries/whitespace. +/// When a word is too long to fit one line, splitting +/// it on multiple lines with "-" at the line-ends. +/// +/// If it fits, returns the rest of the area. +/// If it does not fit, returns `None`. +pub fn text_multiline2<'s>( + target: &mut impl Renderer<'s>, + area: Rect, + text: TString<'_>, + font: Font, + fg_color: Color, + bg_color: Color, + alignment: Alignment, +) -> Option { + let text_style = TextStyle::new(font, fg_color, bg_color, fg_color, fg_color); + let text_layout = TextLayout::new(text_style) + .with_bounds(area) + .with_align(alignment); + let layout_fit = text.map(|t| text_layout.render_text2(t, target)); + match layout_fit { + LayoutFit::Fitting { height, .. } => Some(area.split_top(height).1), + LayoutFit::OutOfBounds { .. } => None, + } +} + /// Same as `text_multiline` above, but aligns the text to the bottom of the /// area. pub fn text_multiline_bottom( @@ -66,3 +94,34 @@ pub fn text_multiline_bottom( } }) } + +/// Same as `text_multiline` above, but aligns the text to the bottom of the +/// area. +pub fn text_multiline_bottom2<'s>( + target: &mut impl Renderer<'s>, + area: Rect, + text: TString<'_>, + font: Font, + fg_color: Color, + bg_color: Color, + alignment: Alignment, +) -> Option { + let text_style = TextStyle::new(font, fg_color, bg_color, fg_color, fg_color); + let mut text_layout = TextLayout::new(text_style) + .with_bounds(area) + .with_align(alignment); + // When text fits the area, displaying it in the bottom part. + // When not, render it "normally". + text.map(|t| match text_layout.fit_text(t) { + LayoutFit::Fitting { height, .. } => { + let (top, bottom) = area.split_bottom(height); + text_layout = text_layout.with_bounds(bottom); + text_layout.render_text2(t, target); + Some(top) + } + LayoutFit::OutOfBounds { .. } => { + text_layout.render_text2(t, target); + None + } + }) +} diff --git a/core/embed/rust/src/ui/component/timeout.rs b/core/embed/rust/src/ui/component/timeout.rs index 67d29c750..6da62e725 100644 --- a/core/embed/rust/src/ui/component/timeout.rs +++ b/core/embed/rust/src/ui/component/timeout.rs @@ -3,6 +3,7 @@ use crate::{ ui::{ component::{Component, Event, EventCtx, TimerToken}, geometry::Rect, + shape::Renderer, }, }; @@ -44,6 +45,8 @@ impl Component for Timeout { } fn paint(&mut self) {} + + fn render<'s>(&'s self, _target: &mut impl Renderer<'s>) {} } #[cfg(feature = "ui_debug")] diff --git a/core/embed/rust/src/ui/display/tjpgd.rs b/core/embed/rust/src/ui/display/tjpgd.rs index 734cad50c..34b398c03 100644 --- a/core/embed/rust/src/ui/display/tjpgd.rs +++ b/core/embed/rust/src/ui/display/tjpgd.rs @@ -24,6 +24,7 @@ pub fn jpeg(data: &[u8], pos: Point, scale: u8) { } } +#[cfg(not(feature = "new_rendering"))] pub fn jpeg_info(data: &[u8]) -> Option<(Offset, i16)> { let mut buffer = BufferJpegWork::get_cleared(); let pool = buffer.buffer.as_mut_slice(); @@ -40,6 +41,82 @@ pub fn jpeg_info(data: &[u8]) -> Option<(Offset, i16)> { result } +#[cfg(feature = "new_rendering")] +pub fn jpeg_info(data: &[u8]) -> Option<(Offset, i16)> { + const M_SOI: u16 = 0xFFD8; + const M_SOF0: u16 = 0xFFC0; + const M_DRI: u16 = 0xFFDD; + const M_RST0: u16 = 0xFFD0; + const M_RST7: u16 = 0xFFD7; + const M_SOS: u16 = 0xFFDA; + const M_EOI: u16 = 0xFFD9; + + let mut result = None; + let mut ofs = 0; + + let read_u16 = |ofs| -> Option { + if ofs + 1 < data.len() { + Some(((data[ofs] as u16) << 8) + data[ofs + 1] as u16) + } else { + None + } + }; + + let read_u8 = |ofs| -> Option { + if ofs < data.len() { + Some(data[ofs]) + } else { + None + } + }; + + while ofs < data.len() { + if read_u16(ofs)? == M_SOI { + break; + } + ofs += 1; + } + + loop { + let marker = read_u16(ofs)?; + + if (marker & 0xFF00) != 0xFF00 { + return None; + } + + ofs += 2; + + ofs += match marker { + M_SOI => 0, + M_SOF0 => { + let w = read_u16(ofs + 3)? as i16; + let h = read_u16(ofs + 5)? as i16; + // Number of components + let nc = read_u8(ofs + 7)?; + if (nc != 1) && (nc != 3) { + return None; + } + // Sampling factor of the first component + let c1 = read_u8(ofs + 9)?; + if (c1 != 0x11) && (c1 != 0x21) & (c1 != 0x22) { + return None; + }; + let mcu_height = (8 * (c1 & 15)) as i16; + result = Some((Offset::new(w, h), mcu_height)); + + read_u16(ofs)? + } + M_DRI => 4, + M_EOI => return None, + M_RST0..=M_RST7 => 0, + M_SOS => break, + _ => read_u16(ofs)?, + } as usize; + } + + result +} + pub fn jpeg_test(data: &[u8]) -> bool { let mut buffer = BufferJpegWork::get_cleared(); let pool = buffer.buffer.as_mut_slice(); diff --git a/core/embed/rust/src/ui/layout/obj.rs b/core/embed/rust/src/ui/layout/obj.rs index 9f7cb0e73..6c00316ad 100644 --- a/core/embed/rust/src/ui/layout/obj.rs +++ b/core/embed/rust/src/ui/layout/obj.rs @@ -19,8 +19,9 @@ use crate::{ ui::{ component::{Component, Event, EventCtx, Never, Root, TimerToken}, constant, - display::sync, + display::{sync, Color}, geometry::Rect, + shape::render_on_display, }, }; @@ -69,9 +70,26 @@ where } fn obj_paint(&mut self) -> bool { - let will_paint = self.inner().will_paint(); - self.paint(); - will_paint + #[cfg(not(feature = "new_rendering"))] + let legacy_mode = true; + + #[cfg(feature = "new_rendering")] + let legacy_mode = false; + + if legacy_mode { + let will_paint = self.inner().will_paint(); + self.paint(); + will_paint + } else { + let will_paint = self.inner().will_paint(); + if will_paint { + render_on_display(None, Some(Color::black()), |target| { + self.render(target); + }); + self.skip_paint(); + } + will_paint + } } #[cfg(feature = "ui_bounds")] diff --git a/core/embed/rust/src/ui/layout/simplified.rs b/core/embed/rust/src/ui/layout/simplified.rs index 412d64444..67c708942 100644 --- a/core/embed/rust/src/ui/layout/simplified.rs +++ b/core/embed/rust/src/ui/layout/simplified.rs @@ -12,8 +12,12 @@ use crate::ui::{ ui_features::ModelUI, UIFeaturesCommon, }; + use num_traits::ToPrimitive; +#[cfg(feature = "new_rendering")] +use crate::ui::{display::color::Color, shape::render_on_display}; + pub trait ReturnToC { fn return_to_c(self) -> u32; } @@ -63,6 +67,27 @@ fn touch_eval() -> Option { TouchEvent::new(event_type, ex as _, ey as _).ok() } +fn render(frame: &mut F) +where + F: Component, +{ + #[cfg(not(feature = "new_rendering"))] + { + display::sync(); + frame.paint(); + display::refresh(); + } + + #[cfg(feature = "new_rendering")] + { + display::sync(); + render_on_display(None, Some(Color::black()), |target| { + frame.render(target); + }); + display::refresh(); + } +} + pub fn run(frame: &mut F) -> u32 where F: Component, @@ -70,9 +95,7 @@ where { frame.place(ModelUI::SCREEN); ModelUI::fadeout(); - display::sync(); - frame.paint(); - display::refresh(); + render(frame); ModelUI::fadein(); #[cfg(feature = "button")] @@ -93,9 +116,7 @@ where if let Some(message) = msg { return message.return_to_c(); } - display::sync(); - frame.paint(); - display::refresh(); + render(frame); } } } @@ -105,12 +126,13 @@ where F: Component, { frame.place(ModelUI::SCREEN); + if fading { ModelUI::fadeout() }; - display::sync(); - frame.paint(); - display::refresh(); + + render(frame); + if fading { ModelUI::fadein() }; diff --git a/core/embed/rust/src/ui/model_mercury/bootloader/intro.rs b/core/embed/rust/src/ui/model_mercury/bootloader/intro.rs index 91c8d9534..1057a732e 100644 --- a/core/embed/rust/src/ui/model_mercury/bootloader/intro.rs +++ b/core/embed/rust/src/ui/model_mercury/bootloader/intro.rs @@ -5,6 +5,7 @@ use crate::{ constant::screen, display::Icon, geometry::{Alignment, Insets, Point, Rect}, + shape::Renderer, }, }; @@ -106,6 +107,15 @@ impl<'a> Component for Intro<'a> { self.menu.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + self.title.render(target); + self.text.render(target); + self.warn.render(target); + self.host.render(target); + self.menu.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.menu.bounds(sink); diff --git a/core/embed/rust/src/ui/model_mercury/bootloader/menu.rs b/core/embed/rust/src/ui/model_mercury/bootloader/menu.rs index ca4b3b94a..9ffccbc56 100644 --- a/core/embed/rust/src/ui/model_mercury/bootloader/menu.rs +++ b/core/embed/rust/src/ui/model_mercury/bootloader/menu.rs @@ -5,6 +5,7 @@ use crate::{ constant::{screen, WIDTH}, display::Icon, geometry::{Insets, Point, Rect}, + shape::Renderer, }, }; @@ -108,6 +109,14 @@ impl Component for Menu { self.reset.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + self.title.render(target); + self.close.render(target); + self.reboot.render(target); + self.reset.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.close.bounds(sink); diff --git a/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs b/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs index f65f726f5..9f0d34f0a 100644 --- a/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs @@ -23,12 +23,31 @@ use super::{ FIRE40, RESULT_FW_INSTALL, RESULT_WIPE, TEXT_BOLD, TEXT_NORMAL, TEXT_WIPE_BOLD, TEXT_WIPE_NORMAL, WARNING40, WELCOME_COLOR, X24, }, - BACKLIGHT_NORMAL, BLACK, GREEN_LIGHT, GREY, WHITE, + BACKLIGHT_NORMAL, GREEN_LIGHT, GREY, }, ModelMercuryFeatures, }; use crate::ui::{ui_features::UIFeaturesBootloader, UIFeaturesCommon}; + +#[cfg(not(feature = "new_rendering"))] +use super::theme::BLACK; + +#[cfg(feature = "new_rendering")] +use crate::ui::{ + constant, + display::toif::Toif, + geometry::{Alignment, Alignment2D}, + shape, + shape::render_on_display, + util::version_split, +}; + +#[cfg(feature = "new_rendering")] +use ufmt::uwrite; + +#[cfg(feature = "new_rendering")] +use super::theme::bootloader::BLD_WARN_COLOR; use intro::Intro; use menu::Menu; @@ -44,6 +63,7 @@ const SCREEN: Rect = ModelMercuryFeatures::SCREEN; const PROGRESS_TEXT_ORIGIN: Point = Point::new(2, 28); impl ModelMercuryFeatures { + #[cfg(not(feature = "new_rendering"))] fn screen_progress( text: &str, progress: u16, @@ -77,6 +97,70 @@ impl ModelMercuryFeatures { Self::fadein(); } } + + #[cfg(feature = "new_rendering")] + fn screen_progress( + text: &str, + progress: u16, + initialize: bool, + fg_color: Color, + bg_color: Color, + icon: Option<(Icon, Color)>, + center_text: Option<&str>, + ) { + if initialize { + Self::fadeout(); + } + display::sync(); + + render_on_display(None, Some(bg_color), |target| { + shape::Text::new(PROGRESS_TEXT_ORIGIN, text) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) + .render(target); + + let loader_offset: i16 = 19; + let center_text_offset: i16 = 10; + let center = SCREEN.center() + Offset::y(-loader_offset); + let inactive_color = bg_color.blend(fg_color, 85); + + shape::Circle::new(center, constant::LOADER_OUTER) + .with_bg(inactive_color) + .render(target); + + shape::Circle::new(center, constant::LOADER_OUTER) + .with_bg(fg_color) + .with_end_angle(((progress as i32 * shape::PI4 as i32 * 8) / 1000) as i16) + .render(target); + + shape::Circle::new(center, constant::LOADER_INNER) + .with_bg(bg_color) + .render(target); + + if let Some((icon, color)) = icon { + shape::ToifImage::new(center, icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(color) + .render(target); + } + + if let Some(center_text) = center_text { + shape::Text::new( + SCREEN.center() + Offset::y(loader_offset + center_text_offset), + center_text, + ) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(GREY) + .render(target); + } + }); + + display::refresh(); + if initialize { + Self::fadein(); + } + } } impl UIFeaturesBootloader for ModelMercuryFeatures { @@ -90,7 +174,7 @@ impl UIFeaturesBootloader for ModelMercuryFeatures { Point::new(SCREEN.width() / 2, SCREEN.height() - 5), "click to continue ...", Font::NORMAL, - WHITE, + BLD_FG, bg_color, ); } @@ -127,8 +211,6 @@ impl UIFeaturesBootloader for ModelMercuryFeatures { None, ); } - - display::refresh(); } fn screen_install_fail() { @@ -252,6 +334,7 @@ impl UIFeaturesBootloader for ModelMercuryFeatures { Self::fadeout(); } + #[cfg(not(feature = "new_rendering"))] display::rect_fill(SCREEN, BLACK); let mut frame = WelcomeScreen::new(); @@ -262,7 +345,6 @@ impl UIFeaturesBootloader for ModelMercuryFeatures { } else { display::set_backlight(BACKLIGHT_NORMAL); } - display::refresh(); } fn screen_wipe_progress(progress: u16, initialize: bool) { @@ -322,4 +404,90 @@ impl UIFeaturesBootloader for ModelMercuryFeatures { ); show(&mut frame, true); } + + #[cfg(feature = "new_rendering")] + fn screen_boot( + warning: bool, + vendor_str: Option<&str>, + version: u32, + vendor_img: &[u8], + wait: i32, + ) { + let bg_color = if warning { BLD_WARN_COLOR } else { BLD_BG }; + + display::sync(); + + render_on_display(None, Some(bg_color), |target| { + // Draw vendor image if it's valid and has size of 120x120 + if let Ok(toif) = Toif::new(vendor_img) { + if (toif.width() == 120) && (toif.height() == 120) { + // Image position depends on the vendor string presence + let pos = if vendor_str.is_some() { + Point::new(SCREEN.width() / 2, 30) + } else { + Point::new(SCREEN.width() / 2, 60) + }; + + shape::ToifImage::new(pos, toif) + .with_align(Alignment2D::TOP_CENTER) + .with_fg(BLD_FG) + .render(target); + } + } + + // Draw vendor string if present + if let Some(text) = vendor_str { + let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5 - 50); + shape::Text::new(pos, text) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) //COLOR_BL_BG + .render(target); + + let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5 - 25); + + let mut version_text: BootloaderString = String::new(); + let ver_nums = version_split(version); + unwrap!(uwrite!( + version_text, + "{}.{}.{}", + ver_nums[0], + ver_nums[1], + ver_nums[2] + )); + + shape::Text::new(pos, version_text.as_str()) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) + .render(target); + } + + // Draw a message + match wait.cmp(&0) { + core::cmp::Ordering::Equal => {} + core::cmp::Ordering::Greater => { + let mut text: BootloaderString = String::new(); + unwrap!(uwrite!(text, "starting in {} s", wait)); + + let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5); + shape::Text::new(pos, text.as_str()) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) + .render(target); + } + core::cmp::Ordering::Less => { + let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5); + shape::Text::new(pos, "click to continue ...") + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) + .render(target); + } + } + }); + + display::refresh(); + } } diff --git a/core/embed/rust/src/ui/model_mercury/bootloader/welcome.rs b/core/embed/rust/src/ui/model_mercury/bootloader/welcome.rs index 0e0321fd2..390045916 100644 --- a/core/embed/rust/src/ui/model_mercury/bootloader/welcome.rs +++ b/core/embed/rust/src/ui/model_mercury/bootloader/welcome.rs @@ -3,6 +3,8 @@ use crate::ui::{ constant::screen, display::{self, Font}, geometry::{Offset, Point, Rect}, + shape, + shape::Renderer, }; use super::super::theme::{BLACK, GREY, WHITE}; @@ -61,4 +63,33 @@ impl Component for Welcome { BLACK, ); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + + shape::Text::new(TEXT_ORIGIN, "Get started") + .with_font(Font::NORMAL) + .with_fg(GREY) + .render(target); + + shape::Text::new(TEXT_ORIGIN + Offset::y(STRIDE), "with your Trezor") + .with_font(Font::NORMAL) + .with_fg(GREY) + .render(target); + + shape::Text::new(TEXT_ORIGIN + Offset::y(2 * STRIDE), "at") + .with_font(Font::NORMAL) + .with_fg(GREY) + .render(target); + + let at_width = Font::NORMAL.text_width("at "); + + shape::Text::new( + TEXT_ORIGIN + Offset::new(at_width, 2 * STRIDE), + "trezor.io/start", + ) + .with_font(Font::NORMAL) + .with_fg(WHITE) + .render(target); + } } diff --git a/core/embed/rust/src/ui/model_mercury/component/bl_confirm.rs b/core/embed/rust/src/ui/model_mercury/component/bl_confirm.rs index 6e33e5dc5..75ab74098 100644 --- a/core/embed/rust/src/ui/model_mercury/component/bl_confirm.rs +++ b/core/embed/rust/src/ui/model_mercury/component/bl_confirm.rs @@ -6,6 +6,8 @@ use crate::{ constant::screen, display::{Color, Icon}, geometry::{Alignment2D, Insets, Offset, Point, Rect}, + shape, + shape::Renderer, }, }; @@ -239,6 +241,40 @@ impl Component for Confirm<'_> { } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + self.content_pad.render(target); + + if let Some(info) = self.info.as_ref() { + if self.show_info { + info.close_button.render(target); + info.title.render(target); + info.text.render(target); + self.left_button.render(target); + self.right_button.render(target); + // short-circuit before painting the main components + return; + } else { + info.info_button.render(target); + // pass through to the rest of the paint + } + } + + self.message.render(target); + self.alert.render(target); + self.left_button.render(target); + self.right_button.render(target); + match &self.title { + ConfirmTitle::Text(label) => label.render(target), + ConfirmTitle::Icon(icon) => { + shape::ToifImage::new(Point::new(screen().center().x, ICON_TOP), icon.toif) + .with_align(Alignment2D::TOP_CENTER) + .with_fg(WHITE) + .render(target); + } + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.left_button.bounds(sink); diff --git a/core/embed/rust/src/ui/model_mercury/component/button.rs b/core/embed/rust/src/ui/model_mercury/component/button.rs index 747f994f6..fa77f1909 100644 --- a/core/embed/rust/src/ui/model_mercury/component/button.rs +++ b/core/embed/rust/src/ui/model_mercury/component/button.rs @@ -8,6 +8,8 @@ use crate::{ display::{self, toif::Icon, Color, Font}, event::TouchEvent, geometry::{Alignment2D, Insets, Offset, Point, Rect}, + shape, + shape::Renderer, }, }; @@ -185,6 +187,18 @@ impl Button { } } + pub fn render_background<'s>(&self, target: &mut impl Renderer<'s>, style: &ButtonStyle) { + match &self.content { + ButtonContent::IconBlend(_, _, _) => {} + _ => shape::Bar::new(self.area) + .with_bg(style.button_color) + .with_fg(style.border_color) + .with_thickness(style.border_width) + .with_radius(style.border_radius as i16) + .render(target), + } + } + pub fn paint_content(&self, style: &ButtonStyle) { match &self.content { ButtonContent::Empty => {} @@ -223,6 +237,45 @@ impl Button { ), } } + + pub fn render_content<'s>(&self, target: &mut impl Renderer<'s>, style: &ButtonStyle) { + match &self.content { + ButtonContent::Empty => {} + ButtonContent::Text(text) => { + let width = text.map(|c| style.font.text_width(c)); + let height = style.font.text_height(); + let start_of_baseline = self.area.center() + + Offset::new(-width / 2, height / 2) + + Offset::y(Self::BASELINE_OFFSET); + text.map(|text| { + shape::Text::new(start_of_baseline, text) + .with_font(style.font) + .with_fg(style.text_color) + .render(target); + }); + } + ButtonContent::Icon(icon) => { + shape::ToifImage::new(self.area.center(), icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(style.text_color) + .render(target); + } + ButtonContent::IconAndText(child) => { + child.render(target, self.area, self.style(), Self::BASELINE_OFFSET); + } + ButtonContent::IconBlend(bg, fg, offset) => { + shape::Bar::new(self.area) + .with_bg(style.background_color) + .render(target); + shape::ToifImage::new(self.area.top_left(), bg.toif) + .with_fg(style.button_color) + .render(target); + shape::ToifImage::new(self.area.top_left() + *offset, fg.toif) + .with_fg(style.text_color) + .render(target); + } + } + } } impl Component for Button { @@ -311,6 +364,12 @@ impl Component for Button { self.paint_content(style); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let style = self.style(); + self.render_background(target, style); + self.render_content(target, style); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.area); @@ -437,4 +496,52 @@ impl IconText { ); } } + + pub fn render<'s>( + &self, + target: &mut impl Renderer<'s>, + area: Rect, + style: &ButtonStyle, + baseline_offset: i16, + ) { + let width = style.font.text_width(self.text); + let height = style.font.text_height(); + + let mut use_icon = false; + let mut use_text = false; + + let mut icon_pos = Point::new( + area.top_left().x + ((Self::ICON_SPACE + Self::ICON_MARGIN) / 2), + area.center().y, + ); + let mut text_pos = + area.center() + Offset::new(-width / 2, height / 2) + Offset::y(baseline_offset); + + if area.width() > (Self::ICON_SPACE + Self::TEXT_MARGIN + width) { + //display both icon and text + text_pos = Point::new(area.top_left().x + Self::ICON_SPACE, text_pos.y); + use_text = true; + use_icon = true; + } else if area.width() > (width + Self::TEXT_MARGIN) { + use_text = true; + } else { + //if we can't fit the text, retreat to centering the icon + icon_pos = area.center(); + use_icon = true; + } + + if use_text { + shape::Text::new(text_pos, self.text) + .with_font(style.font) + .with_fg(style.text_color) + .render(target); + } + + if use_icon { + shape::ToifImage::new(icon_pos, self.icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(style.text_color) + .render(target); + } + } } diff --git a/core/embed/rust/src/ui/model_mercury/component/error.rs b/core/embed/rust/src/ui/model_mercury/component/error.rs index 7e046d722..0999a0654 100644 --- a/core/embed/rust/src/ui/model_mercury/component/error.rs +++ b/core/embed/rust/src/ui/model_mercury/component/error.rs @@ -4,6 +4,8 @@ use crate::{ component::{Child, Component, Event, EventCtx, Label, Never, Pad}, constant::screen, geometry::{Alignment2D, Point, Rect}, + shape, + shape::Renderer, }, }; @@ -89,4 +91,19 @@ impl<'a> Component for ErrorScreen<'a> { self.message.paint(); self.footer.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + + let icon = ICON_WARNING40; + shape::ToifImage::new(Point::new(screen().center().x, ICON_TOP), icon.toif) + .with_fg(WHITE) + .with_bg(FATAL_ERROR_COLOR) + .with_align(Alignment2D::TOP_CENTER) + .render(target); + + self.title.render(target); + self.message.render(target); + self.footer.render(target); + } } diff --git a/core/embed/rust/src/ui/model_mercury/component/frame.rs b/core/embed/rust/src/ui/model_mercury/component/frame.rs index b29f29fe3..e5c549101 100644 --- a/core/embed/rust/src/ui/model_mercury/component/frame.rs +++ b/core/embed/rust/src/ui/model_mercury/component/frame.rs @@ -7,6 +7,7 @@ use crate::{ }, display::Icon, geometry::{Alignment, Insets, Offset, Rect}, + shape::Renderer, }, }; @@ -175,6 +176,12 @@ where self.button.paint(); self.content.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.title.render(target); + self.subtitle.render(target); + self.button.render(target); + self.content.render(target); + } #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { diff --git a/core/embed/rust/src/ui/model_mercury/component/loader.rs b/core/embed/rust/src/ui/model_mercury/component/loader.rs index 8d45cb48e..68ac60933 100644 --- a/core/embed/rust/src/ui/model_mercury/component/loader.rs +++ b/core/embed/rust/src/ui/model_mercury/component/loader.rs @@ -6,7 +6,8 @@ use crate::{ animation::Animation, component::{Component, Event, EventCtx, Pad}, display::{self, toif::Icon, Color}, - geometry::{Offset, Rect}, + geometry::{Alignment2D, Offset, Rect}, + shape::{self, Renderer}, util::animation_disabled, }, }; @@ -205,6 +206,49 @@ impl Component for Loader { ); } } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + // TODO: Consider passing the current instant along with the event -- that way, + // we could synchronize painting across the component tree. Also could be useful + // in automated tests. + // In practice, taking the current instant here is more precise in case some + // other component in the tree takes a long time to draw. + let now = Instant::now(); + + if let Some(progress) = self.progress(now) { + let style = if progress < display::LOADER_MAX { + self.styles.normal + } else { + self.styles.active + }; + + self.pad.render(target); + + let center = self.pad.area.center(); + + let inactive_color = Color::black().blend(style.loader_color, 85); + + shape::Circle::new(center, constant::LOADER_OUTER) + .with_bg(inactive_color) + .render(target); + + shape::Circle::new(center, constant::LOADER_OUTER) + .with_bg(style.loader_color) + .with_end_angle(((progress as i32 * shape::PI4 as i32 * 8) / 1000) as i16) + .render(target); + + shape::Circle::new(center, constant::LOADER_INNER) + .with_bg(style.background_color) + .render(target); + + if let Some((icon, color)) = style.icon { + shape::ToifImage::new(center, icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(color) + .render(target); + } + } + } } pub struct LoaderStyleSheet { diff --git a/core/embed/rust/src/ui/model_mercury/component/result.rs b/core/embed/rust/src/ui/model_mercury/component/result.rs index 5ae92c229..f5e619f18 100644 --- a/core/embed/rust/src/ui/model_mercury/component/result.rs +++ b/core/embed/rust/src/ui/model_mercury/component/result.rs @@ -5,6 +5,8 @@ use crate::{ constant::screen, display::{self, Color, Font, Icon}, geometry::{Alignment2D, Insets, Offset, Point, Rect}, + shape, + shape::Renderer, }, }; @@ -94,6 +96,20 @@ impl Component for ResultFooter<'_> { // footer text self.text.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + // divider line + let bar = Rect::from_center_and_size( + Point::new(self.area.center().x, self.area.y0), + Offset::new(self.area.width(), 1), + ); + shape::Bar::new(bar) + .with_fg(self.style.divider_color) + .render(target); + + // footer text + self.text.render(target); + } } pub struct ResultScreen<'a> { @@ -165,4 +181,21 @@ impl<'a> Component for ResultScreen<'a> { self.message.paint(); self.footer.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + self.footer_pad.render(target); + + shape::ToifImage::new( + Point::new(screen().center().x, ICON_CENTER_Y), + self.icon.toif, + ) + .with_align(Alignment2D::CENTER) + .with_fg(self.style.fg_color) + .with_bg(self.style.bg_color) + .render(target); + + self.message.render(target); + self.footer.render(target); + } } diff --git a/core/embed/rust/src/ui/model_mercury/component/welcome_screen.rs b/core/embed/rust/src/ui/model_mercury/component/welcome_screen.rs index 83fc452af..bfa8b703d 100644 --- a/core/embed/rust/src/ui/model_mercury/component/welcome_screen.rs +++ b/core/embed/rust/src/ui/model_mercury/component/welcome_screen.rs @@ -1,7 +1,10 @@ use crate::ui::{ component::{Component, Event, EventCtx, Never}, display, - geometry::{Alignment2D, Offset, Rect}, + display::font::Font, + geometry::{Alignment, Alignment2D, Offset, Rect}, + shape, + shape::Renderer, }; use super::theme; @@ -50,6 +53,26 @@ impl Component for WelcomeScreen { theme::BG, ); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + shape::ToifImage::new( + self.area.top_center() + Offset::y(ICON_TOP_MARGIN), + theme::ICON_LOGO.toif, + ) + .with_align(Alignment2D::TOP_CENTER) + .with_fg(theme::FG) + .with_bg(theme::BG) + .render(target); + + shape::Text::new( + self.area.bottom_center() - Offset::y(TEXT_BOTTOM_MARGIN), + "Trezor Safe 5", + ) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(theme::FG) + .render(target); + } } #[cfg(feature = "ui_debug")] diff --git a/core/embed/rust/src/ui/model_mercury/screens.rs b/core/embed/rust/src/ui/model_mercury/screens.rs index 5ccc874db..2bb6b3675 100644 --- a/core/embed/rust/src/ui/model_mercury/screens.rs +++ b/core/embed/rust/src/ui/model_mercury/screens.rs @@ -5,17 +5,37 @@ use super::{ constant, }; +#[cfg(feature = "new_rendering")] +use crate::ui::{display::Color, shape::render_on_display}; + pub fn screen_fatal_error(title: &str, msg: &str, footer: &str) { let mut frame = ErrorScreen::new(title.into(), msg.into(), footer.into()); frame.place(constant::screen()); + + #[cfg(feature = "new_rendering")] + render_on_display(None, Some(Color::black()), |target| { + frame.render(target); + }); + + #[cfg(not(feature = "new_rendering"))] frame.paint(); + display::refresh(); } pub fn screen_boot_stage_2() { let mut frame = WelcomeScreen::new(); frame.place(screen()); + display::sync(); + + #[cfg(feature = "new_rendering")] + render_on_display(None, Some(Color::black()), |target| { + frame.render(target); + }); + + #[cfg(not(feature = "new_rendering"))] frame.paint(); + display::refresh(); } diff --git a/core/embed/rust/src/ui/model_tr/bootloader/intro.rs b/core/embed/rust/src/ui/model_tr/bootloader/intro.rs index 3cb0e4637..cf323dac4 100644 --- a/core/embed/rust/src/ui/model_tr/bootloader/intro.rs +++ b/core/embed/rust/src/ui/model_tr/bootloader/intro.rs @@ -4,6 +4,8 @@ use crate::{ component::{Child, Component, Event, EventCtx, Label, Pad}, geometry::{Alignment, Alignment2D, Rect}, layout::simplified::ReturnToC, + shape, + shape::Renderer, }, }; @@ -104,6 +106,27 @@ impl<'a> Component for Intro<'a> { self.buttons.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + self.title.render(target); + + let area = self.bg.area; + + shape::ToifImage::new(area.top_left(), ICON_WARN_TITLE.toif) + .with_align(Alignment2D::TOP_LEFT) + .with_fg(BLD_FG) + .render(target); + + shape::ToifImage::new(area.top_left(), ICON_WARN_TITLE.toif) + .with_align(Alignment2D::TOP_RIGHT) + .with_fg(BLD_FG) + .render(target); + + self.warn.render(target); + self.text.render(target); + self.buttons.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.title.bounds(sink); diff --git a/core/embed/rust/src/ui/model_tr/bootloader/menu.rs b/core/embed/rust/src/ui/model_tr/bootloader/menu.rs index d0a5fa0e1..6ba41fb6e 100644 --- a/core/embed/rust/src/ui/model_tr/bootloader/menu.rs +++ b/core/embed/rust/src/ui/model_tr/bootloader/menu.rs @@ -7,8 +7,10 @@ use crate::{ constant::screen, display, display::{Font, Icon}, - geometry::{Alignment2D, Offset, Point, Rect}, + geometry::{Alignment, Alignment2D, Offset, Point, Rect}, layout::simplified::ReturnToC, + shape, + shape::Renderer, }, }; @@ -69,6 +71,26 @@ impl Choice for MenuChoice { ); } + fn render_center<'s>(&self, target: &mut impl Renderer<'s>, _area: Rect, _inverse: bool) { + // Icon on top and two lines of text below + shape::ToifImage::new(SCREEN_CENTER + Offset::y(-20), self.icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(BLD_FG) + .render(target); + + shape::Text::new(SCREEN_CENTER, self.first_line) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) + .render(target); + + shape::Text::new(SCREEN_CENTER + Offset::y(10), self.second_line) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) + .render(target); + } + fn btn_layout(&self) -> ButtonLayout { ButtonLayout::arrow_armed_arrow("SELECT".into()) } @@ -162,6 +184,11 @@ impl Component for Menu { self.choice_page.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.pad.render(target); + self.choice_page.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.choice_page.bounds(sink) diff --git a/core/embed/rust/src/ui/model_tr/bootloader/mod.rs b/core/embed/rust/src/ui/model_tr/bootloader/mod.rs index 93cb79e3c..cced3d029 100644 --- a/core/embed/rust/src/ui/model_tr/bootloader/mod.rs +++ b/core/embed/rust/src/ui/model_tr/bootloader/mod.rs @@ -7,7 +7,7 @@ use crate::{ constant, constant::{HEIGHT, SCREEN}, display::{self, Color, Font, Icon}, - geometry::{Alignment2D, Offset, Point, Rect}, + geometry::{Alignment2D, Offset, Point}, layout::simplified::{run, show, ReturnToC}, }, }; @@ -24,6 +24,18 @@ use super::{ ModelTRFeatures, }; +#[cfg(not(feature = "new_rendering"))] +use crate::ui::geometry::Rect; + +#[cfg(feature = "new_rendering")] +use crate::ui::{ + display::toif::Toif, geometry::Alignment, model_tr::cshape, shape, shape::render_on_display, + util::version_split, +}; + +#[cfg(feature = "new_rendering")] +use ufmt::uwrite; + mod intro; mod menu; mod welcome; @@ -42,6 +54,7 @@ impl ReturnToC for ConfirmMsg { } impl ModelTRFeatures { + #[cfg(not(feature = "new_rendering"))] fn screen_progress( text: &str, text2: &str, @@ -84,6 +97,50 @@ impl ModelTRFeatures { display::refresh(); } + + #[cfg(feature = "new_rendering")] + fn screen_progress( + text: &str, + text2: &str, + progress: u16, + _initialize: bool, + fg_color: Color, + bg_color: Color, + icon: Option<(Icon, Color)>, + ) { + let progress = if progress < 20 { 20 } else { progress }; + + display::sync(); + + render_on_display(None, Some(bg_color), |target| { + let center = SCREEN.top_center() + Offset::y(12); + + cshape::LoaderCircular::new(center, progress) + .with_color(fg_color) + .render(target); + + if let Some((icon, color)) = icon { + shape::ToifImage::new(center, icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(color) + .render(target); + } + + shape::Text::new(SCREEN.center() + Offset::y(8), text) + .with_align(Alignment::Center) + .with_font(Font::BOLD) + .with_fg(fg_color) + .render(target); + + shape::Text::new(SCREEN.center() + Offset::y(20), text2) + .with_align(Alignment::Center) + .with_font(Font::BOLD) + .with_fg(fg_color) + .render(target); + }); + + display::refresh(); + } } impl UIFeaturesBootloader for ModelTRFeatures { @@ -261,6 +318,7 @@ impl UIFeaturesBootloader for ModelTRFeatures { } fn screen_boot_stage_1(_fading: bool) { + #[cfg(not(feature = "new_rendering"))] display::rect_fill(SCREEN, BLD_BG); let mut frame = WelcomeScreen::new(true); @@ -315,4 +373,93 @@ impl UIFeaturesBootloader for ModelTRFeatures { let mut frame = ResultScreen::new(BLD_FG, BLD_BG, ICON_ALERT, title, content, true); show(&mut frame, false); } + + #[cfg(feature = "new_rendering")] + fn screen_boot( + _warning: bool, + vendor_str: Option<&str>, + version: u32, + vendor_img: &[u8], + wait: i32, + ) { + display::sync(); + + render_on_display(None, Some(BLD_BG), |target| { + // Draw vendor image if it's valid and has size of 24x24 + if let Ok(toif) = Toif::new(vendor_img) { + if (toif.width() == 24) && (toif.height() == 24) { + let pos = Point::new((constant::WIDTH - 22) / 2, 0); + shape::ToifImage::new(pos, toif) + .with_align(Alignment2D::TOP_CENTER) + .with_fg(BLD_FG) + .render(target); + } + } + + // Draw vendor string if present + if let Some(text) = vendor_str { + let pos = Point::new(constant::WIDTH / 2, 36); + shape::Text::new(pos, text) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) //COLOR_BL_BG + .render(target); + + let pos = Point::new(constant::WIDTH / 2, 46); + + let mut version_text: BootloaderString = String::new(); + let ver_nums = version_split(version); + unwrap!(uwrite!( + version_text, + "{}.{}.{}", + ver_nums[0], + ver_nums[1], + ver_nums[2] + )); + + shape::Text::new(pos, version_text.as_str()) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) + .render(target); + } + + // Draw a message + match wait.cmp(&0) { + core::cmp::Ordering::Equal => {} + core::cmp::Ordering::Greater => { + let mut text: BootloaderString = String::new(); + unwrap!(uwrite!(text, "starting in {} s", wait)); + + let pos = Point::new(constant::WIDTH / 2, HEIGHT - 5); + shape::Text::new(pos, text.as_str()) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) + .render(target); + } + core::cmp::Ordering::Less => { + let pos = Point::new(constant::WIDTH / 2, HEIGHT - 2); + shape::Text::new(pos, "CONTINUE") + .with_align(Alignment::Center) + .with_fg(BLD_FG) + .render(target); + + let pos = Point::new(constant::WIDTH / 2 - 36, HEIGHT - 6); + shape::ToifImage::new(pos, ICON_ARM_LEFT.toif) + .with_align(Alignment2D::TOP_LEFT) + .with_fg(BLD_FG) + .render(target); + + let pos = Point::new(constant::WIDTH / 2 + 25, HEIGHT - 6); + shape::ToifImage::new(pos, ICON_ARM_RIGHT.toif) + .with_align(Alignment2D::TOP_LEFT) + .with_fg(BLD_FG) + .render(target); + } + } + }); + + display::refresh(); + } } diff --git a/core/embed/rust/src/ui/model_tr/bootloader/welcome.rs b/core/embed/rust/src/ui/model_tr/bootloader/welcome.rs index 0a5e03a29..ad261e3b1 100644 --- a/core/embed/rust/src/ui/model_tr/bootloader/welcome.rs +++ b/core/embed/rust/src/ui/model_tr/bootloader/welcome.rs @@ -1,7 +1,9 @@ use crate::ui::{ component::{Component, Event, EventCtx, Never, Pad}, display::{self, Font}, - geometry::{Offset, Rect}, + geometry::{Alignment, Offset, Rect}, + shape, + shape::Renderer, }; use super::super::theme::bootloader::{BLD_BG, BLD_FG}; @@ -57,4 +59,28 @@ impl Component for Welcome { BLD_BG, ); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + + let top_center = self.bg.area.top_center(); + + shape::Text::new(top_center + Offset::y(24), "Get started with") + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) + .render(target); + + shape::Text::new(top_center + Offset::y(32), "your Trezor at") + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) + .render(target); + + shape::Text::new(top_center + Offset::y(48), "trezor.io/start") + .with_align(Alignment::Center) + .with_font(Font::BOLD) + .with_fg(BLD_FG) + .render(target); + } } diff --git a/core/embed/rust/src/ui/model_tr/component/address_details.rs b/core/embed/rust/src/ui/model_tr/component/address_details.rs index 6ba2afd77..0165bb9a7 100644 --- a/core/embed/rust/src/ui/model_tr/component/address_details.rs +++ b/core/embed/rust/src/ui/model_tr/component/address_details.rs @@ -10,6 +10,7 @@ use crate::{ Child, Component, Event, EventCtx, Pad, Paginate, Qr, }, geometry::Rect, + shape::Renderer, }, }; @@ -259,6 +260,16 @@ impl Component for AddressDetails { } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.pad.render(target); + self.buttons.render(target); + match self.current_page { + 0 => self.qr_code.render(target), + 1 => self.details_view.render(target), + _ => self.xpub_view.render(target), + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.area) diff --git a/core/embed/rust/src/ui/model_tr/component/bl_confirm.rs b/core/embed/rust/src/ui/model_tr/component/bl_confirm.rs index 85e589b73..842c2ce22 100644 --- a/core/embed/rust/src/ui/model_tr/component/bl_confirm.rs +++ b/core/embed/rust/src/ui/model_tr/component/bl_confirm.rs @@ -4,6 +4,8 @@ use crate::{ component::{Child, Component, ComponentExt, Event, EventCtx, Label, Pad}, display::{self, Color, Font}, geometry::{Point, Rect}, + shape, + shape::Renderer, }, }; @@ -216,6 +218,32 @@ impl Component for Confirm<'_> { self.buttons.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + + let mut display_top_left = |text: TString| { + text.map(|t| { + shape::Text::new(Point::zero(), t) + .with_font(Font::BOLD) + .with_fg(WHITE) + .render(target); + }); + }; + + // We are either on the info screen or on the "main" screen + if self.showing_info_screen { + if let Some(title) = self.info_title { + display_top_left(title); + } + self.info_text.render(target); + } else { + display_top_left(self.title); + self.message.render(target); + self.alert.render(target); + } + self.buttons.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.buttons.bounds(sink); diff --git a/core/embed/rust/src/ui/model_tr/component/button.rs b/core/embed/rust/src/ui/model_tr/component/button.rs index 04d7762db..329d202e8 100644 --- a/core/embed/rust/src/ui/model_tr/component/button.rs +++ b/core/embed/rust/src/ui/model_tr/component/button.rs @@ -7,6 +7,8 @@ use crate::{ display::{self, Color, Font, Icon}, event::PhysicalButton, geometry::{Alignment2D, Offset, Point, Rect}, + shape, + shape::Renderer, }, }; @@ -265,6 +267,88 @@ impl Component for Button { } } } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let style = self.style(); + let fg_color = style.text_color; + let bg_color = fg_color.negate(); + let area = self.get_current_area(); + let inversed_colors = bg_color != theme::BG; + + // Filling the background (with 2-pixel rounding when applicable) + if inversed_colors { + shape::Bar::new(area) + .with_radius(3) + .with_bg(bg_color) + .render(target); + } else if style.with_outline { + shape::Bar::new(area) + .with_radius(3) + .with_fg(fg_color) + .render(target); + } else { + shape::Bar::new(area).with_bg(bg_color).render(target); + } + + // Optionally display "arms" at both sides of content - always in FG and BG + // colors (they are not inverted). + if style.with_arms { + shape::ToifImage::new(area.left_center(), theme::ICON_ARM_LEFT.toif) + .with_align(Alignment2D::TOP_RIGHT) + .with_fg(theme::FG) + .render(target); + + shape::ToifImage::new(area.right_center(), theme::ICON_ARM_RIGHT.toif) + .with_align(Alignment2D::TOP_LEFT) + .with_fg(theme::FG) + .render(target); + } + + // Painting the content + match &self.content { + ButtonContent::Text(text) => text.map(|t| { + shape::Text::new( + self.get_text_baseline(style) - Offset::x(style.font.start_x_bearing(t)), + t, + ) + .with_font(style.font) + .with_fg(fg_color) + .render(target); + }), + ButtonContent::Icon(icon) => { + // Allowing for possible offset of the area from current style + let icon_area = area.translate(style.offset); + if style.with_outline { + shape::ToifImage::new(icon_area.center(), icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(fg_color) + .render(target); + } else { + // Positioning the icon in the corresponding corner/center + match self.pos { + ButtonPos::Left => { + shape::ToifImage::new(icon_area.bottom_left(), icon.toif) + .with_align(Alignment2D::BOTTOM_LEFT) + .with_fg(fg_color) + .render(target) + } + + ButtonPos::Right => { + shape::ToifImage::new(icon_area.bottom_right(), icon.toif) + .with_align(Alignment2D::BOTTOM_RIGHT) + .with_fg(fg_color) + .render(target) + } + + ButtonPos::Middle => shape::ToifImage::new(icon_area.center(), icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(fg_color) + .render(target), + } + } + } + } + } } #[derive(PartialEq, Eq)] diff --git a/core/embed/rust/src/ui/model_tr/component/button_controller.rs b/core/embed/rust/src/ui/model_tr/component/button_controller.rs index 76d053faa..280938879 100644 --- a/core/embed/rust/src/ui/model_tr/component/button_controller.rs +++ b/core/embed/rust/src/ui/model_tr/component/button_controller.rs @@ -7,6 +7,7 @@ use crate::{ component::{base::Event, Component, EventCtx, Pad, TimerToken}, event::{ButtonEvent, PhysicalButton}, geometry::Rect, + shape::Renderer, }, }; @@ -93,6 +94,18 @@ impl ButtonType { Self::Nothing => {} } } + + pub fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + match self { + Self::Button(button) => { + button.render(target); + } + Self::HoldToConfirm(htc) => { + htc.render(target); + } + Self::Nothing => {} + } + } } /// Wrapping a button and its state, so that it can be easily @@ -154,6 +167,10 @@ impl ButtonContainer { self.button_type.paint(); } + pub fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.button_type.render(target); + } + /// Setting the visual state of the button - released/pressed. pub fn set_pressed(&mut self, ctx: &mut EventCtx, is_pressed: bool) { if let ButtonType::Button(btn) = &mut self.button_type { @@ -575,6 +592,13 @@ impl Component for ButtonController { self.right_btn.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.pad.render(target); + self.left_btn.render(target); + self.middle_btn.render(target); + self.right_btn.render(target); + } + fn place(&mut self, bounds: Rect) -> Rect { // Saving button area so that we can re-place the buttons // when they get updated @@ -754,6 +778,8 @@ impl Component for AutomaticMover { fn paint(&mut self) {} + fn render<'s>(&'s self, _target: &mut impl Renderer<'s>) {} + fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option { // Moving automatically only when we receive a TimerToken that we have // requested before diff --git a/core/embed/rust/src/ui/model_tr/component/changing_text.rs b/core/embed/rust/src/ui/model_tr/component/changing_text.rs index 35ec84457..7d9bbb9a0 100644 --- a/core/embed/rust/src/ui/model_tr/component/changing_text.rs +++ b/core/embed/rust/src/ui/model_tr/component/changing_text.rs @@ -2,6 +2,8 @@ use crate::ui::{ component::{Component, Event, EventCtx, Never, Pad}, display::Font, geometry::{Alignment, Point, Rect}, + shape, + shape::Renderer, util::long_line_content_with_ellipsis, }; @@ -109,16 +111,39 @@ where common::display_left(baseline, &self.text, self.font); } + fn render_left<'s>(&'s self, target: &mut impl Renderer<'s>) { + let baseline = Point::new(self.pad.area.x0, self.y_baseline()); + shape::Text::new(baseline, self.text.as_ref()) + .with_font(self.font) + .render(target); + } + fn paint_center(&self) { let baseline = Point::new(self.pad.area.bottom_center().x, self.y_baseline()); common::display_center(baseline, &self.text, self.font); } + fn render_center<'s>(&'s self, target: &mut impl Renderer<'s>) { + let baseline = Point::new(self.pad.area.bottom_center().x, self.y_baseline()); + shape::Text::new(baseline, self.text.as_ref()) + .with_align(Alignment::Center) + .with_font(self.font) + .render(target); + } + fn paint_right(&self) { let baseline = Point::new(self.pad.area.x1, self.y_baseline()); common::display_right(baseline, &self.text, self.font); } + fn render_right<'s>(&'s self, target: &mut impl Renderer<'s>) { + let baseline = Point::new(self.pad.area.x1, self.y_baseline()); + shape::Text::new(baseline, self.text.as_ref()) + .with_align(Alignment::End) + .with_font(self.font) + .render(target); + } + fn paint_long_content_with_ellipsis(&self) { let text_to_display = long_line_content_with_ellipsis( self.text.as_ref(), @@ -175,4 +200,20 @@ where } } } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.pad.render(target); + if self.show_content { + // In the case text cannot fit, show ellipsis and its right part + if !self.text_fits_completely() { + self.paint_long_content_with_ellipsis(); + } else { + match self.alignment { + Alignment::Start => self.render_left(target), + Alignment::Center => self.render_center(target), + Alignment::End => self.render_right(target), + } + } + } + } } diff --git a/core/embed/rust/src/ui/model_tr/component/coinjoin_progress.rs b/core/embed/rust/src/ui/model_tr/component/coinjoin_progress.rs index 1e2194efb..1e3d122f8 100644 --- a/core/embed/rust/src/ui/model_tr/component/coinjoin_progress.rs +++ b/core/embed/rust/src/ui/model_tr/component/coinjoin_progress.rs @@ -6,11 +6,16 @@ use crate::{ ui::{ component::{ base::Never, - text::util::{text_multiline, text_multiline_bottom}, + text::util::{ + text_multiline, text_multiline2, text_multiline_bottom, text_multiline_bottom2, + }, Component, Event, EventCtx, }, display::{self, Font}, - geometry::{Alignment, Insets, Rect}, + geometry::{Alignment, Alignment2D, Insets, Offset, Rect}, + model_tr::cshape, + shape, + shape::Renderer, util::animation_disabled, }, }; @@ -124,6 +129,56 @@ impl Component for CoinJoinProgress { ); } } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + // TOP + let center = self.area.center() + Offset::y(self.loader_y_offset); + + if self.indeterminate { + text_multiline2( + target, + self.area, + TR::coinjoin__title_progress.into(), + Font::BOLD, + theme::FG, + theme::BG, + Alignment::Center, + ); + cshape::LoaderSmall::new(center, self.value) + .with_color(theme::FG) + .render(target); + } else { + cshape::LoaderCircular::new(center, self.value) + .with_color(theme::FG) + .render(target); + shape::ToifImage::new(center, theme::ICON_TICK_FAT.toif) + .with_align(Alignment2D::CENTER) + .with_fg(theme::FG) + .render(target); + } + + // BOTTOM + let top_rest = text_multiline_bottom2( + target, + self.area, + TR::coinjoin__do_not_disconnect.into(), + Font::BOLD, + theme::FG, + theme::BG, + Alignment::Center, + ); + if let Some(rest) = top_rest { + text_multiline_bottom2( + target, + rest.inset(Insets::bottom(FOOTER_TEXT_MARGIN)), + self.text, + Font::NORMAL, + theme::FG, + theme::BG, + Alignment::Center, + ); + } + } } #[cfg(feature = "ui_debug")] diff --git a/core/embed/rust/src/ui/model_tr/component/error.rs b/core/embed/rust/src/ui/model_tr/component/error.rs index 52818a342..9772bc9d8 100644 --- a/core/embed/rust/src/ui/model_tr/component/error.rs +++ b/core/embed/rust/src/ui/model_tr/component/error.rs @@ -5,6 +5,9 @@ use crate::{ constant::{screen, WIDTH}, display, geometry::{Alignment2D, Offset, Point, Rect}, + model_tr::cshape, + shape, + shape::Renderer, }, }; @@ -98,4 +101,29 @@ impl Component for ErrorScreen<'_> { self.footer.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + + if self.show_icons { + shape::ToifImage::new(screen().top_left(), theme::ICON_WARN_TITLE.toif) + .with_align(Alignment2D::TOP_LEFT) + .with_fg(FG) + .render(target); + + shape::ToifImage::new(screen().top_right(), theme::ICON_WARN_TITLE.toif) + .with_align(Alignment2D::TOP_RIGHT) + .with_fg(FG) + .render(target); + } + self.title.render(target); + self.message.render(target); + + cshape::HorizontalLine::new(Point::new(0, DIVIDER_POSITION), WIDTH) + .with_step(3) + .with_color(FG) + .render(target); + + self.footer.render(target); + } } diff --git a/core/embed/rust/src/ui/model_tr/component/flow.rs b/core/embed/rust/src/ui/model_tr/component/flow.rs index 85a1bc8c4..b51209af1 100644 --- a/core/embed/rust/src/ui/model_tr/component/flow.rs +++ b/core/embed/rust/src/ui/model_tr/component/flow.rs @@ -3,6 +3,7 @@ use crate::{ ui::{ component::{Child, Component, ComponentExt, Event, EventCtx, Pad, Paginate}, geometry::Rect, + shape::Renderer, }, }; @@ -305,6 +306,23 @@ where // (and painting buttons last would cover the lower part). self.current_page.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.pad.render(target); + // Scrollbars are painted only with a title and when requested + if self.title.is_some() { + if self.show_scrollbar { + self.scrollbar.render(target); + } + self.title.render(target); + } + self.buttons.render(target); + // On purpose painting current page at the end, after buttons, + // because we sometimes (in the case of QR code) need to use the + // whole height of the display for showing the content + // (and painting buttons last would cover the lower part). + self.current_page.render(target); + } } // DEBUG-ONLY SECTION BELOW diff --git a/core/embed/rust/src/ui/model_tr/component/flow_pages.rs b/core/embed/rust/src/ui/model_tr/component/flow_pages.rs index 5ffd92c03..c143c7423 100644 --- a/core/embed/rust/src/ui/model_tr/component/flow_pages.rs +++ b/core/embed/rust/src/ui/model_tr/component/flow_pages.rs @@ -3,6 +3,7 @@ use crate::{ ui::{ component::{base::Component, FormattedText, Paginate}, geometry::Rect, + shape::Renderer, }, }; @@ -121,6 +122,10 @@ impl Page { self.formatted.paint(); } + pub fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.formatted.render(target); + } + pub fn place(&mut self, bounds: Rect) -> Rect { self.formatted.place(bounds); self.page_count = self.page_count(); diff --git a/core/embed/rust/src/ui/model_tr/component/frame.rs b/core/embed/rust/src/ui/model_tr/component/frame.rs index b9d8d3f7b..f47158d9e 100644 --- a/core/embed/rust/src/ui/model_tr/component/frame.rs +++ b/core/embed/rust/src/ui/model_tr/component/frame.rs @@ -3,6 +3,7 @@ use crate::{ ui::{ component::{Child, Component, ComponentExt, Event, EventCtx, Paginate}, geometry::{Insets, Rect}, + shape::Renderer, }, }; @@ -80,6 +81,11 @@ where self.title.paint(); self.content.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.title.render(target); + self.content.render(target); + } } impl Paginate for Frame @@ -197,6 +203,12 @@ where self.scrollbar.paint(); self.content.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.title.render(target); + self.scrollbar.render(target); + self.content.render(target); + } } // DEBUG-ONLY SECTION BELOW diff --git a/core/embed/rust/src/ui/model_tr/component/hold_to_confirm.rs b/core/embed/rust/src/ui/model_tr/component/hold_to_confirm.rs index 06f652b76..d0afb1e2c 100644 --- a/core/embed/rust/src/ui/model_tr/component/hold_to_confirm.rs +++ b/core/embed/rust/src/ui/model_tr/component/hold_to_confirm.rs @@ -5,6 +5,7 @@ use crate::{ component::{Component, Event, EventCtx}, event::ButtonEvent, geometry::Rect, + shape::Renderer, }, }; @@ -122,6 +123,10 @@ impl Component for HoldToConfirm { fn paint(&mut self) { self.loader.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.loader.render(target); + } } // DEBUG-ONLY SECTION BELOW diff --git a/core/embed/rust/src/ui/model_tr/component/homescreen.rs b/core/embed/rust/src/ui/model_tr/component/homescreen.rs index 31de64e0e..5205961e2 100644 --- a/core/embed/rust/src/ui/model_tr/component/homescreen.rs +++ b/core/embed/rust/src/ui/model_tr/component/homescreen.rs @@ -11,8 +11,10 @@ use crate::{ Font, Icon, }, event::USBEvent, - geometry::{Alignment2D, Insets, Offset, Point, Rect}, + geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect}, layout::util::get_user_custom_image, + shape, + shape::Renderer, }, }; @@ -46,6 +48,16 @@ fn paint_default_image() { ); } +fn render_default_image<'s>(target: &mut impl Renderer<'s>) { + shape::ToifImage::new( + TOP_CENTER + Offset::y(LOGO_ICON_TOP_MARGIN), + theme::ICON_LOGO.toif, + ) + .with_align(Alignment2D::TOP_CENTER) + .with_fg(theme::FG) + .render(target); +} + enum CurrentScreen { EmptyAtStart, Homescreen, @@ -57,6 +69,7 @@ pub struct Homescreen { // always painted, so we need to always paint the label too label: Label<'static>, notification: Option<(TString<'static>, u8)>, + custom_image: Option>, /// Used for HTC functionality to lock device from homescreen invisible_buttons: Child, /// Holds the loader component @@ -81,6 +94,7 @@ impl Homescreen { Self { label: Label::centered(label, theme::TEXT_BIG), notification, + custom_image: get_user_custom_image().ok(), invisible_buttons: Child::new(ButtonController::new(invisible_btn_layout)), loader, show_loader: false, @@ -89,8 +103,8 @@ impl Homescreen { } fn paint_homescreen_image(&self) { - let homescreen_bytes = get_user_custom_image().ok(); - let homescreen = homescreen_bytes + let homescreen = self + .custom_image .as_ref() .and_then(|data| Toif::new(data.as_ref()).ok()) .filter(check_homescreen_format); @@ -101,6 +115,22 @@ impl Homescreen { } } + fn render_homescreen_image<'s>(&'s self, target: &mut impl Renderer<'s>) { + let homescreen = self + .custom_image + .as_ref() + .and_then(|data| Toif::new(data.as_ref()).ok()) + .filter(check_homescreen_format); + if let Some(toif) = homescreen { + shape::ToifImage::new(TOP_CENTER, toif) + .with_align(Alignment2D::TOP_CENTER) + .with_fg(theme::FG) + .render(target); + } else { + render_default_image(target); + } + } + fn paint_notification(&self) { let baseline = TOP_CENTER + Offset::y(NOTIFICATION_FONT.line_height()); if !usb_configured() { @@ -132,6 +162,51 @@ impl Homescreen { } } + fn render_notification<'s>(&'s self, target: &mut impl Renderer<'s>) { + let baseline = TOP_CENTER + Offset::y(NOTIFICATION_FONT.line_height()); + if !usb_configured() { + shape::Bar::new(AREA.split_top(NOTIFICATION_HEIGHT).0) + .with_bg(theme::BG) + .render(target); + + // TODO: fill warning icons here as well? + TR::homescreen__title_no_usb_connection.map_translated(|t| { + shape::Text::new(baseline, t) + .with_align(Alignment::Center) + .with_font(NOTIFICATION_FONT) + .render(target) + }); + } else if let Some((notification, _level)) = &self.notification { + shape::Bar::new(AREA.split_top(NOTIFICATION_HEIGHT).0) + .with_bg(theme::BG) + .render(target); + + notification.map(|c| { + shape::Text::new(baseline, c) + .with_align(Alignment::Center) + .with_font(NOTIFICATION_FONT) + .render(target) + }); + + // Painting warning icons in top corners when the text is short enough not to + // collide with them + let icon_width = NOTIFICATION_ICON.toif.width(); + let text_width = notification.map(|c| NOTIFICATION_FONT.text_width(c)); + if AREA.width() >= text_width + (icon_width + 1) * 2 { + shape::ToifImage::new(AREA.top_left(), NOTIFICATION_ICON.toif) + .with_align(Alignment2D::TOP_LEFT) + .with_fg(theme::FG) + .with_bg(theme::BG) + .render(target); + shape::ToifImage::new(AREA.top_right(), NOTIFICATION_ICON.toif) + .with_align(Alignment2D::TOP_RIGHT) + .with_fg(theme::FG) + .with_bg(theme::BG) + .render(target); + } + } + } + fn paint_label(&mut self) { // paint black background to place the label let mut outset = Insets::uniform(LABEL_OUTSET); @@ -142,6 +217,19 @@ impl Homescreen { self.label.paint(); } + fn render_label<'s>(&'s self, target: &mut impl Renderer<'s>) { + // paint black background to place the label + let mut outset = Insets::uniform(LABEL_OUTSET); + // the margin at top is bigger (caused by text-height vs line-height?) + // compensate by shrinking the outset + outset.top -= 5; + shape::Bar::new(self.label.text_area().outset(outset)) + .with_bg(theme::BG) + .render(target); + + self.label.render(target); + } + /// So that notification is well visible even on homescreen image fn fill_notification_background(&self) { rect_fill(AREA.split_top(NOTIFICATION_HEIGHT).0, theme::BG); @@ -229,6 +317,19 @@ impl Component for Homescreen { self.paint_label(); } } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + // Redraw the whole screen when the screen changes (loader vs homescreen) + if self.show_loader { + self.loader.render(target); + } else { + // Painting the homescreen image first, as the notification and label + // should be "on top of it" + self.render_homescreen_image(target); + self.render_notification(target); + self.render_label(target); + } + } } pub struct Lockscreen<'a> { @@ -301,29 +402,50 @@ impl Component for Lockscreen<'_> { ) } } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + if self.screensaver { + // keep screen blank + return; + } + shape::ToifImage::new( + TOP_CENTER + Offset::y(LOCK_ICON_TOP_MARGIN), + theme::ICON_LOCK.toif, + ) + .with_align(Alignment2D::TOP_CENTER) + .with_fg(theme::FG) + .render(target); + + self.instruction.render(target); + self.label.render(target); + + if let Some(icon) = &self.coinjoin_icon { + shape::ToifImage::new(COINJOIN_CORNER, icon.toif) + .with_align(Alignment2D::TOP_RIGHT) + .with_fg(theme::FG) + .render(target); + } + } } -pub struct ConfirmHomescreen { +pub struct ConfirmHomescreen { title: Child>, - buffer_func: F, + image: Obj, buttons: Child, } -impl ConfirmHomescreen { - pub fn new(title: TString<'static>, buffer_func: F) -> Self { +impl ConfirmHomescreen { + pub fn new(title: TString<'static>, image: Obj) -> Self { let btn_layout = ButtonLayout::cancel_none_text(TR::buttons__change.into()); ConfirmHomescreen { title: Child::new(Label::centered(title, theme::TEXT_BOLD)), - buffer_func, + image, buttons: Child::new(ButtonController::new(btn_layout)), } } } -impl<'a, F> Component for ConfirmHomescreen -where - F: Fn() -> &'a [u8], -{ +impl Component for ConfirmHomescreen { type Msg = CancelConfirmMsg; fn place(&mut self, bounds: Rect) -> Rect { @@ -349,11 +471,13 @@ where fn paint(&mut self) { // Drawing the image full-screen first and then other things on top - let buffer = (self.buffer_func)(); - if buffer.is_empty() { + // SAFETY: We expect no existing mutable reference. Resulting reference is + // discarded before returning to micropython. + let image_data = unwrap!(unsafe { get_buffer(self.image) }); + if image_data.is_empty() { paint_default_image(); } else { - let toif_data = unwrap!(Toif::new(buffer)); + let toif_data = unwrap!(Toif::new(image_data)); toif_data.draw(TOP_CENTER, Alignment2D::TOP_CENTER, theme::FG, theme::BG); }; // Need to make all the title background black, so the title text is well @@ -363,6 +487,32 @@ where self.title.paint(); self.buttons.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + // Drawing the image full-screen first and then other things on top + // SAFETY: We expect no existing mutable reference. Resulting reference is + // discarded before returning to micropython. + let image_data = unwrap!(unsafe { get_buffer(self.image) }); + if image_data.is_empty() { + render_default_image(target); + } else { + let toif_data = unwrap!(Toif::new(image_data)); + shape::ToifImage::new(TOP_CENTER, toif_data) + .with_align(Alignment2D::TOP_CENTER) + .with_fg(theme::FG) + .render(target); + }; + // Need to make all the title background black, so the title text is well + // visible + let title_area = self.title.inner().area(); + + shape::Bar::new(title_area) + .with_bg(theme::BG) + .render(target); + + self.title.render(target); + self.buttons.render(target); + } } pub fn check_homescreen_format(toif: &Toif) -> bool { @@ -388,7 +538,7 @@ impl crate::trace::Trace for Lockscreen<'_> { } #[cfg(feature = "ui_debug")] -impl crate::trace::Trace for ConfirmHomescreen { +impl crate::trace::Trace for ConfirmHomescreen { fn trace(&self, t: &mut dyn crate::trace::Tracer) { t.component("ConfirmHomescreen"); t.child("title", &self.title); diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/choice.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/choice.rs index 71d662ccc..4cd681b77 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/choice.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/choice.rs @@ -1,6 +1,7 @@ use crate::ui::{ component::{Child, Component, Event, EventCtx, Pad}, geometry::{Insets, Offset, Rect}, + shape::Renderer, util::animation_disabled, }; @@ -14,11 +15,17 @@ pub trait Choice { // Only `paint_center` is required, the rest is optional // and therefore has a default implementation. fn paint_center(&self, area: Rect, inverse: bool); + + fn render_center<'s>(&self, target: &mut impl Renderer<'s>, _area: Rect, _inverse: bool); + fn width_center(&self) -> i16 { 0 } fn paint_side(&self, _area: Rect) {} + + fn render_side<'s>(&self, _target: &mut impl Renderer<'s>, _area: Rect) {} + fn width_side(&self) -> i16 { 0 } @@ -248,6 +255,43 @@ where } } + /// Display current, previous and next choices according to + /// the current ChoiceItem. + fn render_choices<'s>(&'s self, target: &mut impl Renderer<'s>) { + // Getting the row area for the choices - so that displaying + // items in the used font will show them in the middle vertically. + let area_height_half = self.pad.area.height() / 2; + let font_size_half = theme::FONT_CHOICE_ITEMS.visible_text_height("Ay") / 2; + let center_row_area = self + .pad + .area + .split_top(area_height_half) + .0 + .outset(Insets::bottom(font_size_half)); + + // Drawing the current item in the middle. + self.show_current_choice2(target, center_row_area); + + // Not drawing the rest when not wanted + if self.show_only_one_item { + return; + } + + // Getting the remaining left and right areas. + let center_width = self.get_current_item().width_center(); + let (left_area, _center_area, right_area) = center_row_area.split_center(center_width); + + // Possibly drawing on the left side. + if self.has_previous_choice() || self.is_carousel { + self.show_left_choices2(target, left_area); + } + + // Possibly drawing on the right side. + if self.has_next_choice() || self.is_carousel { + self.show_right_choices2(target, right_area); + } + } + /// Setting current buttons, and clearing. fn update(&mut self, ctx: &mut EventCtx) { self.set_buttons(ctx); @@ -296,6 +340,12 @@ where .paint_center(area, self.inverse_selected_item); } + /// Display the current choice in the middle. + fn show_current_choice2<'s>(&'s self, target: &mut impl Renderer<'s>, area: Rect) { + self.get_current_item() + .render_center(target, area, self.inverse_selected_item); + } + /// Display all the choices fitting on the left side. /// Going as far as possible. fn show_left_choices(&self, area: Rect) { @@ -338,6 +388,48 @@ where } } + /// Display all the choices fitting on the left side. + /// Going as far as possible. + fn show_left_choices2<'s>(&'s self, target: &mut impl Renderer<'s>, area: Rect) { + // NOTE: page index can get negative here, so having it as i16 instead of usize + let mut page_index = self.page_counter as i16 - 1; + let mut current_area = area.split_right(self.items_distance).0; + while current_area.width() > 0 { + // Breaking out of the loop if we exhausted left items + // and the carousel mode is not enabled. + if page_index < 0 { + if self.is_carousel { + // Moving to the last page. + page_index = self.last_page_index() as i16; + } else { + break; + } + } + + let (choice, _) = self.choices.get(page_index as usize); + let choice_width = choice.width_side(); + + if current_area.width() <= choice_width && !self.show_incomplete { + // early break for an item that will not fit the remaining space + break; + } + + // We need to calculate the area explicitly because we want to allow it + // to exceed the bounds of the original area. + let choice_area = Rect::from_top_right_and_size( + current_area.top_right(), + Offset::new(choice_width, current_area.height()), + ); + choice.render_side(target, choice_area); + + // Updating loop variables. + current_area = current_area + .split_right(choice_width + self.items_distance) + .0; + page_index -= 1; + } + } + /// Display all the choices fitting on the right side. /// Going as far as possible. fn show_right_choices(&self, area: Rect) { @@ -379,6 +471,47 @@ where } } + /// Display all the choices fitting on the right side. + /// Going as far as possible. + fn show_right_choices2<'s>(&'s self, target: &mut impl Renderer<'s>, area: Rect) { + let mut page_index = self.page_counter + 1; + let mut current_area = area.split_left(self.items_distance).1; + while current_area.width() > 0 { + // Breaking out of the loop if we exhausted right items + // and the carousel mode is not enabled. + if page_index > self.last_page_index() { + if self.is_carousel { + // Moving to the first page. + page_index = 0; + } else { + break; + } + } + + let (choice, _) = self.choices.get(page_index); + let choice_width = choice.width_side(); + + if current_area.width() <= choice_width && !self.show_incomplete { + // early break for an item that will not fit the remaining space + break; + } + + // We need to calculate the area explicitly because we want to allow it + // to exceed the bounds of the original area. + let choice_area = Rect::from_top_left_and_size( + current_area.top_left(), + Offset::new(choice_width, current_area.height()), + ); + choice.render_side(target, choice_area); + + // Updating loop variables. + current_area = current_area + .split_left(choice_width + self.items_distance) + .1; + page_index += 1; + } + } + /// Decrease the page counter to the previous page. fn decrease_page_counter(&mut self) { self.page_counter -= 1; @@ -586,6 +719,12 @@ where self.buttons.paint(); self.paint_choices(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.pad.render(target); + self.buttons.render(target); + self.render_choices(target); + } } // DEBUG-ONLY SECTION BELOW diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/choice_item.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/choice_item.rs index f9792f638..e40ce39d2 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/choice_item.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/choice_item.rs @@ -3,6 +3,8 @@ use crate::{ ui::{ display::{self, rect_fill, rect_fill_corners, rect_outline_rounded, Font, Icon}, geometry::{Alignment2D, Offset, Rect}, + shape, + shape::Renderer, }, }; @@ -107,6 +109,27 @@ impl Choice for ChoiceItem { ); } + /// Painting the item as the main choice in the middle. + /// Showing both the icon and text, if the icon is available. + fn render_center<'s>(&self, target: &mut impl Renderer<'s>, area: Rect, inverse: bool) { + let width = text_icon_width(Some(self.text.as_ref()), self.icon, self.font); + render_rounded_highlight( + target, + area, + Offset::new(width, self.font.visible_text_height("Ay")), + inverse, + ); + render_text_icon( + target, + area, + width, + Some(self.text.as_ref()), + self.icon, + self.font, + inverse, + ); + } + /// Getting the overall width in pixels when displayed in center. /// That means both the icon and text will be shown. fn width_center(&self) -> i16 { @@ -125,6 +148,20 @@ impl Choice for ChoiceItem { paint_text_icon(area, width, self.side_text(), self.icon, self.font, false); } + /// Painting smaller version of the item on the side. + fn render_side<'s>(&self, target: &mut impl Renderer<'s>, area: Rect) { + let width = text_icon_width(self.side_text(), self.icon, self.font); + render_text_icon( + target, + area, + width, + self.side_text(), + self.icon, + self.font, + false, + ); + } + /// Getting current button layout. fn btn_layout(&self) -> ButtonLayout { self.btn_layout.clone() @@ -151,6 +188,37 @@ fn paint_rounded_highlight(area: Rect, size: Offset, inverse: bool) { } } +fn render_rounded_highlight<'s>( + target: &mut impl Renderer<'s>, + area: Rect, + size: Offset, + inverse: bool, +) { + let bound = theme::BUTTON_OUTLINE; + let left_bottom = area.bottom_center() + Offset::new(-size.x / 2 - bound, bound + 1); + let x_size = size.x + 2 * bound; + let y_size = size.y + 2 * bound; + let outline_size = Offset::new(x_size, y_size); + let outline = Rect::from_bottom_left_and_size(left_bottom, outline_size); + if inverse { + shape::Bar::new(outline) + .with_radius(1) + .with_bg(theme::FG) + .render(target); + } else { + // Draw outline by drawing two rounded rectangles + shape::Bar::new(outline) + .with_radius(1) + .with_bg(theme::FG) + .render(target); + + shape::Bar::new(outline.shrink(1)) + .with_radius(1) + .with_bg(theme::BG) + .render(target); + } +} + fn text_icon_width(text: Option<&str>, icon: Option, font: Font) -> i16 { match (text, icon) { (Some(text), Some(icon)) => { @@ -194,6 +262,40 @@ fn paint_text_icon( } } +fn render_text_icon<'s>( + target: &mut impl Renderer<'s>, + area: Rect, + width: i16, + text: Option<&str>, + icon: Option, + font: Font, + inverse: bool, +) { + let fg_color = if inverse { theme::BG } else { theme::FG }; + + let mut baseline = area.bottom_center() - Offset::x(width / 2); + if let Some(icon) = icon { + let height_diff = font.visible_text_height("Ay") - icon.toif.height(); + let vertical_offset = Offset::y(-height_diff / 2); + shape::ToifImage::new(baseline + vertical_offset, icon.toif) + .with_align(Alignment2D::BOTTOM_LEFT) + .with_fg(fg_color) + .render(target); + + baseline = baseline + Offset::x(icon.toif.width() + ICON_RIGHT_PADDING); + } + + if let Some(text) = text { + // Possibly shifting the baseline left, when there is a text bearing. + // This is to center the text properly. + baseline = baseline - Offset::x(font.start_x_bearing(text)); + shape::Text::new(baseline, text) + .with_font(font) + .with_fg(fg_color) + .render(target); + } +} + // DEBUG-ONLY SECTION BELOW #[cfg(feature = "ui_debug")] diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/number_input.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/number_input.rs index a507c3160..9d25e5db8 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/number_input.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/number_input.rs @@ -3,6 +3,7 @@ use crate::{ ui::{ component::{Component, Event, EventCtx}, geometry::Rect, + shape::Renderer, }, }; @@ -81,6 +82,10 @@ impl Component for NumberInput { fn paint(&mut self) { self.choice_page.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.choice_page.render(target); + } } // DEBUG-ONLY SECTION BELOW diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/passphrase.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/passphrase.rs index fc0ca17f9..d3f422a6e 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/passphrase.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/passphrase.rs @@ -6,6 +6,7 @@ use crate::{ component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx}, display::Icon, geometry::Rect, + shape::Renderer, util::char_to_string, }, }; @@ -448,6 +449,11 @@ impl Component for PassphraseEntry { self.passphrase_dots.paint(); self.choice_page.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.passphrase_dots.render(target); + self.choice_page.render(target); + } } // DEBUG-ONLY SECTION BELOW diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/pin.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/pin.rs index 404e8d8ba..e977e4ea3 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/pin.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/pin.rs @@ -6,6 +6,7 @@ use crate::{ component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx}, display::{Font, Icon}, geometry::Rect, + shape::Renderer, }, }; @@ -323,6 +324,12 @@ impl Component for PinEntry<'_> { self.pin_line.paint(); self.choice_page.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.header_line.render(target); + self.pin_line.render(target); + self.choice_page.render(target); + } } // DEBUG-ONLY SECTION BELOW diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/simple_choice.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/simple_choice.rs index 23bc9bd1c..f322c8cf1 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/simple_choice.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/simple_choice.rs @@ -4,6 +4,7 @@ use crate::{ ui::{ component::{Component, Event, EventCtx}, geometry::Rect, + shape::Renderer, }, }; @@ -115,6 +116,10 @@ impl Component for SimpleChoice { fn paint(&mut self) { self.choice_page.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.choice_page.render(target); + } } // DEBUG-ONLY SECTION BELOW diff --git a/core/embed/rust/src/ui/model_tr/component/input_methods/wordlist.rs b/core/embed/rust/src/ui/model_tr/component/input_methods/wordlist.rs index 789fa0bd3..27bfd4fff 100644 --- a/core/embed/rust/src/ui/model_tr/component/input_methods/wordlist.rs +++ b/core/embed/rust/src/ui/model_tr/component/input_methods/wordlist.rs @@ -4,6 +4,7 @@ use crate::{ ui::{ component::{text::common::TextBox, Child, Component, ComponentExt, Event, EventCtx}, geometry::Rect, + shape::Renderer, util::char_to_string, }, }; @@ -314,6 +315,11 @@ impl Component for WordlistEntry { self.chosen_letters.paint(); self.choice_page.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.chosen_letters.render(target); + self.choice_page.render(target); + } } // DEBUG-ONLY SECTION BELOW diff --git a/core/embed/rust/src/ui/model_tr/component/loader.rs b/core/embed/rust/src/ui/model_tr/component/loader.rs index dc05d5b8d..799bfb19a 100644 --- a/core/embed/rust/src/ui/model_tr/component/loader.rs +++ b/core/embed/rust/src/ui/model_tr/component/loader.rs @@ -6,7 +6,9 @@ use crate::{ component::{Child, Component, Event, EventCtx}, constant, display::{self, Color, Font, LOADER_MAX}, - geometry::{Offset, Rect}, + geometry::{Offset, Point, Rect}, + shape, + shape::Renderer, util::animation_disabled, }, }; @@ -164,6 +166,51 @@ impl Loader { invert_from as i16, ); } + + pub fn render_loader<'s>( + &'s self, + target: &mut impl Renderer<'s>, + style: &LoaderStyle, + done: i32, + ) { + let width = self.area.width(); + // NOTE: need to calculate this in `i32`, it would overflow using `i16` + let split_point = (((width as i32 + 1) * done) / (display::LOADER_MAX as i32)) as i16; + let (r_left, r_right) = self.area.split_left(split_point); + let parts = [(r_left, true), (r_right, false)]; + parts.map(|(r, invert)| { + target.in_clip(r, &|target| { + if invert { + shape::Bar::new(self.area) + .with_radius(3) + .with_bg(style.fg_color) + .render(target); + } else { + shape::Bar::new(self.area) + .with_radius(3) + .with_fg(style.fg_color) + .render(target); + } + + let text_color = if invert { + style.bg_color + } else { + style.fg_color + }; + + self.get_text().map(|t| { + let pt = Point::new( + style.font.horz_center(self.area.x0, self.area.x1, t), + style.font.vert_center(self.area.y0, self.area.y1, "A"), + ); + shape::Text::new(pt, t) + .with_font(style.font) + .with_fg(text_color) + .render(target); + }); + }); + }); + } } impl Component for Loader { @@ -223,6 +270,28 @@ impl Component for Loader { } } } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + // TODO: Consider passing the current instant along with the event -- that way, + // we could synchronize painting across the component tree. Also could be useful + // in automated tests. + // In practice, taking the current instant here is more precise in case some + // other component in the tree takes a long time to draw. + let now = Instant::now(); + + if let State::Initial = self.state { + self.render_loader(target, self.styles.normal, 0); + } else if let State::Grown = self.state { + self.render_loader(target, self.styles.normal, display::LOADER_MAX as i32); + } else { + let progress = self.progress(now); + if let Some(done) = progress { + self.render_loader(target, self.styles.normal, done as i32); + } else { + self.render_loader(target, self.styles.normal, 0); + } + } + } } pub struct LoaderStyleSheet { @@ -323,6 +392,10 @@ impl Component for ProgressLoader { fn paint(&mut self) { self.loader.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.loader.render(target); + } } // DEBUG-ONLY SECTION BELOW diff --git a/core/embed/rust/src/ui/model_tr/component/page.rs b/core/embed/rust/src/ui/model_tr/component/page.rs index 31e6505e6..935f184c8 100644 --- a/core/embed/rust/src/ui/model_tr/component/page.rs +++ b/core/embed/rust/src/ui/model_tr/component/page.rs @@ -4,6 +4,7 @@ use crate::{ component::{Child, Component, ComponentExt, Event, EventCtx, Pad, PageMsg, Paginate}, display::Color, geometry::{Insets, Rect}, + shape::Renderer, }, }; @@ -215,6 +216,12 @@ where self.content.paint(); self.buttons.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.pad.render(target); + self.content.render(target); + self.buttons.render(target); + } } // DEBUG-ONLY SECTION BELOW diff --git a/core/embed/rust/src/ui/model_tr/component/progress.rs b/core/embed/rust/src/ui/model_tr/component/progress.rs index 4b9da68f5..711b42da4 100644 --- a/core/embed/rust/src/ui/model_tr/component/progress.rs +++ b/core/embed/rust/src/ui/model_tr/component/progress.rs @@ -10,7 +10,10 @@ use crate::{ }, constant, display::{self, Font, Icon, LOADER_MAX}, - geometry::Rect, + geometry::{Alignment2D, Offset, Rect}, + model_tr::cshape, + shape, + shape::Renderer, util::animation_disabled, }, }; @@ -151,6 +154,29 @@ impl Component for Progress { self.description.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.title.render(target); + + let area = constant::screen(); + let center = area.center() + Offset::y(self.loader_y_offset); + + if self.indeterminate { + cshape::LoaderStarry::new(center, self.value) + .with_color(theme::FG) + .render(target); + } else { + cshape::LoaderCircular::new(center, self.value) + .with_color(theme::FG) + .render(target); + shape::ToifImage::new(center, self.icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(theme::FG) + .render(target); + } + self.description_pad.render(target); + self.description.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(Self::AREA); diff --git a/core/embed/rust/src/ui/model_tr/component/result.rs b/core/embed/rust/src/ui/model_tr/component/result.rs index 6da84f843..32f20f418 100644 --- a/core/embed/rust/src/ui/model_tr/component/result.rs +++ b/core/embed/rust/src/ui/model_tr/component/result.rs @@ -3,6 +3,8 @@ use crate::ui::{ constant::{screen, HEIGHT, WIDTH}, display::{Color, Icon}, geometry::{Alignment2D, Offset, Point, Rect}, + shape, + shape::Renderer, }; const MESSAGE_AREA_START: i16 = 24 + 11; @@ -107,4 +109,17 @@ impl Component for ResultScreen<'_> { self.message_top.paint(); self.message_bottom.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + self.small_pad.render(target); + + shape::ToifImage::new(screen().top_center() + Offset::y(ICON_TOP), self.icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(self.fg_color) + .render(target); + + self.message_top.render(target); + self.message_bottom.render(target); + } } diff --git a/core/embed/rust/src/ui/model_tr/component/scrollbar.rs b/core/embed/rust/src/ui/model_tr/component/scrollbar.rs index d394da7be..3e812ee91 100644 --- a/core/embed/rust/src/ui/model_tr/component/scrollbar.rs +++ b/core/embed/rust/src/ui/model_tr/component/scrollbar.rs @@ -2,6 +2,8 @@ use crate::ui::{ component::{Component, Event, EventCtx, Never, Pad, Paginate}, display, geometry::{Offset, Point, Rect}, + shape, + shape::Renderer, }; use super::super::theme; @@ -103,6 +105,35 @@ impl ScrollBar { } } + /// Create a (seemingly circular) dot given its top left point. + /// Make it full when it is active, otherwise paint just the perimeter and + /// leave center empty. + fn render_dot<'s>(&self, target: &mut impl Renderer<'s>, dot_type: &DotType, top_right: Point) { + let full_square = + Rect::from_top_right_and_size(top_right, Offset::uniform(Self::MAX_DOT_SIZE)); + + match dot_type { + DotType::BigFull => shape::Bar::new(full_square) + .with_radius(2) + .with_bg(theme::FG) + .render(target), + + DotType::Big => shape::Bar::new(full_square) + .with_radius(2) + .with_fg(theme::FG) + .render(target), + + DotType::Middle => shape::Bar::new(full_square.shrink(1)) + .with_radius(1) + .with_fg(theme::FG) + .render(target), + + DotType::Small => shape::Bar::new(full_square.shrink(2)) + .with_bg(theme::FG) + .render(target), + } + } + /// Get a sequence of dots to be drawn, with specifying their appearance. /// Painting only big dots in case of 2 and 3 pages, /// three big and 1 middle in case of 4 pages, @@ -202,6 +233,14 @@ impl ScrollBar { top_right.x -= Self::DOTS_INTERVAL; } } + + fn render_horizontal<'s>(&'s self, target: &mut impl Renderer<'s>) { + let mut top_right = self.pad.area.top_right(); + for dot in self.get_drawable_dots().iter().rev() { + self.render_dot(target, dot, top_right); + top_right.x -= Self::DOTS_INTERVAL; + } + } } impl Component for ScrollBar { @@ -233,6 +272,17 @@ impl Component for ScrollBar { self.pad.paint(); self.paint_horizontal(); } + + /// Displaying one dot for each page. + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + // Not showing the scrollbar dot when there is only one page + if self.page_count <= 1 { + return; + } + + self.pad.render(target); + self.render_horizontal(target); + } } impl Paginate for ScrollBar { diff --git a/core/embed/rust/src/ui/model_tr/component/share_words.rs b/core/embed/rust/src/ui/model_tr/component/share_words.rs index 24283950a..c9ca06667 100644 --- a/core/embed/rust/src/ui/model_tr/component/share_words.rs +++ b/core/embed/rust/src/ui/model_tr/component/share_words.rs @@ -3,10 +3,13 @@ use crate::{ translations::TR, ui::{ component::{ - text::util::text_multiline, Child, Component, Event, EventCtx, Never, Paginate, + text::util::{text_multiline, text_multiline2}, + Child, Component, Event, EventCtx, Never, Paginate, }, display::Font, geometry::{Alignment, Offset, Rect}, + shape, + shape::Renderer, }, }; @@ -95,6 +98,20 @@ where ); } + /// Display the final page with user confirmation. + fn render_final_page<'s>(&'s self, target: &mut impl Renderer<'s>) { + let final_text = self.get_final_text(); + text_multiline2( + target, + self.area.split_top(INFO_TOP_OFFSET).1, + final_text.as_str().into(), + Font::NORMAL, + theme::FG, + theme::BG, + Alignment::Start, + ); + } + /// Display current set of recovery words. fn paint_words(&mut self) { let mut y_offset = 0; @@ -112,6 +129,32 @@ where display_left(baseline + Offset::x(WORD_X_OFFSET), word, WORD_FONT); } } + + /// Display current set of recovery words. + fn render_words<'s>(&'s self, target: &mut impl Renderer<'s>) { + let mut y_offset = 0; + // Showing the word index and the words itself + for i in 0..WORDS_PER_PAGE { + y_offset += NUMBER_FONT.line_height() + EXTRA_LINE_HEIGHT; + let index = self.word_index() + i; + if index >= self.share_words.len() { + break; + } + let word = &self.share_words[index]; + let baseline = self.area.top_left() + Offset::y(y_offset); + let ordinal = build_string!(5, inttostr!(index as u8 + 1), "."); + + shape::Text::new(baseline + Offset::x(NUMBER_X_OFFSET), &ordinal) + .with_font(NUMBER_FONT) + .with_fg(theme::FG) + .render(target); + + shape::Text::new(baseline + Offset::x(WORD_X_OFFSET), word.as_ref()) + .with_font(WORD_FONT) + .with_fg(theme::FG) + .render(target); + } + } } impl Component for ShareWords @@ -147,6 +190,17 @@ where self.paint_words(); } } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + // Showing scrollbar in all cases + // Individual pages are responsible for not colliding with it + self.scrollbar.render(target); + if self.is_final_page() { + self.render_final_page(target); + } else { + self.render_words(target); + } + } } impl Paginate for ShareWords diff --git a/core/embed/rust/src/ui/model_tr/component/show_more.rs b/core/embed/rust/src/ui/model_tr/component/show_more.rs index 717481d90..c2a10e1b5 100644 --- a/core/embed/rust/src/ui/model_tr/component/show_more.rs +++ b/core/embed/rust/src/ui/model_tr/component/show_more.rs @@ -3,6 +3,7 @@ use crate::{ ui::{ component::{Child, Component, Event, EventCtx}, geometry::{Insets, Rect}, + shape::Renderer, }, }; @@ -77,6 +78,11 @@ where self.content.paint(); self.buttons.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.content.render(target); + self.buttons.render(target); + } } // DEBUG-ONLY SECTION BELOW diff --git a/core/embed/rust/src/ui/model_tr/component/title.rs b/core/embed/rust/src/ui/model_tr/component/title.rs index f75d488df..a34e4a288 100644 --- a/core/embed/rust/src/ui/model_tr/component/title.rs +++ b/core/embed/rust/src/ui/model_tr/component/title.rs @@ -4,7 +4,9 @@ use crate::{ ui::{ component::{Component, Event, EventCtx, Marquee, Never}, display, - geometry::{Offset, Rect}, + geometry::{Alignment, Offset, Rect}, + shape, + shape::Renderer, }, }; @@ -59,6 +61,22 @@ impl Title { }); } + /// Display title/header at the top left of the given area. + pub fn render_header_left<'s>( + target: &mut impl Renderer<'s>, + title: &TString<'static>, + area: Rect, + ) { + let text_height = theme::FONT_HEADER.text_height(); + let title_baseline = area.top_left() + Offset::y(text_height - 1); + title.map(|s| { + shape::Text::new(title_baseline, s) + .with_font(theme::FONT_HEADER) + .with_fg(theme::FG) + .render(target); + }); + } + /// Display title/header centered at the top of the given area. pub fn paint_header_centered(title: &TString<'static>, area: Rect) { let text_height = theme::FONT_HEADER.text_height(); @@ -67,6 +85,23 @@ impl Title { display::text_center(title_baseline, s, theme::FONT_HEADER, theme::FG, theme::BG) }); } + + /// Display title/header centered at the top of the given area. + pub fn render_header_centered<'s>( + target: &mut impl Renderer<'s>, + title: &TString<'static>, + area: Rect, + ) { + let text_height = theme::FONT_HEADER.text_height(); + let title_baseline = area.top_center() + Offset::y(text_height - 1); + title.map(|s| { + shape::Text::new(title_baseline, s) + .with_align(Alignment::Center) + .with_font(theme::FONT_HEADER) + .with_fg(theme::FG) + .render(target); + }); + } } impl Component for Title { @@ -99,6 +134,16 @@ impl Component for Title { Self::paint_header_left(&self.title, self.area); } } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + if self.needs_marquee { + self.marquee.render(target); + } else if self.centered { + Self::render_header_centered(target, &self.title, self.area); + } else { + Self::render_header_left(target, &self.title, self.area); + } + } } // DEBUG-ONLY SECTION BELOW diff --git a/core/embed/rust/src/ui/model_tr/component/welcome_screen.rs b/core/embed/rust/src/ui/model_tr/component/welcome_screen.rs index e9ae5985b..a824d85e7 100644 --- a/core/embed/rust/src/ui/model_tr/component/welcome_screen.rs +++ b/core/embed/rust/src/ui/model_tr/component/welcome_screen.rs @@ -1,6 +1,8 @@ use crate::ui::{ component::{Component, Event, EventCtx, Never}, geometry::{Alignment2D, Offset, Rect}, + shape, + shape::Renderer, }; use super::super::theme; @@ -52,6 +54,30 @@ impl Component for WelcomeScreen { theme::BG, ); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + shape::ToifImage::new( + self.area.bottom_center() - Offset::y(5), + theme::ICON_DEVICE_NAME.toif, + ) + .with_align(Alignment2D::BOTTOM_CENTER) + .with_fg(theme::FG) + .render(target); + + let icon = if self.empty_lock { + theme::ICON_LOGO_EMPTY + } else { + theme::ICON_LOGO + }; + + shape::ToifImage::new( + self.area.top_center() + Offset::y(ICON_TOP_MARGIN), + icon.toif, + ) + .with_align(Alignment2D::TOP_CENTER) + .with_fg(theme::FG) + .render(target); + } } #[cfg(feature = "ui_debug")] diff --git a/core/embed/rust/src/ui/model_tr/cshape/dotted_line.rs b/core/embed/rust/src/ui/model_tr/cshape/dotted_line.rs new file mode 100644 index 000000000..6c5fe08c3 --- /dev/null +++ b/core/embed/rust/src/ui/model_tr/cshape/dotted_line.rs @@ -0,0 +1,87 @@ +use crate::ui::{ + display::Color, + geometry::{Offset, Point, Rect}, + shape::{Canvas, DrawingCache, Renderer, Shape, ShapeClone}, +}; + +use without_alloc::alloc::LocalAllocLeakExt; + +// Shape of horizontal solid/dotted line +pub struct HorizontalLine { + /// Position of the left-top point + pos: Point, + // Length of the line + length: i16, + /// Line thickness (default 1) + thickness: u8, + /// Steps of dots (default 0 - full line) + step: u8, + /// Color + color: Color, +} + +impl HorizontalLine { + pub fn new(pos: Point, length: i16) -> Self { + Self { + pos, + length, + thickness: 1, + step: 0, + color: Color::white(), + } + } + + pub fn with_color(self, color: Color) -> Self { + Self { color, ..self } + } + + pub fn with_thickness(self, thickness: u8) -> Self { + Self { thickness, ..self } + } + + pub fn with_step(self, step: u8) -> Self { + Self { step, ..self } + } + + pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) { + renderer.render_shape(self); + } +} + +impl<'s> Shape<'s> for HorizontalLine { + fn bounds(&self, _cache: &DrawingCache<'s>) -> Rect { + let size = Offset::new(self.length, self.thickness as i16); + Rect::from_top_left_and_size(self.pos, size) + } + + fn cleanup(&mut self, _cache: &DrawingCache) {} + + fn draw(&mut self, canvas: &mut dyn Canvas, _cache: &DrawingCache) { + if self.step <= self.thickness { + // Solid line + let size = Offset::new(self.length, self.thickness as i16); + let r = Rect::from_top_left_and_size(self.pos, size); + canvas.fill_rect(r, self.color, 255); + } else { + // Dotted line + let thickness = self.thickness as i16; + for x in (0..self.length - thickness).step_by(self.step as usize) { + let r = Rect::from_top_left_and_size( + self.pos + Offset::x(x), + Offset::uniform(thickness), + ); + canvas.fill_rect(r, self.color, 255); + } + } + } +} + +impl<'s> ShapeClone<'s> for HorizontalLine { + fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>> + where + T: LocalAllocLeakExt<'alloc>, + { + let clone = bump.alloc_t::()?; + Some(clone.uninit.init(HorizontalLine { ..self })) + } +} diff --git a/core/embed/rust/src/ui/model_tr/cshape/loader_circular.rs b/core/embed/rust/src/ui/model_tr/cshape/loader_circular.rs new file mode 100644 index 000000000..676575aca --- /dev/null +++ b/core/embed/rust/src/ui/model_tr/cshape/loader_circular.rs @@ -0,0 +1,116 @@ +use crate::ui::{ + display::Color, + geometry::{Offset, Point, Rect}, + shape::{Canvas, DrawingCache, Renderer, Shape, ShapeClone}, +}; + +use without_alloc::alloc::LocalAllocLeakExt; + +static CELLS: [Offset; 24] = [ + Offset::new(1, -4), + Offset::new(2, -4), + Offset::new(3, -3), + Offset::new(4, -2), + Offset::new(4, -1), + Offset::new(4, 0), + Offset::new(4, 1), + Offset::new(4, 2), + Offset::new(3, 3), + Offset::new(2, 4), + Offset::new(1, 4), + Offset::new(0, 4), + Offset::new(-1, 4), + Offset::new(-2, 4), + Offset::new(-3, 3), + Offset::new(-4, 2), + Offset::new(-4, 1), + Offset::new(-4, 0), + Offset::new(-4, -1), + Offset::new(-4, -2), + Offset::new(-3, -3), + Offset::new(-2, -4), + Offset::new(-1, -4), + Offset::new(0, -4), +]; + +pub struct LoaderCircular { + /// Position of point (0,0) + pos: Point, + /// Value 0..1000 + value: u16, + /// Color + color: Color, + /// Scale (length of square size) + scale: i16, +} + +impl LoaderCircular { + pub fn new(pos: Point, value: u16) -> Self { + Self { + pos, + value, + color: Color::white(), + scale: 2, + } + } + + pub fn with_color(self, color: Color) -> Self { + Self { color, ..self } + } + + pub fn with_scale(self, scale: i16) -> Self { + Self { scale, ..self } + } + + fn cells(&self) -> &[Offset] { + let value = self.value.clamp(0, 1000); + let last = (CELLS.len() * value as usize) / 1000; + &CELLS[..last] + } + + fn cell_rect(&self, offset: Offset) -> Rect { + let pt = Point::new( + self.pos.x + (offset.x * self.scale) - self.scale / 2, + self.pos.y + (offset.y * self.scale) - self.scale / 2, + ); + Rect::from_top_left_and_size(pt, Offset::uniform(self.scale)) + } + + pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) { + renderer.render_shape(self); + } +} + +impl<'s> Shape<'s> for LoaderCircular { + fn bounds(&self, _cache: &DrawingCache<'s>) -> Rect { + let cells = self.cells(); + + if cells.is_empty() { + Rect::zero() + } else { + let mut b = self.cell_rect(cells[0]); + cells[1..] + .iter() + .for_each(|c| b = b.union(self.cell_rect(*c))); + b + } + } + + fn cleanup(&mut self, _cache: &DrawingCache) {} + + fn draw(&mut self, canvas: &mut dyn Canvas, _cache: &DrawingCache) { + for c in self.cells().iter() { + canvas.fill_rect(self.cell_rect(*c), self.color, 255); + } + } +} + +impl ShapeClone<'_> for LoaderCircular { + fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape> + where + T: LocalAllocLeakExt<'alloc>, + { + let clone = bump.alloc_t::()?; + Some(clone.uninit.init(LoaderCircular { ..self })) + } +} diff --git a/core/embed/rust/src/ui/model_tr/cshape/loader_small.rs b/core/embed/rust/src/ui/model_tr/cshape/loader_small.rs new file mode 100644 index 000000000..17bbc1e2a --- /dev/null +++ b/core/embed/rust/src/ui/model_tr/cshape/loader_small.rs @@ -0,0 +1,88 @@ +use crate::ui::{ + display::Color, + geometry::{Offset, Point, Rect}, + shape::{Canvas, DrawingCache, Renderer, Shape, ShapeClone}, +}; + +use without_alloc::alloc::LocalAllocLeakExt; + +use core::f32::consts::SQRT_2; + +const STAR_COUNT: usize = 8; +const RADIUS: i16 = 3; +const DIAGONAL: i16 = ((RADIUS as f32 * SQRT_2) / 2_f32) as i16; + +// Offset of the normal point and then the extra offset for the main point +static STARS: [(Offset, Offset); STAR_COUNT] = [ + (Offset::y(-RADIUS), Offset::y(-1)), + (Offset::new(DIAGONAL, -DIAGONAL), Offset::new(1, -1)), + (Offset::x(RADIUS), Offset::x(1)), + (Offset::new(DIAGONAL, DIAGONAL), Offset::new(1, 1)), + (Offset::y(RADIUS), Offset::y(1)), + (Offset::new(-DIAGONAL, DIAGONAL), Offset::new(-1, 1)), + (Offset::x(-RADIUS), Offset::x(-1)), + (Offset::new(-DIAGONAL, -DIAGONAL), Offset::new(-1, -1)), +]; + +/// A shape of a TS3 small loader +pub struct LoaderSmall { + /// Position of point (0,0) + pos: Point, + /// Value 0..1000 + value: u16, + /// Color + color: Color, +} + +impl LoaderSmall { + pub fn new(pos: Point, value: u16) -> Self { + Self { + pos, + value, + color: Color::white(), + } + } + + pub fn with_color(self, color: Color) -> Self { + Self { color, ..self } + } + + pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) { + renderer.render_shape(self); + } +} + +impl Shape<'_> for LoaderSmall { + fn bounds(&self, _cache: &DrawingCache) -> Rect { + Rect::from_top_left_and_size(self.pos, Offset::uniform(1)).expand(RADIUS + 1) + } + + fn cleanup(&mut self, _cache: &DrawingCache) {} + + fn draw(&mut self, canvas: &mut dyn Canvas, _cache: &DrawingCache) { + // Calculate index of the highlighted star + let sel_idx = (STAR_COUNT * self.value as usize / 1000) % STAR_COUNT; + + for (i, (star_offset, hili_offset)) in STARS.iter().enumerate() { + if (sel_idx + 1) % STAR_COUNT != i { + // Draw a star if it's not behind the highlighted one (clockwise) + let star_pos = self.pos + *star_offset; + canvas.draw_pixel(star_pos, self.color); + if sel_idx == i { + // Higlight the main star + canvas.draw_pixel(star_pos + *hili_offset, self.color); + } + } + } + } +} + +impl ShapeClone<'_> for LoaderSmall { + fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape> + where + T: LocalAllocLeakExt<'alloc>, + { + let clone = bump.alloc_t::()?; + Some(clone.uninit.init(LoaderSmall { ..self })) + } +} diff --git a/core/embed/rust/src/ui/model_tr/cshape/loader_starry.rs b/core/embed/rust/src/ui/model_tr/cshape/loader_starry.rs new file mode 100644 index 000000000..f63440c29 --- /dev/null +++ b/core/embed/rust/src/ui/model_tr/cshape/loader_starry.rs @@ -0,0 +1,105 @@ +use crate::ui::{ + display::Color, + geometry::{Offset, Point, Rect}, + shape::{Canvas, DrawingCache, Renderer, Shape, ShapeClone}, +}; + +use without_alloc::alloc::LocalAllocLeakExt; + +use core::f32::consts::SQRT_2; + +const STAR_COUNT: usize = 8; +const STAR_SMALL: i16 = 2; +const STAR_MEDIUM: i16 = 4; +const STAR_LARGE: i16 = 6; + +const RADIUS: i16 = 13; +const DIAGONAL: i16 = ((RADIUS as f32 * SQRT_2) / 2_f32) as i16; + +// Offset of the normal point and then the extra offset for the main point +static STARS: [Offset; STAR_COUNT] = [ + Offset::y(-RADIUS), + Offset::new(DIAGONAL, -DIAGONAL), + Offset::x(RADIUS), + Offset::new(DIAGONAL, DIAGONAL), + Offset::y(RADIUS), + Offset::new(-DIAGONAL, DIAGONAL), + Offset::x(-RADIUS), + Offset::new(-DIAGONAL, -DIAGONAL), +]; + +/// A shape of a TS3 starry loader +pub struct LoaderStarry { + /// Position of point (0,0) + pos: Point, + /// Value 0..1000 + value: u16, + /// Color + color: Color, +} + +impl LoaderStarry { + pub fn new(pos: Point, value: u16) -> Self { + Self { + pos, + value, + color: Color::white(), + } + } + + pub fn with_color(self, color: Color) -> Self { + Self { color, ..self } + } + + pub fn render<'s>(self, renderer: &mut impl Renderer<'s>) { + renderer.render_shape(self); + } + + fn draw_large_star(&self, canvas: &mut dyn Canvas, offset: Offset) { + let r = Rect::from_center_and_size(self.pos + offset, Offset::uniform(STAR_LARGE)); + canvas.fill_round_rect(r, 2, self.color, 255); + } + + fn draw_medium_star(&self, canvas: &mut dyn Canvas, offset: Offset) { + let r = Rect::from_center_and_size(self.pos + offset, Offset::uniform(STAR_MEDIUM)); + canvas.fill_round_rect(r, 1, self.color, 255); + } + + fn draw_small_star(&self, canvas: &mut dyn Canvas, offset: Offset) { + let r = Rect::from_center_and_size(self.pos + offset, Offset::uniform(STAR_SMALL)); + canvas.fill_rect(r, self.color, 255); + } +} + +impl Shape<'_> for LoaderStarry { + fn bounds(&self, _cache: &DrawingCache) -> Rect { + Rect::from_top_left_and_size(self.pos, Offset::uniform(1)).expand(RADIUS + STAR_LARGE) + } + + fn cleanup(&mut self, _cache: &DrawingCache) {} + + fn draw(&mut self, canvas: &mut dyn Canvas, _cache: &DrawingCache) { + // Calculate the index of the big star + let sel_idx = (STAR_COUNT * self.value as usize / 1000) % STAR_COUNT; + + for (i, c) in STARS.iter().enumerate() { + if i == sel_idx { + self.draw_large_star(canvas, *c); + } else if (sel_idx + 1) % 8 == i || (sel_idx - 1) % 8 == i { + self.draw_medium_star(canvas, *c); + } else { + self.draw_small_star(canvas, *c); + } + } + } +} + +impl ShapeClone<'_> for LoaderStarry { + fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape> + where + T: LocalAllocLeakExt<'alloc>, + { + let clone = bump.alloc_t::()?; + Some(clone.uninit.init(LoaderStarry { ..self })) + } +} diff --git a/core/embed/rust/src/ui/model_tr/cshape/mod.rs b/core/embed/rust/src/ui/model_tr/cshape/mod.rs new file mode 100644 index 000000000..99d5db684 --- /dev/null +++ b/core/embed/rust/src/ui/model_tr/cshape/mod.rs @@ -0,0 +1,9 @@ +pub mod dotted_line; +pub mod loader_circular; +pub mod loader_small; +pub mod loader_starry; + +pub use dotted_line::HorizontalLine; +pub use loader_circular::LoaderCircular; +pub use loader_small::LoaderSmall; +pub use loader_starry::LoaderStarry; diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index 5753e44e6..2fcde4d6e 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -216,10 +216,7 @@ impl<'a> ComponentMsgObj for Lockscreen<'a> { } } -impl<'a, F> ComponentMsgObj for ConfirmHomescreen -where - F: Fn() -> &'a [u8], -{ +impl ComponentMsgObj for ConfirmHomescreen { fn msg_try_into_obj(&self, msg: Self::Msg) -> Result { match msg { CancelConfirmMsg::Confirmed => Ok(CONFIRMED.as_obj()), @@ -400,15 +397,8 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m extern "C" fn new_confirm_homescreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - let data: Obj = kwargs.get(Qstr::MP_QSTR_image)?; - - // Layout needs to hold the Obj to play nice with GC. Obj is resolved to &[u8] - // in every paint pass. - // SAFETY: We expect no existing mutable reference. Resulting reference is - // discarded before returning to micropython. - let buffer_func = move || unsafe { unwrap!(get_buffer(data)) }; - - let obj = LayoutObj::new(ConfirmHomescreen::new(title, buffer_func))?; + let image: Obj = kwargs.get(Qstr::MP_QSTR_image)?; + let obj = LayoutObj::new(ConfirmHomescreen::new(title, image))?; Ok(obj.into()) }; diff --git a/core/embed/rust/src/ui/model_tr/mod.rs b/core/embed/rust/src/ui/model_tr/mod.rs index cec56de9d..b68581716 100644 --- a/core/embed/rust/src/ui/model_tr/mod.rs +++ b/core/embed/rust/src/ui/model_tr/mod.rs @@ -5,6 +5,7 @@ pub mod bootloader; pub mod common_messages; pub mod component; pub mod constant; +pub mod cshape; #[cfg(feature = "micropython")] pub mod layout; mod screens; diff --git a/core/embed/rust/src/ui/model_tr/screens.rs b/core/embed/rust/src/ui/model_tr/screens.rs index 4855d3074..9bf323b68 100644 --- a/core/embed/rust/src/ui/model_tr/screens.rs +++ b/core/embed/rust/src/ui/model_tr/screens.rs @@ -2,11 +2,21 @@ use crate::ui::{ component::base::Component, constant::screen, display, model_tr::component::WelcomeScreen, }; +#[cfg(feature = "new_rendering")] +use crate::ui::{display::Color, shape::render_on_display}; + use super::{component::ErrorScreen, constant}; pub fn screen_fatal_error(title: &str, msg: &str, footer: &str) { let mut frame = ErrorScreen::new(title.into(), msg.into(), footer.into()); frame.place(constant::screen()); + + #[cfg(feature = "new_rendering")] + render_on_display(None, Some(Color::black()), |target| { + frame.render(target); + }); + + #[cfg(not(feature = "new_rendering"))] frame.paint(); display::refresh(); } @@ -14,7 +24,20 @@ pub fn screen_fatal_error(title: &str, msg: &str, footer: &str) { pub fn screen_boot_stage_2() { let mut frame = WelcomeScreen::new(false); frame.place(screen()); - display::sync(); - frame.paint(); - display::refresh(); + + #[cfg(feature = "new_rendering")] + { + display::sync(); + render_on_display(None, Some(Color::black()), |target| { + frame.render(target); + }); + display::refresh(); + } + + #[cfg(not(feature = "new_rendering"))] + { + display::sync(); + frame.paint(); + display::refresh(); + } } diff --git a/core/embed/rust/src/ui/model_tt/bootloader/intro.rs b/core/embed/rust/src/ui/model_tt/bootloader/intro.rs index 95b61ddaf..c52d8a680 100644 --- a/core/embed/rust/src/ui/model_tt/bootloader/intro.rs +++ b/core/embed/rust/src/ui/model_tt/bootloader/intro.rs @@ -13,6 +13,7 @@ use crate::{ CONTENT_PADDING, CORNER_BUTTON_AREA, MENU32, TEXT_NORMAL, TEXT_WARNING, TITLE_AREA, }, }, + shape::Renderer, }, }; @@ -105,6 +106,15 @@ impl<'a> Component for Intro<'a> { self.menu.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + self.title.render(target); + self.text.render(target); + self.warn.render(target); + self.host.render(target); + self.menu.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.menu.bounds(sink); diff --git a/core/embed/rust/src/ui/model_tt/bootloader/menu.rs b/core/embed/rust/src/ui/model_tt/bootloader/menu.rs index 7d0d664b4..f542dea4b 100644 --- a/core/embed/rust/src/ui/model_tt/bootloader/menu.rs +++ b/core/embed/rust/src/ui/model_tt/bootloader/menu.rs @@ -13,6 +13,7 @@ use crate::{ X32, }, }, + shape::Renderer, }, }; @@ -108,6 +109,14 @@ impl Component for Menu { self.reset.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + self.title.render(target); + self.close.render(target); + self.reboot.render(target); + self.reset.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.close.bounds(sink); diff --git a/core/embed/rust/src/ui/model_tt/bootloader/mod.rs b/core/embed/rust/src/ui/model_tt/bootloader/mod.rs index 57b88c8d4..fa27bdf8b 100644 --- a/core/embed/rust/src/ui/model_tt/bootloader/mod.rs +++ b/core/embed/rust/src/ui/model_tt/bootloader/mod.rs @@ -23,12 +23,32 @@ use super::{ FIRE40, RESULT_FW_INSTALL, RESULT_INITIAL, RESULT_WIPE, TEXT_BOLD, TEXT_NORMAL, TEXT_WIPE_BOLD, TEXT_WIPE_NORMAL, WARNING40, WELCOME_COLOR, X24, }, - BACKLIGHT_NORMAL, BLACK, FG, WHITE, + BACKLIGHT_NORMAL, FG, }, ModelTTFeatures, }; use crate::ui::{ui_features::UIFeaturesBootloader, UIFeaturesCommon}; + +#[cfg(not(feature = "new_rendering"))] +use super::theme::BLACK; + +#[cfg(feature = "new_rendering")] +use crate::ui::{ + constant, + display::toif::Toif, + geometry::{Alignment, Alignment2D, Offset}, + shape, + shape::render_on_display, + util::version_split, +}; + +#[cfg(feature = "new_rendering")] +use ufmt::uwrite; + +#[cfg(feature = "new_rendering")] +use super::theme::bootloader::BLD_WARN_COLOR; + use intro::Intro; use menu::Menu; @@ -43,6 +63,7 @@ const RECONNECT_MESSAGE: &str = "PLEASE RECONNECT\nTHE DEVICE"; const SCREEN: Rect = ModelTTFeatures::SCREEN; impl ModelTTFeatures { + #[cfg(not(feature = "new_rendering"))] fn screen_progress( text: &str, progress: u16, @@ -70,6 +91,62 @@ impl ModelTTFeatures { } } + #[cfg(feature = "new_rendering")] + fn screen_progress( + text: &str, + progress: u16, + initialize: bool, + fg_color: Color, + bg_color: Color, + icon: Option<(Icon, Color)>, + ) { + if initialize { + ModelTTFeatures::fadeout(); + } + display::sync(); + + render_on_display(None, Some(bg_color), |target| { + shape::Text::new(Point::new(SCREEN.width() / 2, SCREEN.height() - 45), text) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(fg_color) + .render(target); + + let center = SCREEN.center() + Offset::y(-20); + + let inactive_color = bg_color.blend(fg_color, 85); + + shape::Circle::new(center, constant::LOADER_OUTER) + .with_bg(inactive_color) + .render(target); + + shape::Circle::new(center, constant::LOADER_OUTER) + .with_bg(fg_color) + .with_end_angle(((progress as i32 * shape::PI4 as i32 * 8) / 1000) as i16) + .render(target); + + shape::Circle::new(center, constant::LOADER_INNER + 2) + .with_bg(fg_color) + .render(target); + + shape::Circle::new(center, constant::LOADER_INNER) + .with_bg(bg_color) + .render(target); + + if let Some((icon, color)) = icon { + shape::ToifImage::new(center, icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(color) + .render(target); + } + }); + + display::refresh(); + if initialize { + ModelTTFeatures::fadein(); + } + } + fn screen_install_success_bld(msg: &str, complete_draw: bool) { let mut frame = ResultScreen::new( &RESULT_FW_INSTALL, @@ -104,7 +181,7 @@ impl UIFeaturesBootloader for ModelTTFeatures { Point::new(SCREEN.width() / 2, SCREEN.height() - 5), "click to continue ...", Font::NORMAL, - WHITE, + BLD_FG, bg_color, ); } @@ -126,7 +203,6 @@ impl UIFeaturesBootloader for ModelTTFeatures { } else { Self::screen_install_success_bld(reboot_msg.as_str(), complete_draw) } - display::refresh(); } fn screen_install_fail() { @@ -250,6 +326,7 @@ impl UIFeaturesBootloader for ModelTTFeatures { Self::fadeout(); } + #[cfg(not(feature = "new_rendering"))] display::rect_fill(SCREEN, BLACK); let mut frame = WelcomeScreen::new(true); @@ -260,7 +337,6 @@ impl UIFeaturesBootloader for ModelTTFeatures { } else { display::set_backlight(BACKLIGHT_NORMAL); } - display::refresh(); } fn screen_wipe_progress(progress: u16, initialize: bool) { @@ -317,4 +393,90 @@ impl UIFeaturesBootloader for ModelTTFeatures { ); show(&mut frame, true); } + + #[cfg(feature = "new_rendering")] + fn screen_boot( + warning: bool, + vendor_str: Option<&str>, + version: u32, + vendor_img: &[u8], + wait: i32, + ) { + let bg_color = if warning { BLD_WARN_COLOR } else { BLD_BG }; + + display::sync(); + + render_on_display(None, Some(bg_color), |target| { + // Draw vendor image if it's valid and has size of 120x120 + if let Ok(toif) = Toif::new(vendor_img) { + if (toif.width() == 120) && (toif.height() == 120) { + // Image position depends on the vendor string presence + let pos = if vendor_str.is_some() { + Point::new(SCREEN.width() / 2, 30) + } else { + Point::new(SCREEN.width() / 2, 60) + }; + + shape::ToifImage::new(pos, toif) + .with_align(Alignment2D::TOP_CENTER) + .with_fg(BLD_FG) + .render(target); + } + } + + // Draw vendor string if present + if let Some(text) = vendor_str { + let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5 - 50); + shape::Text::new(pos, text) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) //COLOR_BL_BG + .render(target); + + let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5 - 25); + + let mut version_text: BootloaderString = String::new(); + let ver_nums = version_split(version); + unwrap!(uwrite!( + version_text, + "{}.{}.{}", + ver_nums[0], + ver_nums[1], + ver_nums[2] + )); + + shape::Text::new(pos, version_text.as_str()) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) + .render(target); + } + + // Draw a message + match wait.cmp(&0) { + core::cmp::Ordering::Equal => {} + core::cmp::Ordering::Greater => { + let mut text: BootloaderString = String::new(); + unwrap!(uwrite!(text, "starting in {} s", wait)); + + let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5); + shape::Text::new(pos, text.as_str()) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) + .render(target); + } + core::cmp::Ordering::Less => { + let pos = Point::new(SCREEN.width() / 2, SCREEN.height() - 5); + shape::Text::new(pos, "click to continue ...") + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(BLD_FG) + .render(target); + } + } + }); + + display::refresh(); + } } diff --git a/core/embed/rust/src/ui/model_tt/bootloader/welcome.rs b/core/embed/rust/src/ui/model_tt/bootloader/welcome.rs index 5a84bccfe..0e2e3302f 100644 --- a/core/embed/rust/src/ui/model_tt/bootloader/welcome.rs +++ b/core/embed/rust/src/ui/model_tt/bootloader/welcome.rs @@ -1,12 +1,14 @@ use crate::ui::{ component::{Component, Event, EventCtx, Never, Pad}, constant::screen, - display::{self, Font, Icon}, - geometry::{Alignment2D, Offset, Rect}, + display::{self, toif::Toif, Font, Icon}, + geometry::{Alignment, Alignment2D, Offset, Rect}, model_tt::theme::{ bootloader::{START_URL, WELCOME_COLOR}, BLACK, GREY_MEDIUM, WHITE, }, + shape, + shape::Renderer, }; pub struct Welcome { @@ -56,4 +58,26 @@ impl Component for Welcome { BLACK, ); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + + shape::Text::new(screen().top_center() + Offset::y(102), "Get started with") + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(GREY_MEDIUM) + .render(target); + + shape::Text::new(screen().top_center() + Offset::y(126), "your Trezor at") + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(GREY_MEDIUM) + .render(target); + + let icon = unwrap!(Toif::new(START_URL)); + shape::ToifImage::new(screen().top_center() + Offset::y(135), icon) + .with_align(Alignment2D::TOP_CENTER) + .with_fg(WHITE) + .render(target); + } } diff --git a/core/embed/rust/src/ui/model_tt/component/address_details.rs b/core/embed/rust/src/ui/model_tt/component/address_details.rs index b325169b4..851ac5525 100644 --- a/core/embed/rust/src/ui/model_tt/component/address_details.rs +++ b/core/embed/rust/src/ui/model_tt/component/address_details.rs @@ -10,6 +10,7 @@ use crate::{ Component, Event, EventCtx, Paginate, Qr, }, geometry::Rect, + shape::Renderer, }, }; @@ -176,6 +177,14 @@ impl Component for AddressDetails { } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + match self.current_page { + 0 => self.qr_code.render(target), + 1 => self.details.render(target), + _ => self.xpub_view.render(target), + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { match self.current_page { diff --git a/core/embed/rust/src/ui/model_tt/component/bl_confirm.rs b/core/embed/rust/src/ui/model_tt/component/bl_confirm.rs index 9ad7f1517..3bd1de161 100644 --- a/core/embed/rust/src/ui/model_tt/component/bl_confirm.rs +++ b/core/embed/rust/src/ui/model_tt/component/bl_confirm.rs @@ -18,6 +18,8 @@ use crate::{ WHITE, }, }, + shape, + shape::Renderer, }, }; @@ -237,6 +239,40 @@ impl Component for Confirm<'_> { } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + self.content_pad.render(target); + + if let Some(info) = self.info.as_ref() { + if self.show_info { + info.close_button.render(target); + info.title.render(target); + info.text.render(target); + self.left_button.render(target); + self.right_button.render(target); + // short-circuit before painting the main components + return; + } else { + info.info_button.render(target); + // pass through to the rest of the paint + } + } + + self.message.render(target); + self.alert.render(target); + self.left_button.render(target); + self.right_button.render(target); + match &self.title { + ConfirmTitle::Text(label) => label.render(target), + ConfirmTitle::Icon(icon) => { + shape::ToifImage::new(Point::new(screen().center().x, ICON_TOP), icon.toif) + .with_align(Alignment2D::TOP_CENTER) + .with_fg(WHITE) + .render(target); + } + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.left_button.bounds(sink); diff --git a/core/embed/rust/src/ui/model_tt/component/button.rs b/core/embed/rust/src/ui/model_tt/component/button.rs index fdf11ca15..e34f98ff6 100644 --- a/core/embed/rust/src/ui/model_tt/component/button.rs +++ b/core/embed/rust/src/ui/model_tt/component/button.rs @@ -10,6 +10,8 @@ use crate::{ display::{self, toif::Icon, Color, Font}, event::TouchEvent, geometry::{Alignment2D, Insets, Offset, Point, Rect}, + shape, + shape::Renderer, }, }; @@ -187,6 +189,18 @@ impl Button { } } + pub fn render_background<'s>(&self, target: &mut impl Renderer<'s>, style: &ButtonStyle) { + match &self.content { + ButtonContent::IconBlend(_, _, _) => {} + _ => shape::Bar::new(self.area) + .with_bg(style.button_color) + .with_fg(style.border_color) + .with_thickness(style.border_width) + .with_radius(style.border_radius as i16) + .render(target), + } + } + pub fn paint_content(&self, style: &ButtonStyle) { match &self.content { ButtonContent::Empty => {} @@ -225,6 +239,45 @@ impl Button { ), } } + + pub fn render_content<'s>(&self, target: &mut impl Renderer<'s>, style: &ButtonStyle) { + match &self.content { + ButtonContent::Empty => {} + ButtonContent::Text(text) => { + let width = text.map(|c| style.font.text_width(c)); + let height = style.font.text_height(); + let start_of_baseline = self.area.center() + + Offset::new(-width / 2, height / 2) + + Offset::y(Self::BASELINE_OFFSET); + text.map(|text| { + shape::Text::new(start_of_baseline, text) + .with_font(style.font) + .with_fg(style.text_color) + .render(target); + }); + } + ButtonContent::Icon(icon) => { + shape::ToifImage::new(self.area.center(), icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(style.text_color) + .render(target); + } + ButtonContent::IconAndText(child) => { + child.render(target, self.area, self.style(), Self::BASELINE_OFFSET); + } + ButtonContent::IconBlend(bg, fg, offset) => { + shape::Bar::new(self.area) + .with_bg(style.background_color) + .render(target); + shape::ToifImage::new(self.area.top_left(), bg.toif) + .with_fg(style.button_color) + .render(target); + shape::ToifImage::new(self.area.top_left() + *offset, fg.toif) + .with_fg(style.text_color) + .render(target); + } + } + } } impl Component for Button { @@ -313,6 +366,12 @@ impl Component for Button { self.paint_content(style); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let style = self.style(); + self.render_background(target, style); + self.render_content(target, style); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.area); @@ -572,4 +631,52 @@ impl IconText { ); } } + + pub fn render<'s>( + &self, + target: &mut impl Renderer<'s>, + area: Rect, + style: &ButtonStyle, + baseline_offset: i16, + ) { + let width = style.font.text_width(self.text); + let height = style.font.text_height(); + + let mut use_icon = false; + let mut use_text = false; + + let mut icon_pos = Point::new( + area.top_left().x + ((Self::ICON_SPACE + Self::ICON_MARGIN) / 2), + area.center().y, + ); + let mut text_pos = + area.center() + Offset::new(-width / 2, height / 2) + Offset::y(baseline_offset); + + if area.width() > (Self::ICON_SPACE + Self::TEXT_MARGIN + width) { + //display both icon and text + text_pos = Point::new(area.top_left().x + Self::ICON_SPACE, text_pos.y); + use_text = true; + use_icon = true; + } else if area.width() > (width + Self::TEXT_MARGIN) { + use_text = true; + } else { + //if we can't fit the text, retreat to centering the icon + icon_pos = area.center(); + use_icon = true; + } + + if use_text { + shape::Text::new(text_pos, self.text) + .with_font(style.font) + .with_fg(style.text_color) + .render(target); + } + + if use_icon { + shape::ToifImage::new(icon_pos, self.icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(style.text_color) + .render(target); + } + } } diff --git a/core/embed/rust/src/ui/model_tt/component/coinjoin_progress.rs b/core/embed/rust/src/ui/model_tt/component/coinjoin_progress.rs index 7cb492d1d..bfc77d67b 100644 --- a/core/embed/rust/src/ui/model_tt/component/coinjoin_progress.rs +++ b/core/embed/rust/src/ui/model_tt/component/coinjoin_progress.rs @@ -10,8 +10,11 @@ use crate::{ base::Never, painter, Child, Component, ComponentExt, Empty, Event, EventCtx, Label, Split, }, + constant, display::loader::{loader_circular_uncompress, LoaderDimensions}, - geometry::{Insets, Rect}, + geometry::{Insets, Offset, Rect}, + shape, + shape::Renderer, util::animation_disabled, }, }; @@ -122,6 +125,40 @@ where ); self.label.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.content.render(target); + + let center = constant::screen().center() + Offset::y(LOADER_OFFSET); + let active_color = theme::FG; + let background_color = theme::BG; + let inactive_color = background_color.blend(active_color, 85); + + let start = (self.value as i16 - 100) % 1000; + let end = (self.value as i16 + 100) % 1000; + let start = ((start as i32 * 8 * shape::PI4 as i32) / 1000) as i16; + let end = ((end as i32 * 8 * shape::PI4 as i32) / 1000) as i16; + + shape::Circle::new(center, LOADER_OUTER) + .with_bg(inactive_color) + .render(target); + + shape::Circle::new(center, LOADER_OUTER) + .with_bg(active_color) + .with_start_angle(start) + .with_end_angle(end) + .render(target); + + shape::Circle::new(center, LOADER_INNER + 2) + .with_bg(active_color) + .render(target); + + shape::Circle::new(center, LOADER_INNER) + .with_bg(background_color) + .render(target); + + self.label.render(target); + } } #[cfg(feature = "ui_debug")] diff --git a/core/embed/rust/src/ui/model_tt/component/dialog.rs b/core/embed/rust/src/ui/model_tt/component/dialog.rs index 85a46540d..c66d37a91 100644 --- a/core/embed/rust/src/ui/model_tt/component/dialog.rs +++ b/core/embed/rust/src/ui/model_tt/component/dialog.rs @@ -10,6 +10,7 @@ use crate::{ Child, Component, Event, EventCtx, Never, }, geometry::{Insets, LinearPlacement, Rect}, + shape::Renderer, }, }; @@ -71,6 +72,11 @@ where self.controls.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.content.render(target); + self.controls.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.content.bounds(sink); @@ -196,6 +202,12 @@ where self.controls.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.image.render(target); + self.paragraphs.render(target); + self.controls.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.image.bounds(sink); diff --git a/core/embed/rust/src/ui/model_tt/component/error.rs b/core/embed/rust/src/ui/model_tt/component/error.rs index 3769f33c9..12d2bf064 100644 --- a/core/embed/rust/src/ui/model_tt/component/error.rs +++ b/core/embed/rust/src/ui/model_tt/component/error.rs @@ -4,6 +4,8 @@ use crate::{ component::{Child, Component, Event, EventCtx, Label, Never, Pad}, constant::screen, geometry::{Alignment2D, Point, Rect}, + shape, + shape::Renderer, }, }; @@ -89,4 +91,19 @@ impl<'a> Component for ErrorScreen<'a> { self.message.paint(); self.footer.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + + let icon = ICON_WARNING40; + shape::ToifImage::new(Point::new(screen().center().x, ICON_TOP), icon.toif) + .with_fg(WHITE) + .with_bg(FATAL_ERROR_COLOR) + .with_align(Alignment2D::TOP_CENTER) + .render(target); + + self.title.render(target); + self.message.render(target); + self.footer.render(target); + } } diff --git a/core/embed/rust/src/ui/model_tt/component/fido.rs b/core/embed/rust/src/ui/model_tt/component/fido.rs index 4a1b98e50..a1fe452fe 100644 --- a/core/embed/rust/src/ui/model_tt/component/fido.rs +++ b/core/embed/rust/src/ui/model_tt/component/fido.rs @@ -9,6 +9,8 @@ use crate::{ swipe::{Swipe, SwipeDirection}, theme, ScrollBar, }, + shape, + shape::Renderer, }, }; @@ -211,6 +213,36 @@ where } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.icon.render(target); + self.controls.render(target); + self.app_name.render(target); + + if self.scrollbar.page_count > 1 { + self.scrollbar.render(target); + } + + // Erasing the old text content before writing the new one. + let account_name_area = self.account_name.area(); + let real_area = account_name_area + .with_height(account_name_area.height() + self.account_name.font().text_baseline() + 1); + shape::Bar::new(real_area).with_bg(theme::BG).render(target); + + // Account name is optional. + // Showing it only if it differs from app name. + // (Dummy requests usually have some text as both app_name and account_name.) + let account_name = self.account_name.text(); + let app_name = self.app_name.text(); + if !account_name.is_empty() && account_name != app_name { + self.account_name.render(target); + } + + if self.fade.take() { + // Note that this is blocking and takes some time. + display::fade_backlight(theme::BACKLIGHT_NORMAL); + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.icon.bounds(sink); diff --git a/core/embed/rust/src/ui/model_tt/component/frame.rs b/core/embed/rust/src/ui/model_tt/component/frame.rs index 480350dfe..a8e847f71 100644 --- a/core/embed/rust/src/ui/model_tt/component/frame.rs +++ b/core/embed/rust/src/ui/model_tt/component/frame.rs @@ -8,6 +8,7 @@ use crate::{ display::Icon, geometry::{Alignment, Insets, Offset, Rect}, model_tt::component::{Button, ButtonMsg, CancelInfoConfirmMsg}, + shape::Renderer, }, }; @@ -175,6 +176,13 @@ where self.content.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.title.render(target); + self.subtitle.render(target); + self.button.render(target); + self.content.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.title.bounds(sink); diff --git a/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs b/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs index ec5769e77..e829c9d85 100644 --- a/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs +++ b/core/embed/rust/src/ui/model_tt/component/homescreen/mod.rs @@ -1,6 +1,7 @@ mod render; use crate::{ + micropython::gc::Gc, strutil::TString, time::{Duration, Instant}, translations::TR, @@ -9,9 +10,10 @@ use crate::{ component::{Component, Event, EventCtx, Pad, TimerToken}, display::{self, tjpgd::jpeg_info, toif::Icon, Color, Font}, event::{TouchEvent, USBEvent}, - geometry::{Offset, Point, Rect}, + geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect}, layout::util::get_user_custom_image, model_tt::{constant, theme::IMAGE_HOMESCREEN}, + shape::{self, Renderer}, }, }; @@ -20,7 +22,7 @@ use crate::{ ui::{ constant::HEIGHT, display::{ - tjpgd::{jpeg_test, BufferInput}, + tjpgd::BufferInput, toif::{Toif, ToifFormat}, }, model_tt::component::homescreen::render::{ @@ -49,6 +51,7 @@ const LOADER_DURATION: Duration = Duration::from_millis(2000); pub struct Homescreen { label: TString<'static>, notification: Option<(TString<'static>, u8)>, + custom_image: Option>, hold_to_lock: bool, loader: Loader, pad: Pad, @@ -69,6 +72,7 @@ impl Homescreen { Self { label, notification, + custom_image: get_user_custom_image().ok(), hold_to_lock, loader: Loader::with_lock_icon().with_durations(LOADER_DURATION, LOADER_DURATION / 3), pad: Pad::with_background(theme::BG), @@ -119,6 +123,16 @@ impl Homescreen { self.loader.paint() } + fn render_loader<'s>(&'s self, target: &mut impl Renderer<'s>) { + TR::progress__locking_device.map_translated(|t| { + shape::Text::new(TOP_CENTER + Offset::y(HOLD_Y), t) + .with_align(Alignment::Center) + .with_font(Font::NORMAL) + .with_fg(theme::FG); + }); + self.loader.render(target) + } + pub fn set_paint_notification(&mut self) { self.paint_notification_only = true; } @@ -210,10 +224,9 @@ impl Component for Homescreen { let notification = self.get_notification(); - let res = get_user_custom_image(); let mut show_default = true; - if let Ok(data) = res { + if let Some(ref data) = self.custom_image { if is_image_jpeg(data.as_ref()) { let input = BufferInput(data.as_ref()); let mut pool = BufferJpegWork::get_cleared(); @@ -254,6 +267,86 @@ impl Component for Homescreen { } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.pad.render(target); + if self.loader.is_animating() || self.loader.is_completely_grown(Instant::now()) { + self.render_loader(target); + } else { + let img_data = match self.custom_image { + Some(ref img) => img.as_ref(), + None => IMAGE_HOMESCREEN, + }; + + if is_image_jpeg(img_data) { + shape::JpegImage::new(self.pad.area.center(), img_data) + .with_align(Alignment2D::CENTER) + .render(target); + } else if is_image_toif(img_data) { + shape::ToifImage::new(self.pad.area.center(), unwrap!(Toif::new(img_data))) + .with_align(Alignment2D::CENTER) + .render(target); + } + + self.label.map(|t| { + let r = Rect::new(Point::new(6, 198), Point::new(234, 233)); + shape::Bar::new(r) + .with_bg(Color::black()) + .with_alpha(89) + .with_radius(3) + .render(target); + + let style = theme::TEXT_DEMIBOLD; + let pos = Point::new(self.pad.area.center().x, LABEL_Y); + shape::Text::new(pos, t) + .with_align(Alignment::Center) + .with_font(style.text_font) + .with_fg(theme::FG) + .render(target); + }); + + if let Some(notif) = self.get_notification() { + const NOTIFICATION_HEIGHT: i16 = 36; + const NOTIFICATION_BORDER: i16 = 6; + const TEXT_ICON_SPACE: i16 = 8; + + let banner = self + .pad + .area + .inset(Insets::sides(NOTIFICATION_BORDER)) + .with_height(NOTIFICATION_HEIGHT) + .translate(Offset::y(NOTIFICATION_BORDER)); + + shape::Bar::new(banner) + .with_radius(2) + .with_bg(notif.color) + .render(target); + + notif.text.map(|t| { + let style = theme::TEXT_BOLD; + let icon_width = notif.icon.toif.width() + TEXT_ICON_SPACE; + let text_pos = Point::new( + style + .text_font + .horz_center(banner.x0 + icon_width, banner.x1, t), + style.text_font.vert_center(banner.y0, banner.y1, "A"), + ); + + shape::Text::new(text_pos, t) + .with_font(style.text_font) + .with_fg(style.text_color) + .render(target); + + let icon_pos = Point::new(text_pos.x - icon_width, banner.center().y); + + shape::ToifImage::new(icon_pos, notif.icon.toif) + .with_fg(style.text_color) + .with_align(Alignment2D::CENTER_LEFT) + .render(target); + }); + } + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.loader.bounds(sink); @@ -271,6 +364,7 @@ impl crate::trace::Trace for Homescreen { pub struct Lockscreen<'a> { label: TString<'a>, + custom_image: Option>, bootscreen: bool, coinjoin_authorized: bool, } @@ -279,6 +373,7 @@ impl<'a> Lockscreen<'a> { pub fn new(label: TString<'a>, bootscreen: bool, coinjoin_authorized: bool) -> Self { Lockscreen { label, + custom_image: get_user_custom_image().ok(), bootscreen, coinjoin_authorized, } @@ -343,10 +438,9 @@ impl Component for Lockscreen<'_> { texts = &texts[1..]; } - let res = get_user_custom_image(); let mut show_default = true; - if let Ok(data) = res { + if let Some(ref data) = self.custom_image { if is_image_jpeg(data.as_ref()) { let input = BufferInput(data.as_ref()); let mut pool = BufferJpegWork::get_cleared(); @@ -370,10 +464,113 @@ impl Component for Lockscreen<'_> { homescreen_blurred(&mut hs_img, texts); } } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let img_data = match self.custom_image { + Some(ref img) => img.as_ref(), + None => IMAGE_HOMESCREEN, + }; + + let center = constant::screen().center(); + + if is_image_jpeg(img_data) { + shape::JpegImage::new(center, img_data) + .with_align(Alignment2D::CENTER) + .with_blur(4) + .with_dim(140) + .render(target); + } else if is_image_toif(img_data) { + shape::ToifImage::new(center, unwrap!(Toif::new(img_data))) + .with_align(Alignment2D::CENTER) + //.with_blur(5) + .render(target); + } + + let (locked, tap) = if self.bootscreen { + ( + TR::lockscreen__title_not_connected, + TR::lockscreen__tap_to_connect, + ) + } else { + (TR::lockscreen__title_locked, TR::lockscreen__tap_to_unlock) + }; + + let mut label_style = theme::TEXT_DEMIBOLD; + label_style.text_color = theme::GREY_LIGHT; + + let mut texts: &[HomescreenText] = &[ + HomescreenText { + text: "".into(), + style: theme::TEXT_NORMAL, + offset: Offset::new(2, COINJOIN_Y), + icon: Some(theme::ICON_COINJOIN), + }, + HomescreenText { + text: locked.into(), + style: theme::TEXT_BOLD, + offset: Offset::y(LOCKED_Y), + icon: Some(theme::ICON_LOCK), + }, + HomescreenText { + text: tap.into(), + style: theme::TEXT_NORMAL, + offset: Offset::y(TAP_Y), + icon: None, + }, + HomescreenText { + text: self.label, + style: label_style, + offset: Offset::y(LABEL_Y), + icon: None, + }, + ]; + + if !self.coinjoin_authorized { + texts = &texts[1..]; + } + + for item in texts.iter() { + item.text.map(|t| { + const TEXT_ICON_SPACE: i16 = 2; + + let icon_width = match item.icon { + Some(icon) => icon.toif.width() + TEXT_ICON_SPACE, + None => 0, + }; + + let area = constant::screen(); + + let text_pos = Point::new( + item.style + .text_font + .horz_center(area.x0 + icon_width, area.x1, t), + 0, + ) + item.offset; + + shape::Text::new(text_pos, t) + .with_font(item.style.text_font) + .with_fg(item.style.text_color) + .render(target); + + if let Some(icon) = item.icon { + let icon_pos = Point::new(text_pos.x - icon_width, text_pos.y); + shape::ToifImage::new(icon_pos, icon.toif) + .with_align(Alignment2D::BOTTOM_LEFT) + .with_fg(item.style.text_color) + .render(target); + } + }); + } + } } pub fn check_homescreen_format(buffer: &[u8]) -> bool { - is_image_jpeg(buffer) && jpeg_test(buffer) + #[cfg(not(feature = "new_rendering"))] + let result = is_image_jpeg(buffer) && crate::ui::display::tjpgd::jpeg_test(buffer); + #[cfg(feature = "new_rendering")] + let result = is_image_jpeg(buffer); // !@# TODO: test like if `new_rendering` is off + + result } fn is_image_jpeg(buffer: &[u8]) -> bool { diff --git a/core/embed/rust/src/ui/model_tt/component/keyboard/bip39.rs b/core/embed/rust/src/ui/model_tt/component/keyboard/bip39.rs index 0415d985c..2f29ae652 100644 --- a/core/embed/rust/src/ui/model_tt/component/keyboard/bip39.rs +++ b/core/embed/rust/src/ui/model_tt/component/keyboard/bip39.rs @@ -7,13 +7,15 @@ use crate::{ model_tt::{ component::{ keyboard::{ - common::{paint_pending_marker, MultiTapKeyboard}, + common::{paint_pending_marker, render_pending_marker, MultiTapKeyboard}, mnemonic::{MnemonicInput, MnemonicInputMsg, MNEMONIC_KEY_COUNT}, }, Button, ButtonContent, ButtonMsg, }, theme, }, + shape, + shape::Renderer, }, }; use heapless::String; @@ -154,6 +156,51 @@ impl Component for Bip39Input { } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let area = self.button.area(); + let style = self.button.style(); + + // First, paint the button background. + self.button.render_background(target, style); + + // Paint the entered content (the prefix of the suggested word). + let text = self.textbox.content(); + let width = style.font.text_width(text); + // Content starts in the left-center point, offset by 16px to the right and 8px + // to the bottom. + let text_baseline = area.top_left().center(area.bottom_left()) + Offset::new(16, 8); + shape::Text::new(text_baseline, text) + .with_font(style.font) + .with_fg(style.text_color) + .render(target); + + // Paint the rest of the suggested dictionary word. + if let Some(word) = self.suggested_word.and_then(|w| w.get(text.len()..)) { + let word_baseline = text_baseline + Offset::new(width, 0); + let style = self.button_suggestion.style(); + shape::Text::new(word_baseline, word) + .with_font(style.font) + .with_fg(style.text_color) + .render(target); + } + + // Paint the pending marker. + if self.multi_tap.pending_key().is_some() { + render_pending_marker(target, text_baseline, text, style.font, style.text_color); + } + + // Paint the icon. + if let ButtonContent::Icon(icon) = self.button.content() { + // Icon is painted in the right-center point, of expected size 16x16 pixels, and + // 16px from the right edge. + let icon_center = area.top_right().center(area.bottom_right()) - Offset::new(16 + 8, 0); + shape::ToifImage::new(icon_center, icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(style.text_color) + .render(target); + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.button.bounds(sink); diff --git a/core/embed/rust/src/ui/model_tt/component/keyboard/common.rs b/core/embed/rust/src/ui/model_tt/component/keyboard/common.rs index 0220ad4f7..9868770bc 100644 --- a/core/embed/rust/src/ui/model_tt/component/keyboard/common.rs +++ b/core/embed/rust/src/ui/model_tt/component/keyboard/common.rs @@ -4,6 +4,8 @@ use crate::{ component::{text::common::TextEdit, Event, EventCtx, TimerToken}, display::{self, Color, Font}, geometry::{Offset, Point, Rect}, + shape, + shape::Renderer, }, }; @@ -127,3 +129,24 @@ pub fn paint_pending_marker(text_baseline: Point, text: &str, font: Font, color: display::rect_fill(marker_rect, color); } } + +/// Create a visible "underscoring" of the last letter of a text. +pub fn render_pending_marker<'s>( + target: &mut impl Renderer<'s>, + text_baseline: Point, + text: &str, + font: Font, + color: Color, +) { + // Measure the width of the last character of input. + if let Some(last) = text.chars().last() { + let width = font.text_width(text); + let last_width = font.char_width(last); + // Draw the marker 2px under the start of the baseline of the last character. + let marker_origin = text_baseline + Offset::new(width - last_width, 2); + // Draw the marker 1px longer than the last character, and 3px thick. + let marker_rect = + Rect::from_top_left_and_size(marker_origin, Offset::new(last_width + 1, 3)); + shape::Bar::new(marker_rect).with_bg(color).render(target); + } +} diff --git a/core/embed/rust/src/ui/model_tt/component/keyboard/mnemonic.rs b/core/embed/rust/src/ui/model_tt/component/keyboard/mnemonic.rs index 03f9c2315..6d70c89b9 100644 --- a/core/embed/rust/src/ui/model_tt/component/keyboard/mnemonic.rs +++ b/core/embed/rust/src/ui/model_tt/component/keyboard/mnemonic.rs @@ -7,6 +7,7 @@ use crate::{ component::{Button, ButtonMsg, Swipe, SwipeDirection}, theme, }, + shape::Renderer, }, }; @@ -183,6 +184,19 @@ where } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + if self.input.inner().inner().is_empty() { + self.prompt.render(target); + } else { + self.input.render(target); + self.back.render(target); + } + + for btn in &self.keys { + btn.render(target); + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.prompt.bounds(sink); diff --git a/core/embed/rust/src/ui/model_tt/component/keyboard/passphrase.rs b/core/embed/rust/src/ui/model_tt/component/keyboard/passphrase.rs index 01e315a99..151272d67 100644 --- a/core/embed/rust/src/ui/model_tt/component/keyboard/passphrase.rs +++ b/core/embed/rust/src/ui/model_tt/component/keyboard/passphrase.rs @@ -8,10 +8,12 @@ use crate::{ geometry::{Grid, Offset, Rect}, model_tt::component::{ button::{Button, ButtonContent, ButtonMsg}, - keyboard::common::{paint_pending_marker, MultiTapKeyboard}, + keyboard::common::{paint_pending_marker, render_pending_marker, MultiTapKeyboard}, swipe::{Swipe, SwipeDirection}, theme, ScrollBar, }, + shape, + shape::Renderer, util::long_line_content_with_ellipsis, }, }; @@ -299,6 +301,20 @@ impl Component for PassphraseKeyboard { } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.input.render(target); + self.scrollbar.render(target); + self.confirm.render(target); + self.back.render(target); + for btn in &self.keys { + btn.render(target); + } + if self.fade.take() { + // Note that this is blocking and takes some time. + display::fade_backlight(theme::BACKLIGHT_NORMAL); + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.input.bounds(sink); @@ -379,6 +395,40 @@ impl Component for Input { } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let style = theme::label_keyboard(); + + let text_baseline = self.area.top_left() + Offset::y(style.text_font.text_height()) + - Offset::y(style.text_font.text_baseline()); + + let text = self.textbox.content(); + + shape::Bar::new(self.area).with_bg(theme::BG).render(target); + + // Find out how much text can fit into the textbox. + // Accounting for the pending marker, which draws itself one pixel longer than + // the last character + let available_area_width = self.area.width() - 1; + let text_to_display = + long_line_content_with_ellipsis(text, "...", style.text_font, available_area_width); + + shape::Text::new(text_baseline, &text_to_display) + .with_font(style.text_font) + .with_fg(style.text_color) + .render(target); + + // Paint the pending marker. + if self.multi_tap.pending_key().is_some() { + render_pending_marker( + target, + text_baseline, + &text_to_display, + style.text_font, + style.text_color, + ); + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.area) diff --git a/core/embed/rust/src/ui/model_tt/component/keyboard/pin.rs b/core/embed/rust/src/ui/model_tt/component/keyboard/pin.rs index 7c5a74c73..e0a27a83a 100644 --- a/core/embed/rust/src/ui/model_tt/component/keyboard/pin.rs +++ b/core/embed/rust/src/ui/model_tt/component/keyboard/pin.rs @@ -12,11 +12,13 @@ use crate::{ }, display::{self, Font}, event::TouchEvent, - geometry::{Alignment2D, Grid, Insets, Offset, Rect}, + geometry::{Alignment, Alignment2D, Grid, Insets, Offset, Rect}, model_tt::component::{ button::{Button, ButtonContent, ButtonMsg, ButtonMsg::Clicked}, theme, }, + shape, + shape::Renderer, }, }; @@ -264,6 +266,26 @@ impl Component for PinKeyboard<'_> { } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.erase_btn.render(target); + self.textbox_pad.render(target); + if self.textbox.inner().is_empty() { + if let Some(ref w) = self.major_warning { + w.render(target); + } else { + self.major_prompt.render(target); + } + self.minor_prompt.render(target); + self.cancel_btn.render(target); + } else { + self.textbox.render(target); + } + self.confirm_btn.render(target); + for btn in &self.digit_btns { + btn.render(target); + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.major_prompt.bounds(sink); @@ -364,6 +386,27 @@ impl PinDots { } } + fn render_digits<'s>(&self, area: Rect, target: &mut impl Renderer<'s>) { + let center = area.center() + Offset::y(Font::MONO.text_height() / 2); + let right = center + Offset::x(Font::MONO.text_width("0") * (MAX_VISIBLE_DOTS as i16) / 2); + let digits = self.digits.len(); + + if digits <= MAX_VISIBLE_DOTS { + shape::Text::new(center, &self.digits) + .with_align(Alignment::Center) + .with_font(Font::MONO) + .with_fg(self.style.text_color) + .render(target); + } else { + let offset: usize = digits.saturating_sub(MAX_VISIBLE_DIGITS); + shape::Text::new(right, &self.digits[offset..]) + .with_align(Alignment::End) + .with_font(Font::MONO) + .with_fg(self.style.text_color) + .render(target); + } + } + fn paint_dots(&self, area: Rect) { let mut cursor = self.size().snap(area.center(), Alignment2D::CENTER); @@ -407,6 +450,44 @@ impl PinDots { cursor.x += step; } } + + fn render_dots<'s>(&self, area: Rect, target: &mut impl Renderer<'s>) { + let mut cursor = self.size().snap(area.center(), Alignment2D::CENTER); + + let digits = self.digits.len(); + let dots_visible = digits.min(MAX_VISIBLE_DOTS); + let step = Self::DOT + Self::PADDING; + + // Jiggle when overflowed. + if digits > dots_visible && digits % 2 == 0 { + cursor.x += Self::TWITCH + } + + // Small leftmost dot. + if digits > dots_visible + 1 { + shape::ToifImage::new(cursor - Offset::x(2 * step), theme::DOT_SMALL.toif) + .with_align(Alignment2D::TOP_LEFT) + .with_fg(self.style.text_color) + .render(target); + } + + // Greyed out dot. + if digits > dots_visible { + shape::ToifImage::new(cursor - Offset::x(step), theme::DOT_ACTIVE.toif) + .with_align(Alignment2D::TOP_LEFT) + .with_fg(theme::GREY_LIGHT) + .render(target); + } + + // Draw a dot for each PIN digit. + for _ in 0..dots_visible { + shape::ToifImage::new(cursor, theme::DOT_ACTIVE.toif) + .with_align(Alignment2D::TOP_LEFT) + .with_fg(self.style.text_color) + .render(target); + cursor.x += step; + } + } } impl Component for PinDots { @@ -449,6 +530,16 @@ impl Component for PinDots { } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let dot_area = self.area.inset(HEADER_PADDING); + self.pad.render(target); + if self.display_digits { + self.render_digits(dot_area, target) + } else { + self.render_dots(dot_area, target) + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.area); diff --git a/core/embed/rust/src/ui/model_tt/component/keyboard/slip39.rs b/core/embed/rust/src/ui/model_tt/component/keyboard/slip39.rs index 54aa9a702..0794d9c28 100644 --- a/core/embed/rust/src/ui/model_tt/component/keyboard/slip39.rs +++ b/core/embed/rust/src/ui/model_tt/component/keyboard/slip39.rs @@ -14,13 +14,15 @@ use crate::{ model_tt::{ component::{ keyboard::{ - common::{paint_pending_marker, MultiTapKeyboard}, + common::{paint_pending_marker, render_pending_marker, MultiTapKeyboard}, mnemonic::{MnemonicInput, MnemonicInputMsg, MNEMONIC_KEY_COUNT}, }, Button, ButtonContent, ButtonMsg, }, theme, }, + shape, + shape::Renderer, util::ResultExt, }, }; @@ -185,6 +187,71 @@ impl Component for Slip39Input { } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let area = self.button.area(); + let style = self.button.style(); + + // First, paint the button background. + self.button.render_background(target, style); + + // Content starts in the left-center point, offset by 16px to the right and 8px + // to the bottom. + let text_baseline = area.top_left().center(area.bottom_left()) + Offset::new(16, 8); + + // To simplify things, we always copy the printed string here, even if it + // wouldn't be strictly necessary. + let mut text: String = String::new(); + + if let Some(word) = self.final_word { + // We're done with input, paint the full word. + text.push_str(word) + .assert_if_debugging_ui("Text buffer is too small"); + } else { + // Paint an asterisk for each letter of input. + for ch in iter::repeat('*').take(self.textbox.content().len()) { + text.push(ch) + .assert_if_debugging_ui("Text buffer is too small"); + } + // If we're in the pending state, paint the pending character at the end. + if let (Some(key), Some(press)) = + (self.multi_tap.pending_key(), self.multi_tap.pending_press()) + { + assert!(!Self::keys()[key].is_empty()); + // Now we can be sure that the looped iterator will return a value. + let ch = unwrap!(Self::keys()[key].chars().cycle().nth(press)); + text.pop(); + text.push(ch) + .assert_if_debugging_ui("Text buffer is too small"); + } + } + shape::Text::new(text_baseline, text.as_str()) + .with_font(style.font) + .with_fg(style.text_color) + .render(target); + + // Paint the pending marker. + if self.multi_tap.pending_key().is_some() && self.final_word.is_none() { + render_pending_marker( + target, + text_baseline, + text.as_str(), + style.font, + style.text_color, + ); + } + + // Paint the icon. + if let ButtonContent::Icon(icon) = self.button.content() { + // Icon is painted in the right-center point, of expected size 16x16 pixels, and + // 16px from the right edge. + let icon_center = area.top_right().center(area.bottom_right()) - Offset::new(16 + 8, 0); + shape::ToifImage::new(icon_center, icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(style.text_color) + .render(target); + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.button.bounds(sink); diff --git a/core/embed/rust/src/ui/model_tt/component/keyboard/word_count.rs b/core/embed/rust/src/ui/model_tt/component/keyboard/word_count.rs index 453c80938..c6614293e 100644 --- a/core/embed/rust/src/ui/model_tt/component/keyboard/word_count.rs +++ b/core/embed/rust/src/ui/model_tt/component/keyboard/word_count.rs @@ -5,6 +5,7 @@ use crate::ui::{ component::button::{Button, ButtonMsg}, theme, }, + shape::Renderer, }; const NUMBERS: [u32; 5] = [12, 18, 20, 24, 33]; @@ -57,6 +58,12 @@ impl Component for SelectWordCount { } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + for btn in self.button.iter() { + btn.render(target) + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { for btn in self.button.iter() { diff --git a/core/embed/rust/src/ui/model_tt/component/loader.rs b/core/embed/rust/src/ui/model_tt/component/loader.rs index ebac3b3aa..0f36ed467 100644 --- a/core/embed/rust/src/ui/model_tt/component/loader.rs +++ b/core/embed/rust/src/ui/model_tt/component/loader.rs @@ -6,8 +6,9 @@ use crate::{ animation::Animation, component::{Component, Event, EventCtx, Pad}, display::{self, toif::Icon, Color}, - geometry::{Offset, Rect}, + geometry::{Alignment2D, Offset, Rect}, model_tt::constant, + shape::{self, Renderer}, util::animation_disabled, }, }; @@ -206,6 +207,53 @@ impl Component for Loader { ); } } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + // TODO: Consider passing the current instant along with the event -- that way, + // we could synchronize painting across the component tree. Also could be useful + // in automated tests. + // In practice, taking the current instant here is more precise in case some + // other component in the tree takes a long time to draw. + let now = Instant::now(); + + if let Some(progress) = self.progress(now) { + let style = if progress < display::LOADER_MAX { + self.styles.normal + } else { + self.styles.active + }; + + self.pad.render(target); + + let center = self.pad.area.center(); + + let inactive_color = Color::black().blend(style.loader_color, 85); + + shape::Circle::new(center, constant::LOADER_OUTER) + .with_bg(inactive_color) + .render(target); + + shape::Circle::new(center, constant::LOADER_OUTER) + .with_bg(style.loader_color) + .with_end_angle(((progress as i32 * shape::PI4 as i32 * 8) / 1000) as i16) + .render(target); + + shape::Circle::new(center, constant::LOADER_INNER + 2) + .with_bg(style.loader_color) + .render(target); + + shape::Circle::new(center, constant::LOADER_INNER) + .with_bg(style.background_color) + .render(target); + + if let Some((icon, color)) = style.icon { + shape::ToifImage::new(center, icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(color) + .render(target); + } + } + } } pub struct LoaderStyleSheet { diff --git a/core/embed/rust/src/ui/model_tt/component/number_input.rs b/core/embed/rust/src/ui/model_tt/component/number_input.rs index 2cf6c6caf..07e855cfb 100644 --- a/core/embed/rust/src/ui/model_tt/component/number_input.rs +++ b/core/embed/rust/src/ui/model_tt/component/number_input.rs @@ -10,7 +10,8 @@ use crate::{ Child, Component, Event, EventCtx, Pad, }, display::{self, Font}, - geometry::{Grid, Insets, Offset, Rect}, + geometry::{Alignment, Grid, Insets, Offset, Rect}, + shape::{self, Renderer}, }, }; @@ -120,6 +121,14 @@ where self.confirm_button.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.input.render(target); + self.paragraphs_pad.render(target); + self.paragraphs.render(target); + self.info_button.render(target); + self.confirm_button.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.area); @@ -227,6 +236,25 @@ impl Component for NumberInput { self.inc.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let mut buf = [0u8; 10]; + + if let Some(text) = strutil::format_i64(self.value as i64, &mut buf) { + let digit_font = Font::DEMIBOLD; + let y_offset = digit_font.text_height() / 2 + Button::BASELINE_OFFSET; + + shape::Bar::new(self.area).with_bg(theme::BG).render(target); + shape::Text::new(self.area.center() + Offset::y(y_offset), text) + .with_align(Alignment::Center) + .with_fg(theme::FG) + .with_font(digit_font) + .render(target); + } + + self.dec.render(target); + self.inc.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { self.dec.bounds(sink); diff --git a/core/embed/rust/src/ui/model_tt/component/page.rs b/core/embed/rust/src/ui/model_tt/component/page.rs index c8fa2756b..6a334f830 100644 --- a/core/embed/rust/src/ui/model_tt/component/page.rs +++ b/core/embed/rust/src/ui/model_tt/component/page.rs @@ -8,6 +8,7 @@ use crate::{ constant, display::{self, Color}, geometry::{Insets, Rect}, + shape::Renderer, util::animation_disabled, }, }; @@ -17,6 +18,8 @@ use super::{ SwipeDirection, }; +use core::cell::Cell; + /// Allows pagination of inner component. Shows scroll bar, confirm & cancel /// buttons. Optionally handles hold-to-confirm with loader. pub struct ButtonPage { @@ -40,7 +43,7 @@ pub struct ButtonPage { /// Whether to pass-through right swipe to parent component. swipe_right: bool, /// Fade to given backlight level on next paint(). - fade: Option, + fade: Cell>, } impl ButtonPage @@ -75,7 +78,7 @@ where cancel_from_any_page: false, swipe_left: false, swipe_right: false, - fade: None, + fade: Cell::new(None), } } @@ -159,7 +162,7 @@ where // Swipe has dimmed the screen, so fade back to normal backlight after the next // paint. - self.fade = Some(theme::BACKLIGHT_NORMAL); + self.fade.set(Some(theme::BACKLIGHT_NORMAL)); } fn is_cancel_visible(&self) -> bool { @@ -414,6 +417,33 @@ where } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.pad.render(target); + match &self.loader { + Some(l) if l.is_animating() => self.loader.render(target), + _ => { + self.content.render(target); + if self.scrollbar.has_pages() { + self.scrollbar.render(target); + } + } + } + if self.button_cancel.is_some() && self.is_cancel_visible() { + self.button_cancel.render(target); + } else { + self.button_prev.render(target); + } + if self.scrollbar.has_next_page() { + self.button_next.render(target); + } else { + self.button_confirm.render(target); + } + if let Some(val) = self.fade.take() { + // Note that this is blocking and takes some time. + display::fade_backlight(val); + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.pad.area); diff --git a/core/embed/rust/src/ui/model_tt/component/progress.rs b/core/embed/rust/src/ui/model_tt/component/progress.rs index 048bbc40a..bdb8f7fa7 100644 --- a/core/embed/rust/src/ui/model_tt/component/progress.rs +++ b/core/embed/rust/src/ui/model_tt/component/progress.rs @@ -10,8 +10,10 @@ use crate::{ Child, Component, Event, EventCtx, Label, Never, Pad, }, display::{self, Font}, - geometry::{Insets, Rect}, + geometry::{Insets, Offset, Rect}, model_tt::constant, + shape, + shape::Renderer, util::animation_disabled, }, }; @@ -106,6 +108,47 @@ impl Component for Progress { self.description.paint(); } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.title.render(target); + + let center = constant::screen().center() + Offset::y(self.loader_y_offset); + let active_color = theme::FG; + let background_color = theme::BG; + let inactive_color = background_color.blend(active_color, 85); + + let (start, end) = if self.indeterminate { + let start = (self.value as i16 - 100) % 1000; + let end = (self.value as i16 + 100) % 1000; + let start = ((start as i32 * 8 * shape::PI4 as i32) / 1000) as i16; + let end = ((end as i32 * 8 * shape::PI4 as i32) / 1000) as i16; + (start, end) + } else { + let end = ((self.value as i32 * 8 * shape::PI4 as i32) / 1000) as i16; + (0, end) + }; + + shape::Circle::new(center, constant::LOADER_OUTER) + .with_bg(inactive_color) + .render(target); + + shape::Circle::new(center, constant::LOADER_OUTER) + .with_bg(active_color) + .with_start_angle(start) + .with_end_angle(end) + .render(target); + + shape::Circle::new(center, constant::LOADER_INNER + 2) + .with_bg(active_color) + .render(target); + + shape::Circle::new(center, constant::LOADER_INNER) + .with_bg(background_color) + .render(target); + + self.description_pad.render(target); + self.description.render(target); + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(Self::AREA); diff --git a/core/embed/rust/src/ui/model_tt/component/result.rs b/core/embed/rust/src/ui/model_tt/component/result.rs index f5bb2f215..4317bd6a3 100644 --- a/core/embed/rust/src/ui/model_tt/component/result.rs +++ b/core/embed/rust/src/ui/model_tt/component/result.rs @@ -6,6 +6,8 @@ use crate::{ display::{self, Color, Font, Icon}, geometry::{Alignment2D, Insets, Offset, Point, Rect}, model_tt::theme::FG, + shape, + shape::Renderer, }, }; @@ -95,6 +97,20 @@ impl Component for ResultFooter<'_> { // footer text self.text.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + // divider line + let bar = Rect::from_center_and_size( + Point::new(self.area.center().x, self.area.y0), + Offset::new(self.area.width(), 1), + ); + shape::Bar::new(bar) + .with_fg(self.style.divider_color) + .render(target); + + // footer text + self.text.render(target); + } } pub struct ResultScreen<'a> { @@ -166,4 +182,21 @@ impl<'a> Component for ResultScreen<'a> { self.message.paint(); self.footer.paint(); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.bg.render(target); + self.footer_pad.render(target); + + shape::ToifImage::new( + Point::new(screen().center().x, ICON_CENTER_Y), + self.icon.toif, + ) + .with_align(Alignment2D::CENTER) + .with_fg(self.style.fg_color) + .with_bg(self.style.bg_color) + .render(target); + + self.message.render(target); + self.footer.render(target); + } } diff --git a/core/embed/rust/src/ui/model_tt/component/scroll.rs b/core/embed/rust/src/ui/model_tt/component/scroll.rs index b6267fcbf..1ea96fd96 100644 --- a/core/embed/rust/src/ui/model_tt/component/scroll.rs +++ b/core/embed/rust/src/ui/model_tt/component/scroll.rs @@ -2,6 +2,8 @@ use crate::ui::{ component::{Component, Event, EventCtx, Never}, display::toif::Icon, geometry::{Alignment2D, Axis, LinearPlacement, Offset, Rect}, + shape, + shape::Renderer, }; use super::theme; @@ -122,6 +124,49 @@ impl Component for ScrollBar { } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + fn dotsize(distance: usize, nhidden: usize) -> Icon { + match (nhidden.saturating_sub(distance)).min(2 - distance) { + 0 => theme::DOT_INACTIVE, + 1 => theme::DOT_INACTIVE_HALF, + _ => theme::DOT_INACTIVE_QUARTER, + } + } + + // Number of visible dots. + let num_shown = self.page_count.min(Self::MAX_DOTS); + // Page indices corresponding to the first (and last) dot. + let first_shown = self + .active_page + .saturating_sub(Self::MAX_DOTS / 2) + .min(self.page_count.saturating_sub(Self::MAX_DOTS)); + let last_shown = first_shown + num_shown - 1; + + let mut cursor = self.area.center() + - Offset::on_axis( + self.layout.axis, + Self::DOT_INTERVAL * (num_shown.saturating_sub(1) as i16) / 2, + ); + for i in first_shown..(last_shown + 1) { + let icon = if i == self.active_page { + theme::DOT_ACTIVE + } else if i <= first_shown + 1 { + let before_first_shown = first_shown; + dotsize(i - first_shown, before_first_shown) + } else if i >= last_shown - 1 { + let after_last_shown = self.page_count - 1 - last_shown; + dotsize(last_shown - i, after_last_shown) + } else { + theme::DOT_INACTIVE + }; + shape::ToifImage::new(cursor, icon.toif) + .with_align(Alignment2D::CENTER) + .with_fg(theme::FG) + .render(target); + cursor = cursor + Offset::on_axis(self.layout.axis, Self::DOT_INTERVAL); + } + } + fn place(&mut self, bounds: Rect) -> Rect { self.area = bounds; bounds diff --git a/core/embed/rust/src/ui/model_tt/component/simple_page.rs b/core/embed/rust/src/ui/model_tt/component/simple_page.rs index 92bb4c53a..160c049c5 100644 --- a/core/embed/rust/src/ui/model_tt/component/simple_page.rs +++ b/core/embed/rust/src/ui/model_tt/component/simple_page.rs @@ -2,6 +2,7 @@ use crate::ui::{ component::{base::ComponentExt, Component, Event, EventCtx, Pad, PageMsg, Paginate}, display::{self, Color}, geometry::{Axis, Insets, Rect}, + shape::Renderer, }; use super::{theme, ScrollBar, Swipe, SwipeDirection}; @@ -165,6 +166,18 @@ where } } + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + self.pad.render(target); + self.content.render(target); + if self.scrollbar.has_pages() { + self.scrollbar.render(target); + } + if let Some(val) = self.fade.take() { + // Note that this is blocking and takes some time. + display::fade_backlight(val); + } + } + #[cfg(feature = "ui_bounds")] fn bounds(&self, sink: &mut dyn FnMut(Rect)) { sink(self.pad.area); diff --git a/core/embed/rust/src/ui/model_tt/component/swipe.rs b/core/embed/rust/src/ui/model_tt/component/swipe.rs index b43a2fd22..b4cab1d2e 100644 --- a/core/embed/rust/src/ui/model_tt/component/swipe.rs +++ b/core/embed/rust/src/ui/model_tt/component/swipe.rs @@ -3,6 +3,7 @@ use crate::ui::{ display, event::TouchEvent, geometry::{Point, Rect}, + shape::Renderer, }; use super::theme; @@ -159,4 +160,6 @@ impl Component for Swipe { } fn paint(&mut self) {} + + fn render<'s>(&'s self, _target: &mut impl Renderer<'s>) {} } diff --git a/core/embed/rust/src/ui/model_tt/component/welcome_screen.rs b/core/embed/rust/src/ui/model_tt/component/welcome_screen.rs index b0526f8aa..2a359ede1 100644 --- a/core/embed/rust/src/ui/model_tt/component/welcome_screen.rs +++ b/core/embed/rust/src/ui/model_tt/component/welcome_screen.rs @@ -2,16 +2,24 @@ use crate::ui::{ component::{Component, Event, EventCtx, Never}, geometry::{Alignment2D, Offset, Rect}, model_tt::theme, + shape, + shape::Renderer, }; #[cfg(feature = "bootloader")] -use crate::ui::{display::Icon, model_tt::theme::bootloader::DEVICE_NAME}; +use crate::ui::{ + display::{toif::Toif, Icon}, + model_tt::theme::bootloader::DEVICE_NAME, +}; const TEXT_BOTTOM_MARGIN: i16 = 24; // matching the homescreen label margin const ICON_TOP_MARGIN: i16 = 48; #[cfg(not(feature = "bootloader"))] const MODEL_NAME_FONT: display::Font = display::Font::DEMIBOLD; #[cfg(not(feature = "bootloader"))] -use crate::{trezorhal::model, ui::display}; +use crate::{ + trezorhal::model, + ui::{display, geometry::Alignment}, +}; pub struct WelcomeScreen { area: Rect, @@ -67,6 +75,41 @@ impl Component for WelcomeScreen { theme::BG, ); } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + let logo = if self.empty_lock { + theme::ICON_LOGO_EMPTY + } else { + theme::ICON_LOGO + }; + shape::ToifImage::new( + self.area.top_center() + Offset::y(ICON_TOP_MARGIN), + logo.toif, + ) + .with_align(Alignment2D::TOP_CENTER) + .with_fg(theme::FG) + .with_bg(theme::BG) + .render(target); + + #[cfg(not(feature = "bootloader"))] + shape::Text::new( + self.area.bottom_center() - Offset::y(TEXT_BOTTOM_MARGIN), + model::FULL_NAME, + ) + .with_align(Alignment::Center) + .with_font(MODEL_NAME_FONT) + .with_fg(theme::FG) + .render(target); + + #[cfg(feature = "bootloader")] + shape::ToifImage::new( + self.area.bottom_center() - Offset::y(TEXT_BOTTOM_MARGIN), + unwrap!(Toif::new(DEVICE_NAME)), + ) + .with_align(Alignment2D::BOTTOM_CENTER) + .with_fg(theme::FG) + .render(target); + } } #[cfg(feature = "ui_debug")] diff --git a/core/embed/rust/src/ui/model_tt/constant.rs b/core/embed/rust/src/ui/model_tt/constant.rs index 3d4f7005b..914a6ac42 100644 --- a/core/embed/rust/src/ui/model_tt/constant.rs +++ b/core/embed/rust/src/ui/model_tt/constant.rs @@ -7,8 +7,13 @@ pub const HEIGHT: i16 = DISPLAY_RESY as _; pub const LINE_SPACE: i16 = 4; pub const FONT_BPP: i16 = 4; +#[cfg(not(feature = "new_rendering"))] pub const LOADER_OUTER: i16 = 60; +#[cfg(feature = "new_rendering")] +pub const LOADER_OUTER: i16 = 59; + pub const LOADER_INNER: i16 = 42; + pub const LOADER_ICON_MAX_SIZE: i16 = 64; pub const fn size() -> Offset { diff --git a/core/embed/rust/src/ui/model_tt/screens.rs b/core/embed/rust/src/ui/model_tt/screens.rs index b21d4d2bc..0446b773c 100644 --- a/core/embed/rust/src/ui/model_tt/screens.rs +++ b/core/embed/rust/src/ui/model_tt/screens.rs @@ -8,9 +8,19 @@ use crate::ui::{ }, }; +#[cfg(feature = "new_rendering")] +use crate::ui::{display::Color, shape::render_on_display}; + pub fn screen_fatal_error(title: &str, msg: &str, footer: &str) { let mut frame = ErrorScreen::new(title.into(), msg.into(), footer.into()); frame.place(constant::screen()); + + #[cfg(feature = "new_rendering")] + render_on_display(None, Some(Color::black()), |target| { + frame.render(target); + }); + + #[cfg(not(feature = "new_rendering"))] frame.paint(); display::refresh(); } @@ -18,7 +28,20 @@ pub fn screen_fatal_error(title: &str, msg: &str, footer: &str) { pub fn screen_boot_stage_2() { let mut frame = WelcomeScreen::new(false); frame.place(screen()); - display::sync(); - frame.paint(); - display::refresh(); + + #[cfg(feature = "new_rendering")] + { + display::sync(); + render_on_display(None, Some(Color::black()), |target| { + frame.render(target); + }); + display::refresh(); + } + + #[cfg(not(feature = "new_rendering"))] + { + display::sync(); + frame.paint(); + display::refresh(); + } } diff --git a/core/embed/rust/src/ui/ui_features.rs b/core/embed/rust/src/ui/ui_features.rs index 2089d05e6..c927c6dff 100644 --- a/core/embed/rust/src/ui/ui_features.rs +++ b/core/embed/rust/src/ui/ui_features.rs @@ -54,6 +54,15 @@ pub trait UIFeaturesBootloader { fn screen_wipe_success(); fn screen_wipe_fail(); + + #[cfg(feature = "new_rendering")] + fn screen_boot( + warning: bool, + vendor_str: Option<&str>, + version: u32, + vendor_img: &[u8], + wait: i32, + ); } #[cfg(all( From 4b013092168e8f58971ea3a71278b2d5ea76b788 Mon Sep 17 00:00:00 2001 From: cepetr Date: Tue, 12 Mar 2024 10:45:43 +0100 Subject: [PATCH 07/14] WIP - replace painter by jpeg and bar components --- core/embed/rust/src/ui/component/bar.rs | 62 ++++++++++++ core/embed/rust/src/ui/component/jpeg.rs | 98 +++++++++++++++++++ core/embed/rust/src/ui/component/mod.rs | 10 +- core/embed/rust/src/ui/component/painter.rs | 88 ----------------- .../src/ui/model_tr/component/homescreen.rs | 1 + .../model_tt/component/coinjoin_progress.rs | 5 +- core/embed/rust/src/ui/model_tt/layout.rs | 39 +++----- 7 files changed, 184 insertions(+), 119 deletions(-) create mode 100644 core/embed/rust/src/ui/component/bar.rs create mode 100644 core/embed/rust/src/ui/component/jpeg.rs delete mode 100644 core/embed/rust/src/ui/component/painter.rs diff --git a/core/embed/rust/src/ui/component/bar.rs b/core/embed/rust/src/ui/component/bar.rs new file mode 100644 index 000000000..604d382ca --- /dev/null +++ b/core/embed/rust/src/ui/component/bar.rs @@ -0,0 +1,62 @@ +use crate::ui::{ + component::{Component, Event, EventCtx, Never}, + display, + display::Color, + geometry::Rect, + shape, + shape::Renderer, +}; + +pub struct Bar { + area: Rect, + color: Color, + bg_color: Color, + radius: i16, +} + +impl Bar { + pub fn new(color: Color, bg_color: Color, radius: i16) -> Self { + Self { + area: Rect::zero(), + color, + bg_color, + radius, + } + } +} + +impl Component for Bar { + type Msg = Never; + + fn place(&mut self, bounds: Rect) -> Rect { + self.area = bounds; + self.area + } + + fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option { + None + } + + fn paint(&mut self) { + display::rect_fill_rounded(self.area, self.color, self.bg_color, self.radius as u8); + } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + shape::Bar::new(self.area) + .with_bg(self.color) + .with_radius(self.radius) + .render(target); + } + + #[cfg(feature = "ui_bounds")] + fn bounds(&self, sink: &mut dyn FnMut(Rect)) { + sink(self.area) + } +} + +#[cfg(feature = "ui_debug")] +impl crate::trace::Trace for Bar { + fn trace(&self, t: &mut dyn crate::trace::Tracer) { + t.component("Bar"); + } +} diff --git a/core/embed/rust/src/ui/component/jpeg.rs b/core/embed/rust/src/ui/component/jpeg.rs new file mode 100644 index 000000000..fd504b9aa --- /dev/null +++ b/core/embed/rust/src/ui/component/jpeg.rs @@ -0,0 +1,98 @@ +use crate::{ + error::Error, + micropython::{buffer::get_buffer, obj::Obj}, +}; + +use crate::ui::{ + component::{Component, Event, EventCtx, Never}, + display, + geometry::{Alignment2D, Offset, Rect}, + shape, + shape::Renderer, +}; + +pub enum ImageBuffer { + Object { obj: Obj }, + Slice { data: &'static [u8] }, +} + +impl ImageBuffer { + pub fn from_object(obj: Obj) -> Result { + if !obj.is_bytes() { + return Err(Error::TypeError); + } + Ok(ImageBuffer::Object { obj }) + } + + pub fn from_slice(data: &'static [u8]) -> Self { + ImageBuffer::Slice { data } + } + + pub fn is_empty(&self) -> bool { + self.data().is_empty() + } + + pub fn data(&self) -> &[u8] { + match self { + // SAFETY: We expect no existing mutable reference. Resulting reference is + // discarded before returning to micropython. + ImageBuffer::Object { obj } => unsafe { unwrap!(get_buffer(*obj)) }, + ImageBuffer::Slice { data } => data, + } + } +} + +pub struct Jpeg { + area: Rect, + data: ImageBuffer, + scale: u8, +} + +impl Jpeg { + pub fn new(data: ImageBuffer, scale: u8) -> Self { + Self { + area: Rect::zero(), + data, + scale, + } + } +} + +impl Component for Jpeg { + type Msg = Never; + + fn place(&mut self, bounds: Rect) -> Rect { + self.area = bounds; + self.area + } + + fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option { + None + } + + fn paint(&mut self) { + if let Some((size, _)) = display::tjpgd::jpeg_info(self.data.data()) { + let off = Offset::new(size.x / (2 << self.scale), size.y / (2 << self.scale)); + display::tjpgd::jpeg(self.data.data(), self.area.center() - off, self.scale); + } + } + + fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { + shape::JpegImage::new(self.area.center(), self.data.data()) + .with_align(Alignment2D::CENTER) + .with_scale(self.scale) + .render(target); + } + + #[cfg(feature = "ui_bounds")] + fn bounds(&self, sink: &mut dyn FnMut(Rect)) { + sink(self.area) + } +} + +#[cfg(feature = "ui_debug")] +impl crate::trace::Trace for Jpeg { + fn trace(&self, t: &mut dyn crate::trace::Tracer) { + t.component("Jpeg"); + } +} diff --git a/core/embed/rust/src/ui/component/mod.rs b/core/embed/rust/src/ui/component/mod.rs index bc54734d9..0859d6858 100644 --- a/core/embed/rust/src/ui/component/mod.rs +++ b/core/embed/rust/src/ui/component/mod.rs @@ -1,32 +1,36 @@ -#![forbid(unsafe_code)] +//#![forbid(unsafe_code)] +pub mod bar; pub mod base; pub mod border; pub mod connect; pub mod empty; pub mod image; +#[cfg(all(feature = "jpeg", feature = "micropython"))] +pub mod jpeg; pub mod label; pub mod map; pub mod marquee; pub mod maybe; pub mod pad; pub mod paginated; -pub mod painter; pub mod placed; pub mod qr_code; pub mod text; pub mod timeout; +pub use bar::Bar; pub use base::{Child, Component, ComponentExt, Event, EventCtx, Never, Root, TimerToken}; pub use border::Border; pub use empty::Empty; +#[cfg(all(feature = "jpeg", feature = "micropython"))] +pub use jpeg::Jpeg; pub use label::Label; pub use map::MsgMap; pub use marquee::Marquee; pub use maybe::Maybe; pub use pad::Pad; pub use paginated::{PageMsg, Paginate}; -pub use painter::Painter; pub use placed::{FixedHeightBar, Floating, GridPlaced, Split}; pub use qr_code::Qr; pub use text::{ diff --git a/core/embed/rust/src/ui/component/painter.rs b/core/embed/rust/src/ui/component/painter.rs deleted file mode 100644 index d361ef177..000000000 --- a/core/embed/rust/src/ui/component/painter.rs +++ /dev/null @@ -1,88 +0,0 @@ -#[cfg(feature = "jpeg")] -use crate::ui::geometry::Offset; -use crate::ui::{ - component::{image::Image, Component, Event, EventCtx, Never}, - display, - geometry::{Alignment2D, Rect}, - shape, - shape::Renderer, -}; - -pub struct Painter { - area: Rect, - func: F, -} - -impl Painter { - pub fn new(func: F) -> Self { - Self { - func, - area: Rect::zero(), - } - } -} - -impl Component for Painter -where - F: FnMut(Rect), -{ - type Msg = Never; - - fn place(&mut self, bounds: Rect) -> Rect { - self.area = bounds; - self.area - } - - fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option { - None - } - - fn paint(&mut self) { - (self.func)(self.area); - } - - fn render<'s>(&'s self, target: &mut impl Renderer<'s>) { - let area = self.area; - shape::Bar::new(area) - .with_thickness(1) - .with_fg(display::Color::white()) - .render(target); - shape::Text::new(area.top_left(), "Paint").render(target); // !@# replace - } - - #[cfg(feature = "ui_bounds")] - fn bounds(&self, sink: &mut dyn FnMut(Rect)) { - sink(self.area) - } -} - -#[cfg(feature = "ui_debug")] -impl crate::trace::Trace for Painter { - fn trace(&self, t: &mut dyn crate::trace::Tracer) { - t.component("Painter"); - } -} - -pub fn image_painter(image: Image) -> Painter { - let f = move |area: Rect| image.draw(area.center(), Alignment2D::CENTER); - Painter::new(f) -} - -#[cfg(feature = "jpeg")] -pub fn jpeg_painter<'a>( - image: impl Fn() -> &'a [u8], - size: Offset, - scale: u8, -) -> Painter { - let off = Offset::new(size.x / (2 << scale), size.y / (2 << scale)); - #[cfg(not(feature = "new_rendering"))] - let f = move |area: Rect| display::tjpgd::jpeg(image(), area.center() - off, scale); - #[cfg(feature = "new_rendering")] - let f = move |area: Rect| {}; - Painter::new(f) -} - -pub fn rect_painter(fg: display::Color, bg: display::Color) -> Painter { - let f = move |area: Rect| display::rect_fill_rounded(area, fg, bg, 2); - Painter::new(f) -} diff --git a/core/embed/rust/src/ui/model_tr/component/homescreen.rs b/core/embed/rust/src/ui/model_tr/component/homescreen.rs index 5205961e2..415eb3381 100644 --- a/core/embed/rust/src/ui/model_tr/component/homescreen.rs +++ b/core/embed/rust/src/ui/model_tr/component/homescreen.rs @@ -1,4 +1,5 @@ use crate::{ + micropython::{buffer::get_buffer, gc::Gc, obj::Obj}, strutil::TString, translations::TR, trezorhal::usb::usb_configured, diff --git a/core/embed/rust/src/ui/model_tt/component/coinjoin_progress.rs b/core/embed/rust/src/ui/model_tt/component/coinjoin_progress.rs index bfc77d67b..d53d075b1 100644 --- a/core/embed/rust/src/ui/model_tt/component/coinjoin_progress.rs +++ b/core/embed/rust/src/ui/model_tt/component/coinjoin_progress.rs @@ -7,8 +7,7 @@ use crate::{ translations::TR, ui::{ component::{ - base::Never, painter, Child, Component, ComponentExt, Empty, Event, EventCtx, Label, - Split, + base::Never, Bar, Child, Component, ComponentExt, Empty, Event, EventCtx, Label, Split, }, constant, display::loader::{loader_circular_uncompress, LoaderDimensions}, @@ -45,7 +44,7 @@ impl CoinJoinProgress { let style = theme::label_coinjoin_progress(); let label = Label::centered(TR::coinjoin__title_do_not_disconnect.into(), style) .vertically_centered(); - let bg = painter::rect_painter(style.background_color, theme::BG); + let bg = Bar::new(style.background_color, theme::BG, 2); let inner = (bg, label); CoinJoinProgress::with_background(text, inner, indeterminate) } diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index 2bcb17728..f384c85c7 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -14,8 +14,8 @@ use crate::{ base::ComponentExt, connect::Connect, image::BlendedImage, + jpeg::{ImageBuffer, Jpeg}, paginated::{PageMsg, Paginate}, - painter, placed::GridPlaced, text::{ op::OpTextLayout, @@ -196,10 +196,7 @@ where } } -impl ComponentMsgObj for painter::Painter -where - F: FnMut(geometry::Rect), -{ +impl ComponentMsgObj for Jpeg { fn msg_try_into_obj(&self, _msg: Self::Msg) -> Result { unreachable!() } @@ -617,33 +614,25 @@ extern "C" fn new_confirm_properties(n_args: usize, args: *const Obj, kwargs: *m extern "C" fn new_confirm_homescreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - let data: Obj = kwargs.get(Qstr::MP_QSTR_image)?; - - // Layout needs to hold the Obj to play nice with GC. Obj is resolved to &[u8] - // in every paint pass. - let buffer_func = move || { - // SAFETY: We expect no existing mutable reference. Resulting reference is - // discarded before returning to micropython. - let buffer = unsafe { unwrap!(get_buffer(data)) }; - // Incoming data may be empty, meaning we should display default homescreen - // image. - if buffer.is_empty() { - theme::IMAGE_HOMESCREEN - } else { - buffer - } - }; + let image: Obj = kwargs.get(Qstr::MP_QSTR_image)?; + + let mut jpeg = unwrap!(ImageBuffer::from_object(image)); + + if jpeg.is_empty() { + // Incoming data may be empty, meaning we should + // display default homescreen image. + jpeg = ImageBuffer::from_slice(theme::IMAGE_HOMESCREEN); + } - let size = match jpeg_info(buffer_func()) { - Some(info) => info.0, - _ => return Err(value_error!("Invalid image.")), + if jpeg_info(jpeg.data()).is_none() { + return Err(value_error!("Invalid image.")); }; let buttons = Button::cancel_confirm_text(None, Some(TR::buttons__change.into())); let obj = LayoutObj::new(Frame::centered( theme::label_title(), title, - Dialog::new(painter::jpeg_painter(buffer_func, size, 1), buttons), + Dialog::new(Jpeg::new(jpeg, 1), buttons), ))?; Ok(obj.into()) }; From c1c7f3e659532f91f3466e5f523260f5160b617d Mon Sep 17 00:00:00 2001 From: cepetr Date: Wed, 24 Apr 2024 15:49:44 +0200 Subject: [PATCH 08/14] fixup! feat(core): introduce new drawing library --- core/embed/rust/src/trezorhal/bitblt.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/embed/rust/src/trezorhal/bitblt.rs b/core/embed/rust/src/trezorhal/bitblt.rs index 49eef2751..30756a19c 100644 --- a/core/embed/rust/src/trezorhal/bitblt.rs +++ b/core/embed/rust/src/trezorhal/bitblt.rs @@ -204,12 +204,12 @@ impl BitBlt { unsafe { ffi::gl_mono8_blend_mono4(self) }; } - #[cfg(feature = "new_rendering")] + #[cfg(all(not(feature = "xframebuffer"), feature = "new_rendering"))] pub unsafe fn display_fill(&self) { unsafe { ffi::display_fill(self) }; } - #[cfg(feature = "new_rendering")] + #[cfg(all(not(feature = "xframebuffer"), feature = "new_rendering"))] pub unsafe fn display_copy_rgb565(&self) { unsafe { ffi::display_copy_rgb565(self) }; } From d6bdaa93326ebaf03249a9899595b5ad04323f14 Mon Sep 17 00:00:00 2001 From: cepetr Date: Wed, 24 Apr 2024 16:00:20 +0200 Subject: [PATCH 09/14] fixup! feat(core): refactor display drivers --- core/SConscript.bootloader | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader index 7b54648c0..07010c20c 100644 --- a/core/SConscript.bootloader +++ b/core/SConscript.bootloader @@ -248,9 +248,8 @@ def cargo_build(): if NEW_RENDERING: features.append('new_rendering') - - if TREZOR_MODEL in ('T',): - features.append('ui_antialiasing') + if TREZOR_MODEL in ('T', 'T3T1', ): + features.append('ui_antialiasing') cargo_opts = [ f'--target={env.get("ENV")["RUST_TARGET"]}', From 8f510e1b8dab114c678a6f82718ad5ee7f0a97a6 Mon Sep 17 00:00:00 2001 From: cepetr Date: Thu, 25 Apr 2024 07:39:54 +0200 Subject: [PATCH 10/14] fixup! feat(core): integrate new drawing library --- core/embed/rust/src/ui/api/bootloader_c.rs | 1 + core/embed/rust/src/ui/model_mercury/bootloader/mod.rs | 1 + core/embed/rust/src/ui/model_tr/bootloader/mod.rs | 1 + core/embed/rust/src/ui/model_tt/bootloader/mod.rs | 1 + core/embed/rust/src/ui/ui_features.rs | 1 + 5 files changed, 5 insertions(+) diff --git a/core/embed/rust/src/ui/api/bootloader_c.rs b/core/embed/rust/src/ui/api/bootloader_c.rs index 7628d46d2..58496b21b 100644 --- a/core/embed/rust/src/ui/api/bootloader_c.rs +++ b/core/embed/rust/src/ui/api/bootloader_c.rs @@ -13,6 +13,7 @@ extern "C" fn screen_welcome() { } #[no_mangle] +#[cfg(not(feature = "new_rendering"))] extern "C" fn bld_continue_label(bg_color: cty::uint16_t) { ModelUI::bld_continue_label(bg_color.into()); } diff --git a/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs b/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs index 9f0d34f0a..52f05f83f 100644 --- a/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs @@ -169,6 +169,7 @@ impl UIFeaturesBootloader for ModelMercuryFeatures { show(&mut frame, true); } + #[cfg(not(feature = "new_rendering"))] fn bld_continue_label(bg_color: Color) { display::text_center( Point::new(SCREEN.width() / 2, SCREEN.height() - 5), diff --git a/core/embed/rust/src/ui/model_tr/bootloader/mod.rs b/core/embed/rust/src/ui/model_tr/bootloader/mod.rs index cced3d029..624307379 100644 --- a/core/embed/rust/src/ui/model_tr/bootloader/mod.rs +++ b/core/embed/rust/src/ui/model_tr/bootloader/mod.rs @@ -149,6 +149,7 @@ impl UIFeaturesBootloader for ModelTRFeatures { show(&mut frame, true); } + #[cfg(not(feature = "new_rendering"))] fn bld_continue_label(bg_color: Color) { display::text_center( Point::new(constant::WIDTH / 2, HEIGHT - 2), diff --git a/core/embed/rust/src/ui/model_tt/bootloader/mod.rs b/core/embed/rust/src/ui/model_tt/bootloader/mod.rs index fa27bdf8b..bde2a7c0d 100644 --- a/core/embed/rust/src/ui/model_tt/bootloader/mod.rs +++ b/core/embed/rust/src/ui/model_tt/bootloader/mod.rs @@ -176,6 +176,7 @@ impl UIFeaturesBootloader for ModelTTFeatures { show(&mut frame, true); } + #[cfg(not(feature = "new_rendering"))] fn bld_continue_label(bg_color: Color) { display::text_center( Point::new(SCREEN.width() / 2, SCREEN.height() - 5), diff --git a/core/embed/rust/src/ui/ui_features.rs b/core/embed/rust/src/ui/ui_features.rs index c927c6dff..681e88030 100644 --- a/core/embed/rust/src/ui/ui_features.rs +++ b/core/embed/rust/src/ui/ui_features.rs @@ -18,6 +18,7 @@ pub trait UIFeaturesCommon { pub trait UIFeaturesBootloader { fn screen_welcome(); + #[cfg(not(feature = "new_rendering"))] fn bld_continue_label(bg_color: Color); fn screen_install_success(restart_seconds: u8, initial_setup: bool, complete_draw: bool); From 74dc53c1bfd6d1b3c6c8a95b947bec0a7d219645 Mon Sep 17 00:00:00 2001 From: cepetr Date: Thu, 25 Apr 2024 08:40:52 +0200 Subject: [PATCH 11/14] fixup! feat(core): integrate new drawing library --- core/embed/rust/src/ui/model_mercury/bootloader/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs b/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs index 52f05f83f..a86c63c50 100644 --- a/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs +++ b/core/embed/rust/src/ui/model_mercury/bootloader/mod.rs @@ -121,7 +121,7 @@ impl ModelMercuryFeatures { let loader_offset: i16 = 19; let center_text_offset: i16 = 10; - let center = SCREEN.center() + Offset::y(-loader_offset); + let center = SCREEN.center() + Offset::y(loader_offset); let inactive_color = bg_color.blend(fg_color, 85); shape::Circle::new(center, constant::LOADER_OUTER) @@ -133,7 +133,7 @@ impl ModelMercuryFeatures { .with_end_angle(((progress as i32 * shape::PI4 as i32 * 8) / 1000) as i16) .render(target); - shape::Circle::new(center, constant::LOADER_INNER) + shape::Circle::new(center, constant::LOADER_INNER + 2) .with_bg(bg_color) .render(target); From f55c42b94037fbd1658fbb4f28c577b298b409df Mon Sep 17 00:00:00 2001 From: cepetr Date: Thu, 25 Apr 2024 08:57:06 +0200 Subject: [PATCH 12/14] fixup! feat(core): refactor display drivers --- core/SConscript.bootloader_emu | 2 -- core/SConscript.firmware | 1 - core/SConscript.unix | 1 - core/embed/rust/Cargo.toml | 4 ++-- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/core/SConscript.bootloader_emu b/core/SConscript.bootloader_emu index c57cecd0b..4c1653f17 100644 --- a/core/SConscript.bootloader_emu +++ b/core/SConscript.bootloader_emu @@ -318,14 +318,12 @@ def cargo_build(): features.append('new_rendering') if TREZOR_MODEL in ('T',): features.append('display_rgb565') - features.append('ui_antialiasing') elif TREZOR_MODEL in ('R', '1',): features.append('display_mono') features.append('xframebuffer') elif TREZOR_MODEL in ('T3T1',): features.append('display_rgb565') features.append('xframebuffer') - features.append('ui_antialiasing') if TREZOR_MODEL in ('T', 'T3T1'): features.append('touch') diff --git a/core/SConscript.firmware b/core/SConscript.firmware index aacafa3ab..9ba3e55aa 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -769,7 +769,6 @@ def cargo_build(): features.append('debug') features.append('ui_debug') if TREZOR_MODEL in ('T', 'T3T1', 'DISC1', 'DISC2'): - features.append('ui_antialiasing') features.append('ui_blurring') features.append('ui_jpeg_decoder') diff --git a/core/SConscript.unix b/core/SConscript.unix index bed716ab0..b358650ff 100644 --- a/core/SConscript.unix +++ b/core/SConscript.unix @@ -875,7 +875,6 @@ def cargo_build(): if TREZOR_MODEL in ('T', 'T3T1'): features.append('touch') features.append('sd_card') - features.append('ui_antialiasing') features.append('ui_blurring') features.append('ui_jpeg_decoder') if TREZOR_MODEL in ('R', '1'): diff --git a/core/embed/rust/Cargo.toml b/core/embed/rust/Cargo.toml index d66ab18c1..21a2f1038 100644 --- a/core/embed/rust/Cargo.toml +++ b/core/embed/rust/Cargo.toml @@ -17,8 +17,8 @@ ui = [] dma2d = [] xframebuffer = [] display_mono = [] -display_rgb565 = [] -display_rgba8888 = [] +display_rgb565 = ["ui_antialiasing"] +display_rgba8888 = ["ui_antialiasing"] framebuffer = [] framebuffer32bit = [] ui_debug = [] From 581914c405a59cb6c2fb45acdab2fe7e4c5a4ebf Mon Sep 17 00:00:00 2001 From: cepetr Date: Thu, 25 Apr 2024 08:58:07 +0200 Subject: [PATCH 13/14] fixup! feat(core): introduce new drawing library --- core/embed/rust/rust_ui_bootloader.h | 1 - core/embed/rust/src/ui/shape/display/fb_rgba8888.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/embed/rust/rust_ui_bootloader.h b/core/embed/rust/rust_ui_bootloader.h index c69f700ac..8f0149585 100644 --- a/core/embed/rust/rust_ui_bootloader.h +++ b/core/embed/rust/rust_ui_bootloader.h @@ -27,4 +27,3 @@ void bld_continue_label(uint16_t bg_color); void screen_boot(bool warning, const char* vendor_str, size_t vendor_str_len, uint32_t version, const void* vendor_img, size_t vendor_img_len, int wait); - 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 deabf0bf7..d1e732fa9 100644 --- a/core/embed/rust/src/ui/shape/display/fb_rgba8888.rs +++ b/core/embed/rust/src/ui/shape/display/fb_rgba8888.rs @@ -52,7 +52,7 @@ where )); if let Some(viewport) = viewport { - canvas.set_viewport(Viewport::new(viewport)); + canvas.set_viewport(viewport); } let mut target = DirectRenderer::new(&mut canvas, bg_color, &cache); From a4fd40fb1de7a41ea9affdcb0b4f3ff84b5dbb9e Mon Sep 17 00:00:00 2001 From: cepetr Date: Thu, 25 Apr 2024 09:13:36 +0200 Subject: [PATCH 14/14] fixup! fixup! feat(core): refactor display drivers --- core/SConscript.bootloader | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/SConscript.bootloader b/core/SConscript.bootloader index 07010c20c..04ce284c1 100644 --- a/core/SConscript.bootloader +++ b/core/SConscript.bootloader @@ -248,8 +248,6 @@ def cargo_build(): if NEW_RENDERING: features.append('new_rendering') - if TREZOR_MODEL in ('T', 'T3T1', ): - features.append('ui_antialiasing') cargo_opts = [ f'--target={env.get("ENV")["RUST_TARGET"]}',