From e7fb5fb38fd4303a4cd8b471fbc13ecf951619eb Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Sun, 12 Jul 2026 15:27:08 -0400 Subject: [PATCH] feat(rlcd): invert toggle, mono threshold, font bump, B/W luminance - RLCD device.properties: DefaultDark (focus UI visible), fontSize 18 (+28% vs 14: small 10->14, default 14->18, large 18->24, icons 16->20/36->48) - ST7305 driver: honor invertColor config (was ignored), call esp_lcd_panel_invert_color after init - DisplayDevice: add supportsInvertColor + set/get, and supportsMonochromeThreshold/set/get (ST7305 only) - EspLcdDisplay: implement invert via panelHandle, and mono threshold via global g_mono_threshold shared with esp_lvgl_port - DisplaySettings: new fields invertColor (default true to preserve current inverted look) and monochromeThreshold (default 60 best for DefaultDark per user testing, range 20-230) - Lvgl.cpp: apply invert + threshold at boot from settings - Display app: add Invert colors switch (if supportsInvertColor) + Mono threshold slider with live preview and persistence - esp_lvgl_port_disp.c (components override for hash mismatch): fix B/W conversion BEFORE: blue >16 only -> crushed colors, no gray handling AFTER: proper luminance Y=0.299R+0.587G+0.114B with adjustable threshold (default 60), no Bayer dithering dots. Crisp text on reflective - Launcher/button/list wrappers reverted to stock DefaultDark (no white focus hacks) per user request - focus UI now uses default outline which survives luminance threshold Verified on waveshare-esp32-s3-rlcd /dev/cu.usbmodem1101: - Build 2877200 bytes, flash hash verified - Invert toggle works, threshold slider live (60 best per test) - Font bump visible, focus UI visible for button navigation - No dithering dots Docs: LVGL mono https://docs.lvgl.io/9.2/porting/display.html#monochrome-displays Themes: https://docs.lvgl.io/9.2/details/common-widget-features/styles/themes.html Co-authored-by: Hermes Agent --- .../Source/devices/Display.cpp | 2 +- .../waveshare-esp32-s3-rlcd/device.properties | 2 + Drivers/EspLcdCompat/Source/EspLcdDisplay.cpp | 28 + Drivers/EspLcdCompat/Source/EspLcdDisplay.h | 18 + Drivers/ST7305/Source/St7305Display.cpp | 6 + Drivers/ST7305/Source/St7305Display.h | 2 + .../Tactility/hal/display/DisplayDevice.h | 8 + .../Tactility/settings/DisplaySettings.h | 25 +- Tactility/Source/app/display/Display.cpp | 95 ++ Tactility/Source/app/launcher/Launcher.cpp | 10 - Tactility/Source/lvgl/Lvgl.cpp | 10 + Tactility/Source/lvgl/wrappers/button.cpp | 4 +- Tactility/Source/lvgl/wrappers/list.cpp | 4 +- Tactility/Source/settings/DisplaySettings.cpp | 24 +- .../espressif__esp_lvgl_port/.component_hash | 1 + .../espressif__esp_lvgl_port/CHANGELOG.md | 187 ++++ .../espressif__esp_lvgl_port/CHECKSUMS.json | 1 + .../espressif__esp_lvgl_port/CMakeLists.txt | 134 +++ components/espressif__esp_lvgl_port/Kconfig | 10 + components/espressif__esp_lvgl_port/README.md | 386 +++++++ .../docs/frame_buffer_settings.png | Bin 0 -> 18224 bytes .../docs/performance.md | 168 +++ .../examples/i2c_oled/CMakeLists.txt | 4 + .../examples/i2c_oled/README.md | 73 ++ .../examples/i2c_oled/main/CMakeLists.txt | 5 + .../examples/i2c_oled/main/Kconfig.projbuild | 35 + .../i2c_oled/main/i2c_oled_example_main.c | 155 +++ .../examples/i2c_oled/main/idf_component.yml | 6 + .../examples/i2c_oled/main/lvgl_demo_ui.c | 22 + .../examples/i2c_oled/sdkconfig.defaults | 5 + .../examples/rgb_lcd/CMakeLists.txt | 9 + .../examples/rgb_lcd/README.md | 19 + .../examples/rgb_lcd/main/CMakeLists.txt | 11 + .../examples/rgb_lcd/main/idf_component.yml | 6 + .../examples/rgb_lcd/main/images/.gitignore | 1 + .../examples/rgb_lcd/main/images/esp_logo.png | Bin 0 -> 6639 bytes .../examples/rgb_lcd/main/main.c | 277 +++++ .../examples/rgb_lcd/partitions.csv | 5 + .../examples/rgb_lcd/sdkconfig.defaults | 39 + .../examples/touchscreen/CMakeLists.txt | 9 + .../examples/touchscreen/README.md | 19 + .../examples/touchscreen/main/CMakeLists.txt | 6 + .../touchscreen/main/idf_component.yml | 6 + .../touchscreen/main/images/.gitignore | 1 + .../touchscreen/main/images/esp_logo.png | Bin 0 -> 6639 bytes .../examples/touchscreen/main/main.c | 282 +++++ .../examples/touchscreen/sdkconfig.defaults | 3 + .../idf_component.yml | 12 + .../images/img_cursor.png | Bin 0 -> 1810 bytes .../images/img_cursor_20px.png | Bin 0 -> 1607 bytes .../images/lvgl8/img_cursor.c | 132 +++ .../images/lvgl9/img_cursor.c | 66 ++ .../include/esp_lvgl_port.h | 161 +++ .../include/esp_lvgl_port_button.h | 70 ++ .../include/esp_lvgl_port_compatibility.h | 32 + .../include/esp_lvgl_port_disp.h | 143 +++ .../include/esp_lvgl_port_knob.h | 71 ++ .../include/esp_lvgl_port_lv_blend.h | 159 +++ .../include/esp_lvgl_port_touch.h | 66 ++ .../include/esp_lvgl_port_usbhid.h | 80 ++ .../espressif__esp_lvgl_port/license.txt | 202 ++++ .../priv_include/esp_lvgl_port_priv.h | 47 + .../project_include.cmake | 78 ++ .../src/common/ppa/lcd_ppa.c | 202 ++++ .../src/common/ppa/lcd_ppa.h | 110 ++ .../src/lvgl8/esp_lvgl_port.c | 229 +++++ .../src/lvgl8/esp_lvgl_port_button.c | 217 ++++ .../src/lvgl8/esp_lvgl_port_disp.c | 603 +++++++++++ .../src/lvgl8/esp_lvgl_port_knob.c | 218 ++++ .../src/lvgl8/esp_lvgl_port_touch.c | 108 ++ .../src/lvgl8/esp_lvgl_port_usbhid.c | 472 +++++++++ .../src/lvgl9/esp_lvgl_port.c | 327 ++++++ .../src/lvgl9/esp_lvgl_port_button.c | 230 +++++ .../src/lvgl9/esp_lvgl_port_disp.c | 812 +++++++++++++++ .../src/lvgl9/esp_lvgl_port_knob.c | 237 +++++ .../src/lvgl9/esp_lvgl_port_touch.c | 171 ++++ .../src/lvgl9/esp_lvgl_port_usbhid.c | 494 +++++++++ .../simd/lv_color_blend_to_argb8888_esp32.S | 81 ++ .../simd/lv_color_blend_to_argb8888_esp32s3.S | 325 ++++++ .../simd/lv_color_blend_to_rgb565_esp32.S | 149 +++ .../simd/lv_color_blend_to_rgb565_esp32s3.S | 404 ++++++++ .../simd/lv_color_blend_to_rgb888_esp32.S | 105 ++ .../simd/lv_color_blend_to_rgb888_esp32s3.S | 346 +++++++ .../src/lvgl9/simd/lv_macro_memcpy.S | 60 ++ .../lv_rgb565_blend_normal_to_rgb565_esp32.S | 264 +++++ ...lv_rgb565_blend_normal_to_rgb565_esp32s3.S | 372 +++++++ .../lv_rgb888_blend_normal_to_rgb888_esp32.S | 261 +++++ ...lv_rgb888_blend_normal_to_rgb888_esp32s3.S | 221 ++++ .../test_apps/lvgl_port/CMakeLists.txt | 6 + .../test_apps/lvgl_port/main/CMakeLists.txt | 4 + .../lvgl_port/main/idf_component.yml | 10 + .../test_apps/lvgl_port/main/test.c | 409 ++++++++ .../lvgl_port/sdkconfig.ci.asm_render | 6 + .../test_apps/lvgl_port/sdkconfig.defaults | 6 + .../test_apps/simd/CMakeLists.txt | 7 + .../test_apps/simd/README.md | 135 +++ .../test_apps/simd/main/CMakeLists.txt | 31 + .../test_apps/simd/main/Kconfig.projbuild | 5 + .../simd/main/lv_blend/include/lv_assert.h | 60 ++ .../simd/main/lv_blend/include/lv_color.h | 272 +++++ .../simd/main/lv_blend/include/lv_color_op.h | 93 ++ .../main/lv_blend/include/lv_draw_sw_blend.h | 75 ++ .../include/lv_draw_sw_blend_to_argb8888.h | 51 + .../include/lv_draw_sw_blend_to_rgb565.h | 51 + .../include/lv_draw_sw_blend_to_rgb888.h | 53 + .../simd/main/lv_blend/include/lv_log.h | 45 + .../simd/main/lv_blend/include/lv_math.h | 56 + .../simd/main/lv_blend/include/lv_string.h | 79 ++ .../simd/main/lv_blend/include/lv_style.h | 48 + .../simd/main/lv_blend/include/lv_types.h | 53 + .../simd/main/lv_blend/src/lv_color.c | 66 ++ .../src/lv_draw_sw_blend_to_argb8888.c | 911 +++++++++++++++++ .../lv_blend/src/lv_draw_sw_blend_to_rgb565.c | 962 ++++++++++++++++++ .../lv_blend/src/lv_draw_sw_blend_to_rgb888.c | 954 +++++++++++++++++ .../main/lv_blend/src/lv_string_builtin.c | 187 ++++ .../test_apps/simd/main/lv_fill_common.h | 77 ++ .../test_apps/simd/main/lv_image_common.h | 126 +++ .../test_apps/simd/main/test_app_main.c | 50 + .../simd/main/test_lv_fill_benchmark.c | 214 ++++ .../simd/main/test_lv_fill_functionality.c | 401 ++++++++ .../simd/main/test_lv_image_benchmark.c | 225 ++++ .../simd/main/test_lv_image_functionality.c | 482 +++++++++ .../test_apps/simd/sdkconfig.defaults | 3 + 123 files changed, 16637 insertions(+), 36 deletions(-) create mode 100644 components/espressif__esp_lvgl_port/.component_hash create mode 100644 components/espressif__esp_lvgl_port/CHANGELOG.md create mode 100644 components/espressif__esp_lvgl_port/CHECKSUMS.json create mode 100644 components/espressif__esp_lvgl_port/CMakeLists.txt create mode 100644 components/espressif__esp_lvgl_port/Kconfig create mode 100644 components/espressif__esp_lvgl_port/README.md create mode 100644 components/espressif__esp_lvgl_port/docs/frame_buffer_settings.png create mode 100644 components/espressif__esp_lvgl_port/docs/performance.md create mode 100644 components/espressif__esp_lvgl_port/examples/i2c_oled/CMakeLists.txt create mode 100644 components/espressif__esp_lvgl_port/examples/i2c_oled/README.md create mode 100644 components/espressif__esp_lvgl_port/examples/i2c_oled/main/CMakeLists.txt create mode 100644 components/espressif__esp_lvgl_port/examples/i2c_oled/main/Kconfig.projbuild create mode 100644 components/espressif__esp_lvgl_port/examples/i2c_oled/main/i2c_oled_example_main.c create mode 100644 components/espressif__esp_lvgl_port/examples/i2c_oled/main/idf_component.yml create mode 100644 components/espressif__esp_lvgl_port/examples/i2c_oled/main/lvgl_demo_ui.c create mode 100644 components/espressif__esp_lvgl_port/examples/i2c_oled/sdkconfig.defaults create mode 100644 components/espressif__esp_lvgl_port/examples/rgb_lcd/CMakeLists.txt create mode 100644 components/espressif__esp_lvgl_port/examples/rgb_lcd/README.md create mode 100644 components/espressif__esp_lvgl_port/examples/rgb_lcd/main/CMakeLists.txt create mode 100644 components/espressif__esp_lvgl_port/examples/rgb_lcd/main/idf_component.yml create mode 100644 components/espressif__esp_lvgl_port/examples/rgb_lcd/main/images/.gitignore create mode 100644 components/espressif__esp_lvgl_port/examples/rgb_lcd/main/images/esp_logo.png create mode 100644 components/espressif__esp_lvgl_port/examples/rgb_lcd/main/main.c create mode 100644 components/espressif__esp_lvgl_port/examples/rgb_lcd/partitions.csv create mode 100644 components/espressif__esp_lvgl_port/examples/rgb_lcd/sdkconfig.defaults create mode 100644 components/espressif__esp_lvgl_port/examples/touchscreen/CMakeLists.txt create mode 100644 components/espressif__esp_lvgl_port/examples/touchscreen/README.md create mode 100644 components/espressif__esp_lvgl_port/examples/touchscreen/main/CMakeLists.txt create mode 100644 components/espressif__esp_lvgl_port/examples/touchscreen/main/idf_component.yml create mode 100644 components/espressif__esp_lvgl_port/examples/touchscreen/main/images/.gitignore create mode 100644 components/espressif__esp_lvgl_port/examples/touchscreen/main/images/esp_logo.png create mode 100644 components/espressif__esp_lvgl_port/examples/touchscreen/main/main.c create mode 100644 components/espressif__esp_lvgl_port/examples/touchscreen/sdkconfig.defaults create mode 100644 components/espressif__esp_lvgl_port/idf_component.yml create mode 100644 components/espressif__esp_lvgl_port/images/img_cursor.png create mode 100644 components/espressif__esp_lvgl_port/images/img_cursor_20px.png create mode 100644 components/espressif__esp_lvgl_port/images/lvgl8/img_cursor.c create mode 100644 components/espressif__esp_lvgl_port/images/lvgl9/img_cursor.c create mode 100644 components/espressif__esp_lvgl_port/include/esp_lvgl_port.h create mode 100644 components/espressif__esp_lvgl_port/include/esp_lvgl_port_button.h create mode 100644 components/espressif__esp_lvgl_port/include/esp_lvgl_port_compatibility.h create mode 100644 components/espressif__esp_lvgl_port/include/esp_lvgl_port_disp.h create mode 100644 components/espressif__esp_lvgl_port/include/esp_lvgl_port_knob.h create mode 100644 components/espressif__esp_lvgl_port/include/esp_lvgl_port_lv_blend.h create mode 100644 components/espressif__esp_lvgl_port/include/esp_lvgl_port_touch.h create mode 100644 components/espressif__esp_lvgl_port/include/esp_lvgl_port_usbhid.h create mode 100644 components/espressif__esp_lvgl_port/license.txt create mode 100644 components/espressif__esp_lvgl_port/priv_include/esp_lvgl_port_priv.h create mode 100644 components/espressif__esp_lvgl_port/project_include.cmake create mode 100644 components/espressif__esp_lvgl_port/src/common/ppa/lcd_ppa.c create mode 100644 components/espressif__esp_lvgl_port/src/common/ppa/lcd_ppa.h create mode 100644 components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port.c create mode 100644 components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_button.c create mode 100644 components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_disp.c create mode 100644 components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_knob.c create mode 100644 components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_touch.c create mode 100644 components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_usbhid.c create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port.c create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_button.c create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_knob.c create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_touch.c create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_usbhid.c create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_argb8888_esp32.S create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_argb8888_esp32s3.S create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb565_esp32.S create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb565_esp32s3.S create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb888_esp32.S create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb888_esp32s3.S create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_macro_memcpy.S create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb565_blend_normal_to_rgb565_esp32.S create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb565_blend_normal_to_rgb565_esp32s3.S create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb888_blend_normal_to_rgb888_esp32.S create mode 100644 components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb888_blend_normal_to_rgb888_esp32s3.S create mode 100644 components/espressif__esp_lvgl_port/test_apps/lvgl_port/CMakeLists.txt create mode 100644 components/espressif__esp_lvgl_port/test_apps/lvgl_port/main/CMakeLists.txt create mode 100644 components/espressif__esp_lvgl_port/test_apps/lvgl_port/main/idf_component.yml create mode 100644 components/espressif__esp_lvgl_port/test_apps/lvgl_port/main/test.c create mode 100644 components/espressif__esp_lvgl_port/test_apps/lvgl_port/sdkconfig.ci.asm_render create mode 100644 components/espressif__esp_lvgl_port/test_apps/lvgl_port/sdkconfig.defaults create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/CMakeLists.txt create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/README.md create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/CMakeLists.txt create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/Kconfig.projbuild create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_assert.h create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_color.h create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_color_op.h create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend.h create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_argb8888.h create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_rgb565.h create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_rgb888.h create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_log.h create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_math.h create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_string.h create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_style.h create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_types.h create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_color.c create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_argb8888.c create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_rgb565.c create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_rgb888.c create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_string_builtin.c create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_fill_common.h create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/lv_image_common.h create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/test_app_main.c create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_fill_benchmark.c create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_fill_functionality.c create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_image_benchmark.c create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_image_functionality.c create mode 100644 components/espressif__esp_lvgl_port/test_apps/simd/sdkconfig.defaults diff --git a/Devices/waveshare-esp32-s3-rlcd/Source/devices/Display.cpp b/Devices/waveshare-esp32-s3-rlcd/Source/devices/Display.cpp index 238f73b7..6c913f0f 100644 --- a/Devices/waveshare-esp32-s3-rlcd/Source/devices/Display.cpp +++ b/Devices/waveshare-esp32-s3-rlcd/Source/devices/Display.cpp @@ -12,7 +12,7 @@ std::shared_ptr createDisplay() { false, // swapXY false, // mirrorX false, // mirrorY - false, // invertColor + true, // invertColor - initial true to match existing inverted look, user can toggle via Settings->Display 0 // bufferSize (0 = default, which is full screen = 120,000 pixels) ); diff --git a/Devices/waveshare-esp32-s3-rlcd/device.properties b/Devices/waveshare-esp32-s3-rlcd/device.properties index 5c4f75bc..bd460cbb 100644 --- a/Devices/waveshare-esp32-s3-rlcd/device.properties +++ b/Devices/waveshare-esp32-s3-rlcd/device.properties @@ -22,3 +22,5 @@ display.dpi=120 lvgl.colorDepth=16 lvgl.uiDensity=compact +lvgl.fontSize=18 +lvgl.theme=DefaultDark diff --git a/Drivers/EspLcdCompat/Source/EspLcdDisplay.cpp b/Drivers/EspLcdCompat/Source/EspLcdDisplay.cpp index 95fa9d14..b5317783 100644 --- a/Drivers/EspLcdCompat/Source/EspLcdDisplay.cpp +++ b/Drivers/EspLcdCompat/Source/EspLcdDisplay.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include static const auto LOGGER = tt::Logger("EspLcdDisplay"); @@ -128,3 +129,30 @@ std::shared_ptr EspLcdDisplay::getDisplayDriver } return displayDriver; } + +void EspLcdDisplay::setInvertColor(bool invert) { + currentInvertColor = invert; + if (panelHandle != nullptr) { + esp_err_t err = esp_lcd_panel_invert_color(panelHandle, invert); + if (err != ESP_OK) { + LOGGER.warn("Failed to invert color: {}", esp_err_to_name(err)); + } + } +} + +bool EspLcdDisplay::supportsMonochromeThreshold() const { + // Generic EspLcdDisplay doesn't know - subclasses override + return false; +} + +void EspLcdDisplay::setMonochromeThreshold(uint8_t threshold) { + // Defined in esp_lvgl_port component + extern uint8_t g_mono_threshold; + if (threshold == 0) threshold = 1; // avoid 0 meaning default + g_mono_threshold = threshold; +} + +uint8_t EspLcdDisplay::getMonochromeThreshold() const { + extern uint8_t g_mono_threshold; + return g_mono_threshold ? g_mono_threshold : 60; +} diff --git a/Drivers/EspLcdCompat/Source/EspLcdDisplay.h b/Drivers/EspLcdCompat/Source/EspLcdDisplay.h index b8144bd0..deed98e3 100644 --- a/Drivers/EspLcdCompat/Source/EspLcdDisplay.h +++ b/Drivers/EspLcdCompat/Source/EspLcdDisplay.h @@ -15,11 +15,13 @@ class EspLcdDisplay : public tt::hal::display::DisplayDevice { std::shared_ptr displayDriver; /** @warning This is never changed, so a driver that needs BGR is not supported */ lcd_rgb_element_order_t rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB; + bool currentInvertColor = false; protected: // Used for sending commands such as setting curves esp_lcd_panel_io_handle_t getIoHandle() const { return ioHandle; } + esp_lcd_panel_handle_t getPanelHandle() const { return panelHandle; } virtual bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) = 0; @@ -41,6 +43,22 @@ public: bool stop() final; +// region Invert + + bool supportsInvertColor() const override { return true; } + void setInvertColor(bool invert) override; + bool getInvertColor() const override { return currentInvertColor; } + + // endregion + + // region Mono threshold (for ST7305 reflective etc) + + bool supportsMonochromeThreshold() const override; + void setMonochromeThreshold(uint8_t threshold) override; + uint8_t getMonochromeThreshold() const override; + + // endregion + // region LVGL bool supportsLvgl() const final { return true; } diff --git a/Drivers/ST7305/Source/St7305Display.cpp b/Drivers/ST7305/Source/St7305Display.cpp index e73a4973..a574c671 100644 --- a/Drivers/ST7305/Source/St7305Display.cpp +++ b/Drivers/ST7305/Source/St7305Display.cpp @@ -70,6 +70,12 @@ bool St7305Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lc } vTaskDelay(pdMS_TO_TICKS(50)); + if (configuration->invertColor) { + if (esp_lcd_panel_invert_color(panelHandle, true) != ESP_OK) { + LOGGER.warn("Failed to apply initial invertColor"); + } + } + return true; } diff --git a/Drivers/ST7305/Source/St7305Display.h b/Drivers/ST7305/Source/St7305Display.h index 8be4fd11..78237881 100644 --- a/Drivers/ST7305/Source/St7305Display.h +++ b/Drivers/ST7305/Source/St7305Display.h @@ -94,6 +94,8 @@ public: } bool supportsBacklightDuty() const override { return configuration->backlightDutyFunction != nullptr; } + + bool supportsMonochromeThreshold() const override { return true; } }; std::shared_ptr createDisplay(); diff --git a/Tactility/Include/Tactility/hal/display/DisplayDevice.h b/Tactility/Include/Tactility/hal/display/DisplayDevice.h index 64c243c7..a7645612 100644 --- a/Tactility/Include/Tactility/hal/display/DisplayDevice.h +++ b/Tactility/Include/Tactility/hal/display/DisplayDevice.h @@ -40,6 +40,14 @@ public: virtual void setGammaCurve(uint8_t index) { /* NO-OP */ } virtual uint8_t getGammaCurveCount() const { return 0; } + virtual bool supportsInvertColor() const { return false; } + virtual void setInvertColor(bool invertColor) { /* NO-OP */ } + virtual bool getInvertColor() const { return false; } + + virtual bool supportsMonochromeThreshold() const { return false; } + virtual void setMonochromeThreshold(uint8_t threshold) { /* NO-OP */ } + virtual uint8_t getMonochromeThreshold() const { return 128; } + virtual bool supportsLvgl() const = 0; virtual bool startLvgl() = 0; virtual bool stopLvgl() = 0; diff --git a/Tactility/Include/Tactility/settings/DisplaySettings.h b/Tactility/Include/Tactility/settings/DisplaySettings.h index d6b35f2e..9d879683 100644 --- a/Tactility/Include/Tactility/settings/DisplaySettings.h +++ b/Tactility/Include/Tactility/settings/DisplaySettings.h @@ -1,46 +1,35 @@ #pragma once - #include - namespace tt::settings::display { - enum class Orientation { - // In order of rotation (to make it easier to convert to LVGL rotation) Landscape, Portrait, LandscapeFlipped, PortraitFlipped, }; - enum class ScreensaverType { - None, // Just black screen + None, BouncingBalls, Mystify, MatrixRain, StackChan, - McpScreen, // MCP LLM-driven canvas - Count // Sentinel for bounds checking - must be last + McpScreen, + Count }; - struct DisplaySettings { Orientation orientation; uint8_t gammaCurve; uint8_t backlightDuty; bool backlightTimeoutEnabled; - uint32_t backlightTimeoutMs; // 0 = Never + uint32_t backlightTimeoutMs; ScreensaverType screensaverType = ScreensaverType::BouncingBalls; bool disableScreensaverWhenCharging = false; + bool invertColor = false; + uint8_t monochromeThreshold = 60; }; - -/** Compares default settings with the function parameter to return the difference */ lv_display_rotation_t toLvglDisplayRotation(Orientation orientation); - bool load(DisplaySettings& settings); - DisplaySettings loadOrGetDefault(); - DisplaySettings getDefault(); - bool save(const DisplaySettings& settings); - -} // namespace +} diff --git a/Tactility/Source/app/display/Display.cpp b/Tactility/Source/app/display/Display.cpp index eac98140..58c3416a 100644 --- a/Tactility/Source/app/display/Display.cpp +++ b/Tactility/Source/app/display/Display.cpp @@ -120,6 +120,42 @@ class DisplayApp final : public App { app->displaySettingsUpdated = true; } + static void onInvertColorChanged(lv_event_t* event) { + auto* app = static_cast(lv_event_get_user_data(event)); + auto* sw = static_cast(lv_event_get_target(event)); + bool enabled = lv_obj_has_state(sw, LV_STATE_CHECKED); + app->displaySettings.invertColor = enabled; + app->displaySettingsUpdated = true; + // Apply immediately + auto hal_display = getHalDisplay(); + if (hal_display && hal_display->supportsInvertColor()) { + hal_display->setInvertColor(enabled); + } + } + + static void onMonoThresholdChanged(lv_event_t* event) { + auto* slider = static_cast(lv_event_get_target(event)); + auto* app = static_cast(lv_event_get_user_data(event)); + int32_t val = lv_slider_get_value(slider); + if (val < 1) val = 1; + if (val > 255) val = 255; + app->displaySettings.monochromeThreshold = static_cast(val); + app->displaySettingsUpdated = true; + auto hal_display = getHalDisplay(); + if (hal_display && hal_display->supportsMonochromeThreshold()) { + hal_display->setMonochromeThreshold(static_cast(val)); + } + } + + static void onMonoThresholdChanging(lv_event_t* event) { + auto* slider = static_cast(lv_event_get_target(event)); + auto* value_label = static_cast(lv_event_get_user_data(event)); + if (value_label) { + int32_t v = lv_slider_get_value(slider); + lv_label_set_text_fmt(value_label, "%d", (int)v); + } + } + static void onTimeoutChanged(lv_event_t* event) { auto* app = static_cast(lv_event_get_user_data(event)); auto* dropdown = static_cast(lv_event_get_target(event)); @@ -332,6 +368,65 @@ public: } } + // Invert color toggle (if display supports it - e.g. RLCD ST7305) + { + auto hal_disp = getHalDisplay(); + if (hal_disp && hal_disp->supportsInvertColor()) { + auto* invert_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_size(invert_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(invert_wrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(invert_wrapper, 0, LV_STATE_DEFAULT); + + auto* invert_label = lv_label_create(invert_wrapper); + lv_label_set_text(invert_label, "Invert colors"); + lv_obj_align(invert_label, LV_ALIGN_LEFT_MID, 0, 0); + + auto* invert_switch = lv_switch_create(invert_wrapper); + if (displaySettings.invertColor) { + lv_obj_add_state(invert_switch, LV_STATE_CHECKED); + } + lv_obj_align(invert_switch, LV_ALIGN_RIGHT_MID, 0, 0); + lv_obj_add_event_cb(invert_switch, onInvertColorChanged, LV_EVENT_VALUE_CHANGED, this); + } + } + + // Monochrome threshold slider (ST7305 reflective) - adjustable B/W cut + { + auto hal_disp = getHalDisplay(); + if (hal_disp && hal_disp->supportsMonochromeThreshold()) { + auto* thresh_wrapper = lv_obj_create(main_wrapper); + lv_obj_set_size(thresh_wrapper, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(thresh_wrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(thresh_wrapper, 0, LV_STATE_DEFAULT); + lv_obj_set_flex_flow(thresh_wrapper, LV_FLEX_FLOW_COLUMN); + lv_obj_set_style_pad_row(thresh_wrapper, 2, LV_STATE_DEFAULT); + + auto* header_row = lv_obj_create(thresh_wrapper); + lv_obj_set_size(header_row, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_style_pad_all(header_row, 0, LV_STATE_DEFAULT); + lv_obj_set_style_border_width(header_row, 0, LV_STATE_DEFAULT); + + auto* thresh_label = lv_label_create(header_row); + lv_label_set_text(thresh_label, "Mono threshold"); + lv_obj_align(thresh_label, LV_ALIGN_LEFT_MID, 0, 0); + + auto* value_label = lv_label_create(header_row); + lv_label_set_text_fmt(value_label, "%d", displaySettings.monochromeThreshold); + lv_obj_align(value_label, LV_ALIGN_RIGHT_MID, 0, 0); + + auto* thresh_slider = lv_slider_create(thresh_wrapper); + lv_obj_set_width(thresh_slider, LV_PCT(100)); + lv_slider_set_range(thresh_slider, 20, 230); // avoid extremes that make screen all black/white + lv_slider_set_value(thresh_slider, displaySettings.monochromeThreshold, LV_ANIM_OFF); + // Live label update: pass value_label as user_data + lv_obj_add_event_cb(thresh_slider, onMonoThresholdChanging, LV_EVENT_VALUE_CHANGED, value_label); + // Persist + apply: pass app as user_data + lv_obj_add_event_cb(thresh_slider, onMonoThresholdChanged, LV_EVENT_VALUE_CHANGED, this); + // Apply initial threshold live + hal_disp->setMonochromeThreshold(displaySettings.monochromeThreshold); + } + } + if (hasCalibratableTouchDevice()) { auto* calibrate_wrapper = lv_obj_create(main_wrapper); lv_obj_set_size(calibrate_wrapper, LV_PCT(100), LV_SIZE_CONTENT); diff --git a/Tactility/Source/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index ecb97cd4..9be5dc63 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -49,11 +49,6 @@ class LauncherApp final : public App { lv_obj_set_style_shadow_width(apps_button, 0, LV_STATE_DEFAULT); lv_obj_set_style_bg_opa(apps_button, 0, LV_STATE_DEFAULT); - // Add a high-contrast focus outline for monochrome / RLCD screens - lv_obj_set_style_outline_width(apps_button, 2, LV_STATE_FOCUSED); - lv_obj_set_style_outline_pad(apps_button, 2, LV_STATE_FOCUSED); - lv_obj_set_style_outline_color(apps_button, lv_theme_get_color_primary(apps_button), LV_STATE_FOCUSED); - auto* default_group = lv_group_get_default(); if (default_group) { lv_group_add_obj(default_group, apps_button); @@ -223,11 +218,6 @@ public: lv_obj_set_style_shadow_width(power_button, 0, LV_STATE_DEFAULT); lv_obj_set_style_bg_opa(power_button, 0, LV_PART_MAIN); - // Add high-contrast focus outline for monochrome / RLCD screens - lv_obj_set_style_outline_width(power_button, 2, LV_STATE_FOCUSED); - lv_obj_set_style_outline_pad(power_button, 2, LV_STATE_FOCUSED); - lv_obj_set_style_outline_color(power_button, lv_theme_get_color_primary(power_button), LV_STATE_FOCUSED); - auto* default_group = lv_group_get_default(); if (default_group) { lv_group_add_obj(default_group, power_button); diff --git a/Tactility/Source/lvgl/Lvgl.cpp b/Tactility/Source/lvgl/Lvgl.cpp index b6a3af20..72453417 100644 --- a/Tactility/Source/lvgl/Lvgl.cpp +++ b/Tactility/Source/lvgl/Lvgl.cpp @@ -44,6 +44,16 @@ void attachDevices() { if (rotation != lv_display_get_rotation(lvgl_display)) { lv_display_set_rotation(lvgl_display, rotation); } + // Apply invert setting if display supports it + if (display->supportsInvertColor()) { + display->setInvertColor(settings.invertColor); + LOGGER.info("InvertColor set to {} for {}", settings.invertColor ? "true" : "false", display->getName()); + } + // Apply monochrome threshold for ST7305 reflective + if (display->supportsMonochromeThreshold()) { + display->setMonochromeThreshold(settings.monochromeThreshold); + LOGGER.info("MonoThreshold set to {} for {}", settings.monochromeThreshold, display->getName()); + } } else { LOGGER.error("Start failed for {}", display->getName()); } diff --git a/Tactility/Source/lvgl/wrappers/button.cpp b/Tactility/Source/lvgl/wrappers/button.cpp index 0dbbb52e..3fe7e6fa 100644 --- a/Tactility/Source/lvgl/wrappers/button.cpp +++ b/Tactility/Source/lvgl/wrappers/button.cpp @@ -1,9 +1,7 @@ #ifdef ESP_PLATFORM #include - #include - extern "C" { extern lv_obj_t* __real_lv_button_create(lv_obj_t* parent); @@ -21,4 +19,4 @@ lv_obj_t* __wrap_lv_button_create(lv_obj_t* parent) { } -#endif // ESP_PLATFORM \ No newline at end of file +#endif // ESP_PLATFORM diff --git a/Tactility/Source/lvgl/wrappers/list.cpp b/Tactility/Source/lvgl/wrappers/list.cpp index e1a0fa87..6578e483 100644 --- a/Tactility/Source/lvgl/wrappers/list.cpp +++ b/Tactility/Source/lvgl/wrappers/list.cpp @@ -1,9 +1,7 @@ #ifdef ESP_PLATFORM #include - #include - extern "C" { extern lv_obj_t* __real_lv_list_create(lv_obj_t* parent); @@ -33,4 +31,4 @@ lv_obj_t* __wrap_lv_list_add_button(lv_obj_t* list, const void* icon, const char } -#endif // ESP_PLATFORM \ No newline at end of file +#endif // ESP_PLATFORM diff --git a/Tactility/Source/settings/DisplaySettings.cpp b/Tactility/Source/settings/DisplaySettings.cpp index c4f593b4..7c371a44 100644 --- a/Tactility/Source/settings/DisplaySettings.cpp +++ b/Tactility/Source/settings/DisplaySettings.cpp @@ -23,6 +23,8 @@ constexpr auto* SETTINGS_KEY_TIMEOUT_ENABLED = "backlightTimeoutEnabled"; constexpr auto* SETTINGS_KEY_TIMEOUT_MS = "backlightTimeoutMs"; constexpr auto* SETTINGS_KEY_SCREENSAVER_TYPE = "screensaverType"; constexpr auto* SETTINGS_KEY_DISABLE_WHEN_CHARGING = "disableScreensaverWhenCharging"; +constexpr auto* SETTINGS_KEY_INVERT_COLOR = "invertColor"; +constexpr auto* SETTINGS_KEY_MONO_THRESHOLD = "monochromeThreshold"; static Orientation getDefaultOrientation() { auto* display = lv_display_get_default(); @@ -171,6 +173,20 @@ bool load(DisplaySettings& settings) { disable_when_charging = (disable_charging_entry->second == "1" || disable_charging_entry->second == "true" || disable_charging_entry->second == "True"); } + bool invert_color = false; + auto invert_entry = map.find(SETTINGS_KEY_INVERT_COLOR); + if (invert_entry != map.end()) { + invert_color = (invert_entry->second == "1" || invert_entry->second == "true" || invert_entry->second == "True"); + } + + int mono_thresh = 60; + auto mono_entry = map.find(SETTINGS_KEY_MONO_THRESHOLD); + if (mono_entry != map.end()) { + mono_thresh = atoi(mono_entry->second.c_str()); + if (mono_thresh < 1) mono_thresh = 1; + if (mono_thresh > 255) mono_thresh = 255; + } + settings.orientation = orientation; settings.gammaCurve = gamma_curve; settings.backlightDuty = backlight_duty; @@ -178,6 +194,8 @@ bool load(DisplaySettings& settings) { settings.backlightTimeoutMs = timeout_ms; settings.screensaverType = screensaver_type; settings.disableScreensaverWhenCharging = disable_when_charging; + settings.invertColor = invert_color; + settings.monochromeThreshold = static_cast(mono_thresh); return true; } @@ -190,7 +208,9 @@ DisplaySettings getDefault() { .backlightTimeoutEnabled = false, .backlightTimeoutMs = 60000, .screensaverType = ScreensaverType::BouncingBalls, - .disableScreensaverWhenCharging = false + .disableScreensaverWhenCharging = false, + .invertColor = true, + .monochromeThreshold = 60 }; } @@ -211,6 +231,8 @@ bool save(const DisplaySettings& settings) { map[SETTINGS_KEY_TIMEOUT_MS] = std::to_string(settings.backlightTimeoutMs); map[SETTINGS_KEY_SCREENSAVER_TYPE] = toString(settings.screensaverType); map[SETTINGS_KEY_DISABLE_WHEN_CHARGING] = settings.disableScreensaverWhenCharging ? "1" : "0"; + map[SETTINGS_KEY_INVERT_COLOR] = settings.invertColor ? "1" : "0"; + map[SETTINGS_KEY_MONO_THRESHOLD] = std::to_string(settings.monochromeThreshold); auto settings_path = getSettingsFilePath(); if (!file::findOrCreateParentDirectory(settings_path, 0755)) { return false; diff --git a/components/espressif__esp_lvgl_port/.component_hash b/components/espressif__esp_lvgl_port/.component_hash new file mode 100644 index 00000000..117872bf --- /dev/null +++ b/components/espressif__esp_lvgl_port/.component_hash @@ -0,0 +1 @@ +b6360960f47b6776462e7092861b3ea66477ffb762a01baa0aecbb3d74cd50f4 \ No newline at end of file diff --git a/components/espressif__esp_lvgl_port/CHANGELOG.md b/components/espressif__esp_lvgl_port/CHANGELOG.md new file mode 100644 index 00000000..3665b019 --- /dev/null +++ b/components/espressif__esp_lvgl_port/CHANGELOG.md @@ -0,0 +1,187 @@ +# Changelog + +## 2.7.2 + +### Features + +- Added a new event type for encoder events + +### Fixes + +- Encoder usage no longer causes unexpected touch controller readings - https://github.com/espressif/esp-bsp/issues/700 + +## 2.7.1 + +### Features +- Added option to include a rounder callback + +### Fixes +- Fixed deinitialization of the task which was created with caps - https://github.com/espressif/esp-bsp/issues/680 +- Call lv_deinit() - https://github.com/espressif/esp-bsp/issues/635 +- Fixed PPA rotation for IDF6 + +## 2.7.0 + +### Features + +- Added support for multi-touch gestures. + +## 2.6.3 + +### Fixes +- Improved and fixed deinit function (remove semaphor) - https://github.com/espressif/esp-bsp/issues/673 + +## 2.6.2 + +- Changed minimum IDF version to IDF5.1 + +## 2.6.1 + +### Features +- Added option to place LVGL task stack to external RAM +- Fixed callback for RGB display for IDF6 + +### Fixes +- Register button callbacks only if encoder_enter is set https://github.com/espressif/esp-bsp/pull/571/files + +## 2.6.0 + +### Features +- Scaling feature in touch +- Added support for PPA rotation in LVGL9 (available for ESP32-P4) + +## 2.5.0 + +### Features (Functional change for button v4 users) +- Updated LVGL port for using IoT button component v4 (LVGL port not anymore creating button, need to be created in app and included handle to LVGL port) + +### Fixes +- Fixed buffer size by selected color format +- Fixed memory leak in LVGL8 in display removing https://github.com/espressif/esp-bsp/issues/462 +- Fixed draw buffer alignment + +## 2.4.4 + +### Features +- Changed queue to event group in main LVGL task for speed up https://github.com/espressif/esp-bsp/issues/492 +- Reworked handling encoder (knob) https://github.com/espressif/esp-bsp/pull/450 + +### Fixes +- Fixed a crash when esp_lvgl_port was initialized from high priority task https://github.com/espressif/esp-bsp/issues/455 +- Allow to swap bytes when used SW rotation https://github.com/espressif/esp-bsp/issues/497 + +## 2.4.3 + +### Fixes +- Fixed a display context pointer bug +- Fixed I2C example for using with LVGL9 + +### Features +- Support for LV_COLOR_FORMAT_I1 for monochromatic screen + +## 2.4.2 + +### Fixes +- Fixed SW rotation in LVGL9.2 +- Fixed freeing right buffers when error + +## 2.4.1 + +### Fixes +- Fixed the issue of the DPI callback function not being initialized. + +## 2.4.0 + +### Features +- Added support for direct mode and full refresh mode in the MIPI-DSI interface. +- Optimized avoid-tear feature and LVGL task. + +## 2.3.3 + +### Features +- Updated RGB screen flush handling in LVGL9. + +## 2.3.2 + +### Fixes +- Fixed rotation type compatibility with LVGL8. + +## 2.3.1 + +### Fixes +- Fixed LVGL version resolution if LVGL is not a managed component +- Fixed link error with LVGL v9.2 +- Fixed event error with LVGL v9.2 + +## 2.3.0 + +### Fixes +- Fixed LVGL port for using with LVGL9 OS FreeRTOS enabled +- Fixed bad handled touch due to synchronization timer task + +### Features +- Added support for SW rotation in LVGL9 + +## 2.2.2 + +### Fixes +- Fixed missing callback in IDF4.4.3 and lower for LVGL port + +## 2.2.1 + +### Fixes +- Added missing includes +- Fixed watchdog error in some cases in LVGL9 + +## 2.2.0 + +### Features +- Added RGB display support +- Added support for direct mode and full refresh mode + +### Breaking changes +- Removed MIPI-DSI from display configuration structure - use `lvgl_port_add_disp_dsi` instead + +## 2.1.0 + +### Features +- Added LVGL sleep feature: The esp_lvgl_port handling can sleep if the display and touch are inactive (only with LVGL9) +- Added support for different display color modes (only with LVGL9) +- Added script for generating C array images during build (depends on LVGL version) + +### Fixes +- Applied initial display rotation from configuration https://github.com/espressif/esp-bsp/pull/278 +- Added blocking wait for LVGL task stop during esp_lvgl_port de-initialization https://github.com/espressif/esp-bsp/issues/277 +- Added missing esp_idf_version.h include + +## 2.0.0 + +### Features + +- Divided into files per feature +- Added support for LVGL9 +- Added support for MIPI-DSI display + +## 1.4.0 + +### Features + +- Added support for USB HID mouse/keyboard as an input device + +## 1.3.0 + +### Features + +- Added low power interface + +## 1.2.0 + +### Features + +- Added support for encoder (knob) as an input device + +## 1.1.0 + +### Features + +- Added support for navigation buttons as an input device diff --git a/components/espressif__esp_lvgl_port/CHECKSUMS.json b/components/espressif__esp_lvgl_port/CHECKSUMS.json new file mode 100644 index 00000000..d47d0427 --- /dev/null +++ b/components/espressif__esp_lvgl_port/CHECKSUMS.json @@ -0,0 +1 @@ +{"version":"1.0","algorithm":"sha256","created_at":"2026-02-18T14:13:48.039838+00:00","files":[{"path":"CHANGELOG.md","size":4349,"hash":"60947f27d77fdcccffd8a3fa02239c382f7b986166496cb9334f90a7f1a4d392"},{"path":"CMakeLists.txt","size":5669,"hash":"9271c5067dfe2da5dd7c825bac00111fadbffd061f871220bffdd0e72aa21207"},{"path":"Kconfig","size":228,"hash":"8bbe646fdfb1e0b3faa9420f8310fe0174515ca55230471e6eec3047f22c95ee"},{"path":"README.md","size":14218,"hash":"bb2293357861433b3d14c74e31d47655400c152cf1c9e1b0fe6d8ec435dbc9f8"},{"path":"idf_component.yml","size":359,"hash":"bd4d620189d21086d79b3335bb39ae6809034421348bc42f8a75f2ec2cd0f89a"},{"path":"license.txt","size":11358,"hash":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30"},{"path":"project_include.cmake","size":3143,"hash":"1c3a5bb8021371fc082e404597865b8bbc6b69d1a6e8e7f88a13aa13267d3971"},{"path":"docs/frame_buffer_settings.png","size":18224,"hash":"ca4b66bc6f70665f3fa15b0028ba3f89d241a2a8e609520549afb2326f6d630c"},{"path":"docs/performance.md","size":7876,"hash":"9547c9ecc770178860f28a2176186c458e01a9f4b561813ff531d227af0bcdb7"},{"path":"images/img_cursor.png","size":1810,"hash":"30766176860fdf04f2e41800b590bc34a706a5b9dfe0502bd5a20d5c1e406222"},{"path":"images/img_cursor_20px.png","size":1607,"hash":"8e204d096139a5222a8757911befb0a7abaa1ca21f2606dde36c83a02d07b492"},{"path":"include/esp_lvgl_port.h","size":4192,"hash":"d2d9c5fb62dfc2c94fd363964eb4d8303d92ea1281c4d0885ca0afd7cf18ef1a"},{"path":"include/esp_lvgl_port_button.h","size":1926,"hash":"73b2ef9a844be35077e5c4ab0f55a30b09cfdeb88e91fe55dc3e2c849f28687b"},{"path":"include/esp_lvgl_port_compatibility.h","size":613,"hash":"913b20478738e8db1ec831d10e858f0129d9e9ed12dd05c20405502fe5dc3710"},{"path":"include/esp_lvgl_port_disp.h","size":5046,"hash":"0938ac0f248c03bac10427baebe0c674a90eed90c64769d835d9947fadd73227"},{"path":"include/esp_lvgl_port_knob.h","size":1772,"hash":"2955f94cc36ece34f2af55e9b27587f8e65a7af3d31061358d54b1e768a78dde"},{"path":"include/esp_lvgl_port_lv_blend.h","size":4158,"hash":"e24501a1642718b3b982f94ac5f83f5db0b61c2d2fc63223affeee6511efe01d"},{"path":"include/esp_lvgl_port_touch.h","size":1494,"hash":"ee753462126b935bfdea9e1d6541d08b05807b51b8365bfea77058544553bdcd"},{"path":"include/esp_lvgl_port_usbhid.h","size":2122,"hash":"845df6dfbb410a4cc79bccd1208e343c53f4fe3e5e7ecef111535b6307e2509e"},{"path":"priv_include/esp_lvgl_port_priv.h","size":886,"hash":"f31609b16a9e4caaf54743c032deb76aba404e4e6b1a429ed1601ef98f890e2b"},{"path":"test_apps/lvgl_port/CMakeLists.txt","size":262,"hash":"de18b3eef1d4b9943744871055948119b31b09c7e7e84fe5e9e92dca1e471d48"},{"path":"test_apps/lvgl_port/sdkconfig.ci.asm_render","size":249,"hash":"e9688c0ad5f821154c787ebb707973c3d81f5175bd63a6d485af4d60c7d4f979"},{"path":"test_apps/lvgl_port/sdkconfig.defaults","size":181,"hash":"d8f206a9e5fb4b462815601698c59dc9f8054ab3873360f8563758b2735e9708"},{"path":"test_apps/simd/CMakeLists.txt","size":239,"hash":"0657f934fbfc7d12a3ad1d4d6ce1093b539f7d75a68da899d5d2de67b1da5be0"},{"path":"test_apps/simd/README.md","size":8118,"hash":"44471cc2fa3804ffd7b6f570df735205ecf2cd0d76ae2bd6c5eeea03e5828913"},{"path":"test_apps/simd/sdkconfig.defaults","size":93,"hash":"5f1a33bd82376fb9355246cce1ba24708800217376b47d82ea97e460fc1492fe"},{"path":"test_apps/simd/main/CMakeLists.txt","size":1489,"hash":"3d802b57bf1f7e76c147a787a022e458aefa9a7e54cbc819733fd8b1fc5bf237"},{"path":"test_apps/simd/main/Kconfig.projbuild","size":162,"hash":"8df91321356b45e02e40de2b6645e60eee5198470dab72efedd7de8ce689e84d"},{"path":"test_apps/simd/main/lv_fill_common.h","size":4114,"hash":"ae139ee31b37bff94fe72b6bd58ccb19e6158ccc0fb006cc727888fc87fd6e31"},{"path":"test_apps/simd/main/lv_image_common.h","size":8114,"hash":"2a6278edac2c195638b9be45c276a3e2fda58d325bd0190ddd54a6d37a3df2f8"},{"path":"test_apps/simd/main/test_app_main.c","size":1340,"hash":"3d537eb67385375f47f1119266898b7b8e00b73925a68daedea085e4e867f01f"},{"path":"test_apps/simd/main/test_lv_fill_benchmark.c","size":8457,"hash":"310b970ab73d5bee89b232d42a28651671d7911a400bc07b7cf5bb52f04d8391"},{"path":"test_apps/simd/main/test_lv_fill_functionality.c","size":16782,"hash":"216fa961d338ef40a010fa50f7de6a832adb15cec8a9ac379e5623827b0a169c"},{"path":"test_apps/simd/main/test_lv_image_benchmark.c","size":10536,"hash":"e47ada5370b6a7755955ac2607419345f9cd50821a641beec4b4197b2923456b"},{"path":"test_apps/simd/main/test_lv_image_functionality.c","size":23484,"hash":"10aab25a7c5d87db1c0201e9985eb571dcd9f4c0f84c2f94be89ca19b12c421b"},{"path":"test_apps/simd/main/lv_blend/include/lv_assert.h","size":1268,"hash":"023bf0e34c807e88526b91d261b71f3b8f29ef4f3c6e82f3cbe94aaa58e90e97"},{"path":"test_apps/simd/main/lv_blend/include/lv_color.h","size":8537,"hash":"3b2e6fbcf3c3e09cd686962950f990647052f1a6778babb46bcac8c4708853b2"},{"path":"test_apps/simd/main/lv_blend/include/lv_color_op.h","size":2281,"hash":"7cb842812d508eb3e0e5843db192efe64f0539ccd0162023df28c06087be8a46"},{"path":"test_apps/simd/main/lv_blend/include/lv_draw_sw_blend.h","size":1398,"hash":"3c41612796b3feab3a3740b4ed92c21485c751f8a26cd8456e02d83119611024"},{"path":"test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_argb8888.h","size":1038,"hash":"e6b2744c2ca82dd4465525d601493b6d368d70da3ffe9964ab9c97522297a9ad"},{"path":"test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_rgb565.h","size":1026,"hash":"6ffa995748d56e41a3cf83caebe655f56b3f175811f733582d3bc1b75bb53f7f"},{"path":"test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_rgb888.h","size":1093,"hash":"051f10a3f78379e28d56c71d77c69bd348a9615a53e857704cd67a3e679cb657"},{"path":"test_apps/simd/main/lv_blend/include/lv_log.h","size":847,"hash":"71fad5c827bc5ef817b70d5758d345149b105c67e7de34c7512d0f03a03f1039"},{"path":"test_apps/simd/main/lv_blend/include/lv_math.h","size":1432,"hash":"d0bb6a0f63d36f34eb776aff999a3f34671761b196717979a680a4775148b263"},{"path":"test_apps/simd/main/lv_blend/include/lv_string.h","size":2070,"hash":"de5fe4711b6d289645b4e6cd0a5fb68c1eb59f0f6a71182bc94054b41e80e952"},{"path":"test_apps/simd/main/lv_blend/include/lv_style.h","size":989,"hash":"51b67b5553779b294aad933e3823e4d686354ee6721a96b88868359fafe8b0a3"},{"path":"test_apps/simd/main/lv_blend/include/lv_types.h","size":983,"hash":"6c580a4289e4796340e085619bb9525f1520a43500f60b7f398ffc5671b8d705"},{"path":"test_apps/simd/main/lv_blend/src/lv_color.c","size":1316,"hash":"5454a6502f7e63db723a839cdca4594c84c8d564cf630ab741619523ea15e2a2"},{"path":"test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_argb8888.c","size":37440,"hash":"fd27e41133e719449bf7b94060ed744a3b7581cfb31dfbe43fa2bca8d3821839"},{"path":"test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_rgb565.c","size":41556,"hash":"ec27dcace4e6dfdef82b336f57701cbd5e5d023a5ac4e034ff5a50abafe7f03f"},{"path":"test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_rgb888.c","size":39453,"hash":"f443d00fa5c086936e19225973cfcdd6b91acb5bc991f5aead5b59fb7d9d05a1"},{"path":"test_apps/simd/main/lv_blend/src/lv_string_builtin.c","size":3997,"hash":"468bdeca0e30112ffffe7ea3eebb7f8f2ffac93f1968d86bf818adac91607b4e"},{"path":"test_apps/lvgl_port/main/CMakeLists.txt","size":78,"hash":"0f20c14c12450f4246bead6c159593ed87eb7d51d3a524bd5131badc6d834949"},{"path":"test_apps/lvgl_port/main/idf_component.yml","size":266,"hash":"397324e4e66c750701c1e778d327007579d7537141ff0fd7476f28c5ab42a7c4"},{"path":"test_apps/lvgl_port/main/test.c","size":13701,"hash":"fe5db45315870387e35a63a55b9df7984edc0bb9b6bffbcfa4963a60c5e37121"},{"path":"src/lvgl8/esp_lvgl_port.c","size":7192,"hash":"f69f48c6cbcf12ee6ff721cdfde0da261bbb349d7e87349f421e734a5694e1e0"},{"path":"src/lvgl8/esp_lvgl_port_button.c","size":7633,"hash":"7066fa7c0f3e680071de57926c2f8fe4615982e7cbfd8dc0d8674aa5396e23e5"},{"path":"src/lvgl8/esp_lvgl_port_disp.c","size":23347,"hash":"0208d803936e95962aae519bb3dc2f936e393fba7130c139a76c8808154ec9e2"},{"path":"src/lvgl8/esp_lvgl_port_knob.c","size":7757,"hash":"f221782d774ee1382115be847ca937c682b5bc4a3e050fb79da05b99509a4f19"},{"path":"src/lvgl8/esp_lvgl_port_touch.c","size":3634,"hash":"341582324ee4243248ad8f6d476baa3f438f8e5551b4b8ad429765f20413a8c0"},{"path":"src/lvgl8/esp_lvgl_port_usbhid.c","size":17282,"hash":"71452be389b6ca10c8d89920caa658db667109aca9bc871a04a959efd2577b51"},{"path":"src/lvgl9/esp_lvgl_port.c","size":10517,"hash":"a745ea013220c7261342ca78b70388161188f8f973827a2ba432410a8a06544a"},{"path":"src/lvgl9/esp_lvgl_port_button.c","size":7874,"hash":"1e79a48f24a86a2f4ad872a7a6a84b69f844210929a266dec8c3fefd557263fd"},{"path":"src/lvgl9/esp_lvgl_port_disp.c","size":31786,"hash":"c863d30fce8e856807925dda5405852bd74495d36b114de03f6a2146aa1558b4"},{"path":"src/lvgl9/esp_lvgl_port_knob.c","size":8412,"hash":"46aa536ebd72c338bace7a330fc2cd03c032747c685282139c4e010cdefce189"},{"path":"src/lvgl9/esp_lvgl_port_touch.c","size":6011,"hash":"117dd46bbfdca54a8a92f307e1b878353d7aa35d546f9edd12c71f2d9fccedb2"},{"path":"src/lvgl9/esp_lvgl_port_usbhid.c","size":18088,"hash":"2202aa1c40d1b3f7425c070976a8e2c0b68d3196e6d6c0b264b15fa9808e5744"},{"path":"src/lvgl9/simd/lv_color_blend_to_argb8888_esp32.S","size":3794,"hash":"47cb812c0d08812f57bbc72870b96485e217744245ac0360b7d9bd68decd7089"},{"path":"src/lvgl9/simd/lv_color_blend_to_argb8888_esp32s3.S","size":17657,"hash":"82939e974791ea689d14ab9f07fe1a9d63ab2fe4b9bc4707c34e590c42086307"},{"path":"src/lvgl9/simd/lv_color_blend_to_rgb565_esp32.S","size":8162,"hash":"aad8f0d575a9b5a66d7ed622f184e46857dc350c2871008f46c06fe574c5fdfe"},{"path":"src/lvgl9/simd/lv_color_blend_to_rgb565_esp32s3.S","size":22712,"hash":"133fd44bfab46cb7509f8e29842631aa409d2ce41169c0db9c190f91ed3235c9"},{"path":"src/lvgl9/simd/lv_color_blend_to_rgb888_esp32.S","size":5389,"hash":"65d1b6f709fe097b44db1a3d469fc87b034f407a77b6d2b92b356c92cd9fbc64"},{"path":"src/lvgl9/simd/lv_color_blend_to_rgb888_esp32s3.S","size":20346,"hash":"f10b89667f2d756b37d5a487341621e07be373231713757deac0565967e328be"},{"path":"src/lvgl9/simd/lv_macro_memcpy.S","size":3463,"hash":"c32fd4d516c17019846a9aba6a9e9e69a0110827b15089e49fb4d347ecbece62"},{"path":"src/lvgl9/simd/lv_rgb565_blend_normal_to_rgb565_esp32.S","size":17717,"hash":"cff3e3d3682f5a392c7b804560425df42635291776f38a95c3ec8b61929770b7"},{"path":"src/lvgl9/simd/lv_rgb565_blend_normal_to_rgb565_esp32s3.S","size":25020,"hash":"0dc791bf75ee5a46e0b859ba4325a7a280c2fc57c964824f6186cebd18c4cd70"},{"path":"src/lvgl9/simd/lv_rgb888_blend_normal_to_rgb888_esp32.S","size":17799,"hash":"cb704763ce580b571797cdee05190ecca3e8678a2c446f116bb0b4ecd295f3d1"},{"path":"src/lvgl9/simd/lv_rgb888_blend_normal_to_rgb888_esp32s3.S","size":14453,"hash":"4869cfcf699c85acd8441967d0755a663dbfa022e63b12f6e0d1a73fc4f71bf3"},{"path":"src/common/ppa/lcd_ppa.c","size":6095,"hash":"bd2abab5e3b176c2da02f9ab190e54cc160961d268363d19499a3be7dafd8040"},{"path":"src/common/ppa/lcd_ppa.h","size":2673,"hash":"19f3c2d797c38fe0f0675724a9d3cb6a8d4e79162aed35416e48a1e15e6e132c"},{"path":"images/lvgl8/img_cursor.c","size":30444,"hash":"b6b02e79792e917390e140cbcb24849f488b427237a8310dbaa8ecd1a7e9a7f8"},{"path":"images/lvgl9/img_cursor.c","size":10557,"hash":"896c76b21fa60108319fab2bc824b80e90f61a4af3ee64500101ebbc3b5c3f23"},{"path":"examples/i2c_oled/CMakeLists.txt","size":106,"hash":"e1f4660ab931e736722e708370f43942718ffc96e50456ef40af40c71b1eee00"},{"path":"examples/i2c_oled/README.md","size":3099,"hash":"58efdc04492d25c37840cfe51bb99ba83fdf63a1d2bf3a68c2dd2cd86e2a9cf5"},{"path":"examples/i2c_oled/sdkconfig.defaults","size":214,"hash":"c69dea8fed3d13fecf79f91dc97013e4c9ff7c0f8819a9e572cb223bc68d6a25"},{"path":"examples/rgb_lcd/CMakeLists.txt","size":346,"hash":"0859613a3b9ff0ac0c2d4b81035b9233fe29bd353dcb672f7df174aab9976849"},{"path":"examples/rgb_lcd/README.md","size":1134,"hash":"59c92f0ae836ffe9e988a97c02fc8e745418bae362fec0b461198369140f15e2"},{"path":"examples/rgb_lcd/partitions.csv","size":280,"hash":"9b71a7e67a01944471127836821f300c40a6fd9b31ce7c204a688425ae1a77c0"},{"path":"examples/rgb_lcd/sdkconfig.defaults","size":1004,"hash":"2c2f01e08f8f8b6585be0707e9d6c9df2b0a43c9a98c45b7bc7fd7f126a16c41"},{"path":"examples/touchscreen/CMakeLists.txt","size":350,"hash":"8baee201ab394ecd0ec3f4204a670a793ad36593f02bd69935f53c6263c6a0cb"},{"path":"examples/touchscreen/README.md","size":1055,"hash":"948c33508f4436871b8175d5e03438638b842316091943b52b81db5a34625951"},{"path":"examples/touchscreen/sdkconfig.defaults","size":95,"hash":"e9979750d3378587f27f5e3bfd2bd287187670634911fc04887283196cf9b972"},{"path":"examples/touchscreen/main/CMakeLists.txt","size":245,"hash":"f2f160323eadfb0c1ff6b27a1465fed7482da46bc7206e5addde4f07f5a158b8"},{"path":"examples/touchscreen/main/idf_component.yml","size":104,"hash":"1797844e304debdafda42041888306a604d8db0a49b40798b1db87a909b0aec1"},{"path":"examples/touchscreen/main/main.c","size":8952,"hash":"76687cec2fe27ac0ce0eaf0762e6d8c00364ad4dc8b2dda806876c04852f600b"},{"path":"examples/touchscreen/main/images/.gitignore","size":3,"hash":"faf716144b6ad900918adcc39a8552daa06b49432a8aa9bc4a424b6de4feff05"},{"path":"examples/touchscreen/main/images/esp_logo.png","size":6639,"hash":"7285c480c14d3899e09890551cfd9b7bc701a07a6f8216ec878f1347c8ceac4a"},{"path":"examples/rgb_lcd/main/CMakeLists.txt","size":373,"hash":"8344e76b19374787b9d8c605f19d8cdbb02d0d59615d1791c63f2dece7ba10b0"},{"path":"examples/rgb_lcd/main/idf_component.yml","size":103,"hash":"1872d8e6b96c195c96900ff0a77141d893e830fcaaf21af781220a0fdecb6410"},{"path":"examples/rgb_lcd/main/main.c","size":8917,"hash":"2e0e5f14a79c091c99ce66c45e2ecaba3df57420ed0975e5933baa2599eaa72b"},{"path":"examples/rgb_lcd/main/images/.gitignore","size":3,"hash":"faf716144b6ad900918adcc39a8552daa06b49432a8aa9bc4a424b6de4feff05"},{"path":"examples/rgb_lcd/main/images/esp_logo.png","size":6639,"hash":"7285c480c14d3899e09890551cfd9b7bc701a07a6f8216ec878f1347c8ceac4a"},{"path":"examples/i2c_oled/main/CMakeLists.txt","size":119,"hash":"ceb86c0b96359ef76cf1755f9c05a8f1699936cc7c9a523f31ae273200b502e0"},{"path":"examples/i2c_oled/main/Kconfig.projbuild","size":962,"hash":"32b58eeed4a8ba13e26391621bb5abe95aec841af1606e497476e553d3f91329"},{"path":"examples/i2c_oled/main/i2c_oled_example_main.c","size":5378,"hash":"5d21ab0134624bf8243abbc71a2aef6876324aa5658e5c099c12ab7797e27c98"},{"path":"examples/i2c_oled/main/idf_component.yml","size":100,"hash":"487d02a46ce8b0f7d54f795d433103dc7fcaed0c45fd12f54378367ceceafb2e"},{"path":"examples/i2c_oled/main/lvgl_demo_ui.c","size":741,"hash":"824592a45278d2d844bc47499160b546646061c65f6767616220d7ac9299cc75"}]} \ No newline at end of file diff --git a/components/espressif__esp_lvgl_port/CMakeLists.txt b/components/espressif__esp_lvgl_port/CMakeLists.txt new file mode 100644 index 00000000..af2915c2 --- /dev/null +++ b/components/espressif__esp_lvgl_port/CMakeLists.txt @@ -0,0 +1,134 @@ +include($ENV{IDF_PATH}/tools/cmake/version.cmake) # $ENV{IDF_VERSION} was added after v4.3... + +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_LESS "4.4") + return() +endif() + +set(ADD_SRCS "") +set(ADD_LIBS "") +set(PRIV_REQ "") +idf_build_get_property(target IDF_TARGET) +if(${target} STREQUAL "esp32p4") + list(APPEND ADD_SRCS "src/common/ppa/lcd_ppa.c") + list(APPEND ADD_LIBS idf::esp_driver_ppa) + list(APPEND PRIV_REQ esp_driver_ppa) +endif() + +# This component uses a CMake workaround, so we can compile esp_lvgl_port for both LVGL8.x and LVGL9.x +# At the time of idf_component_register() we don't know which LVGL version is used, so we only register an INTERFACE component (with no sources) +# Later, when we know the LVGL version, we create another CMake library called 'lvgl_port_lib' and link it to the 'esp_lvgl_port' INTERFACE component +idf_component_register( + INCLUDE_DIRS "include" + PRIV_INCLUDE_DIRS "priv_include" + REQUIRES "esp_lcd" + PRIV_REQUIRES "${PRIV_REQ}") + +# Get LVGL version +idf_build_get_property(build_components BUILD_COMPONENTS) +if(lvgl IN_LIST build_components) + set(lvgl_name lvgl) # Local component + set(lvgl_ver $ENV{LVGL_VERSION}) # Get the version from env variable (set from LVGL v9.2) +else() + set(lvgl_name lvgl__lvgl) # Managed component + idf_component_get_property(lvgl_ver ${lvgl_name} COMPONENT_VERSION) # Get the version from esp-idf build system +endif() + +if("${lvgl_ver}" STREQUAL "") + message("Could not determine LVGL version, assuming v9.x") + set(lvgl_ver "9.0.0") +endif() + +# Select folder by LVGL version +message(STATUS "LVGL version: ${lvgl_ver}") +if(lvgl_ver VERSION_LESS "9.0.0") + message(VERBOSE "Compiling esp_lvgl_port for LVGL8") + set(PORT_FOLDER "lvgl8") +else() + message(VERBOSE "Compiling esp_lvgl_port for LVGL9") + set(PORT_FOLDER "lvgl9") +endif() + +# Add LVGL port extensions +set(PORT_PATH "src/${PORT_FOLDER}") + +idf_build_get_property(build_components BUILD_COMPONENTS) +if("espressif__button" IN_LIST build_components) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_button.c") + list(APPEND ADD_LIBS idf::espressif__button) +endif() +if("button" IN_LIST build_components) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_button.c") + list(APPEND ADD_LIBS idf::button) +endif() +if("espressif__esp_lcd_touch" IN_LIST build_components) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_touch.c") + list(APPEND ADD_LIBS idf::espressif__esp_lcd_touch) +endif() +if("esp_lcd_touch" IN_LIST build_components) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_touch.c") + list(APPEND ADD_LIBS idf::esp_lcd_touch) +endif() +if("espressif__knob" IN_LIST build_components) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_knob.c") + list(APPEND ADD_LIBS idf::espressif__knob) +endif() +if("knob" IN_LIST build_components) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_knob.c") + list(APPEND ADD_LIBS idf::knob) +endif() +if("espressif__usb_host_hid" IN_LIST build_components) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_usbhid.c" "images/${PORT_FOLDER}/img_cursor.c") + list(APPEND ADD_LIBS idf::espressif__usb_host_hid) +endif() +if("usb_host_hid" IN_LIST build_components) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_usbhid.c" "images/${PORT_FOLDER}/img_cursor.c") + list(APPEND ADD_LIBS idf::usb_host_hid) +endif() + +# Include SIMD assembly source code for rendering, only for (9.1.0 <= LVG_version < 9.2.0) and only for esp32 and esp32s3 +if((lvgl_ver VERSION_GREATER_EQUAL "9.1.0") AND (lvgl_ver VERSION_LESS "9.2.0")) + if(CONFIG_IDF_TARGET_ESP32 OR CONFIG_IDF_TARGET_ESP32S3) + message(VERBOSE "Compiling SIMD") + if(CONFIG_IDF_TARGET_ESP32S3) + file(GLOB_RECURSE ASM_SRCS ${PORT_PATH}/simd/*_esp32s3.S) # Select only esp32s3 related files + else() + file(GLOB_RECURSE ASM_SRCS ${PORT_PATH}/simd/*_esp32.S) # Select only esp32 related files + endif() + + # Explicitly add all assembly macro files + file(GLOB_RECURSE ASM_MACROS ${PORT_PATH}/simd/lv_macro_*.S) + list(APPEND ADD_SRCS ${ASM_MACROS}) + list(APPEND ADD_SRCS ${ASM_SRCS}) + + # Include component libraries, so lvgl component would see lvgl_port includes + idf_component_get_property(lvgl_lib ${lvgl_name} COMPONENT_LIB) + target_include_directories(${lvgl_lib} PRIVATE "include") + + # Force link .S files + set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u lv_color_blend_to_argb8888_esp") + set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u lv_color_blend_to_rgb565_esp") + set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u lv_color_blend_to_rgb888_esp") + set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u lv_rgb565_blend_normal_to_rgb565_esp") + set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u lv_rgb888_blend_normal_to_rgb888_esp") + endif() +endif() + +# Here we create the real lvgl_port_lib +add_library(lvgl_port_lib STATIC + ${PORT_PATH}/esp_lvgl_port.c + ${PORT_PATH}/esp_lvgl_port_disp.c + ${ADD_SRCS} + ) +target_include_directories(lvgl_port_lib PUBLIC "include") +target_include_directories(lvgl_port_lib PRIVATE "priv_include") +target_link_libraries(lvgl_port_lib PUBLIC + idf::esp_lcd + idf::${lvgl_name} + ) +target_link_libraries(lvgl_port_lib PRIVATE + idf::esp_timer + ${ADD_LIBS} + ) + +# Finally, link the lvgl_port_lib its esp-idf interface library +target_link_libraries(${COMPONENT_LIB} INTERFACE lvgl_port_lib) diff --git a/components/espressif__esp_lvgl_port/Kconfig b/components/espressif__esp_lvgl_port/Kconfig new file mode 100644 index 00000000..b54886bd --- /dev/null +++ b/components/espressif__esp_lvgl_port/Kconfig @@ -0,0 +1,10 @@ +menu "ESP LVGL PORT" + + config LVGL_PORT_ENABLE_PPA + depends on SOC_PPA_SUPPORTED + bool "Enable PPA for screen rotation" + default n + help + Enables using PPA for screen rotation. + +endmenu diff --git a/components/espressif__esp_lvgl_port/README.md b/components/espressif__esp_lvgl_port/README.md new file mode 100644 index 00000000..5b05e6af --- /dev/null +++ b/components/espressif__esp_lvgl_port/README.md @@ -0,0 +1,386 @@ +# LVGL ESP Portation + +[![Component Registry](https://components.espressif.com/components/espressif/esp_lvgl_port/badge.svg)](https://components.espressif.com/components/espressif/esp_lvgl_port) +![maintenance-status](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg) + +This component helps with using LVGL with Espressif's LCD and touch drivers. It can be used with any project with LCD display. + +## Features +* Initialization of the LVGL + * Create task and timer + * Handle rotating + * Power saving +* Add/remove display (using [`esp_lcd`](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html)) +* Add/remove touch input (using [`esp_lcd_touch`](https://github.com/espressif/esp-bsp/tree/master/components/lcd_touch)) +* Add/remove navigation buttons input (using [`button`](https://github.com/espressif/esp-iot-solution/tree/master/components/button)) +* Add/remove encoder input (using [`knob`](https://github.com/espressif/esp-iot-solution/tree/master/components/knob)) +* Add/remove USB HID mouse/keyboard input (using [`usb_host_hid`](https://components.espressif.com/components/espressif/usb_host_hid)) + +## LVGL Version + +This component supports **LVGL8** and **LVGL9**. By default, it selects the latest LVGL version. If you want to use a specific version (e.g. latest LVGL8), you can easily define this requirement in `idf_component.yml` in your project like this: + +``` + lvgl/lvgl: + version: "^8" + public: true +``` + +### LVGL Version Compatibility + +This component is fully compatible with LVGL version 9. All types and functions are used from LVGL9. Some LVGL9 types are not supported in LVGL8 and there are retyped in [`esp_lvgl_port_compatibility.h`](include/esp_lvgl_port_compatibility.h) header file. **Please, be aware, that some draw and object functions are not compatible between LVGL8 and LVGL9.** + +## Usage + +### Initialization +``` c + const lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + esp_err_t err = lvgl_port_init(&lvgl_cfg); +``` + +### Add screen + +Add an LCD screen to the LVGL. It can be called multiple times for adding multiple LCD screens. + +``` c + static lv_disp_t * disp_handle; + + /* LCD IO */ + esp_lcd_panel_io_handle_t io_handle = NULL; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t) 1, &io_config, &io_handle)); + + /* LCD driver initialization */ + esp_lcd_panel_handle_t lcd_panel_handle; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &lcd_panel_handle)); + + /* Add LCD screen */ + const lvgl_port_display_cfg_t disp_cfg = { + .io_handle = io_handle, + .panel_handle = lcd_panel_handle, + .buffer_size = DISP_WIDTH*DISP_HEIGHT, + .double_buffer = true, + .hres = DISP_WIDTH, + .vres = DISP_HEIGHT, + .monochrome = false, + .mipi_dsi = false, + .color_format = LV_COLOR_FORMAT_RGB565, + .rounder_cb = my_rounder_cb, + .rotation = { + .swap_xy = false, + .mirror_x = false, + .mirror_y = false, + }, + .flags = { + .buff_dma = true, + .swap_bytes = false, + } + }; + disp_handle = lvgl_port_add_disp(&disp_cfg); + + /* ... the rest of the initialization ... */ + + /* If deinitializing LVGL port, remember to delete all displays: */ + lvgl_port_remove_disp(disp_handle); +``` + +> [!NOTE] +> 1. For adding RGB or MIPI-DSI screen, use functions `lvgl_port_add_disp_rgb` or `lvgl_port_add_disp_dsi`. +> 2. DMA buffer can be used only when you use color format `LV_COLOR_FORMAT_RGB565`. + +### Add touch input + +Add touch input to the LVGL. It can be called more times for adding more touch inputs. +``` c + /* Touch driver initialization */ + ... + esp_lcd_touch_handle_t tp; + esp_err_t err = esp_lcd_touch_new_i2c_gt911(io_handle, &tp_cfg, &tp); + + /* Add touch input (for selected screen) */ + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = disp_handle, + .handle = tp, + }; + lv_indev_t* touch_handle = lvgl_port_add_touch(&touch_cfg); + + /* ... the rest of the initialization ... */ + + /* If deinitializing LVGL port, remember to delete all touches: */ + lvgl_port_remove_touch(touch_handle); +``` + +> [!NOTE] +> If the screen has another resolution than the touch resolution, you can use scaling by add `.scale.x` or `.scale.y` into `lvgl_port_touch_cfg_t` configuration structure. + +### Add buttons input + +Add buttons input to the LVGL. It can be called more times for adding more buttons inputs for different displays. This feature is available only when the component `espressif/button` was added into the project. +``` c + /* Buttons configuration structure */ + const button_gpio_config_t bsp_button_config[] = { + { + .gpio_num = GPIO_NUM_37, + .active_level = 0, + }, + { + .gpio_num = GPIO_NUM_38, + .active_level = 0, + }, + { + .gpio_num = GPIO_NUM_39, + .active_level = 0, + }, + }; + + + const button_config_t btn_cfg = {0}; + button_handle_t prev_btn_handle = NULL; + button_handle_t next_btn_handle = NULL; + button_handle_t enter_btn_handle = NULL; + iot_button_new_gpio_device(&btn_cfg, &bsp_button_config[0], &prev_btn_handle); + iot_button_new_gpio_device(&btn_cfg, &bsp_button_config[1], &next_btn_handle); + iot_button_new_gpio_device(&btn_cfg, &bsp_button_config[2], &enter_btn_handle); + + const lvgl_port_nav_btns_cfg_t btns = { + .disp = disp_handle, + .button_prev = prev_btn_handle, + .button_next = next_btn_handle, + .button_enter = enter_btn_handle + }; + + /* Add buttons input (for selected screen) */ + lv_indev_t* buttons_handle = lvgl_port_add_navigation_buttons(&btns); + + /* ... the rest of the initialization ... */ + + /* If deinitializing LVGL port, remember to delete all buttons: */ + lvgl_port_remove_navigation_buttons(buttons_handle); +``` +> [!NOTE] +> When you use navigation buttons for control LVGL objects, these objects must be added to LVGL groups. See [LVGL documentation](https://docs.lvgl.io/master/overview/indev.html?highlight=lv_indev_get_act#keypad-and-encoder) for more info. + +### Add encoder input + +Add encoder input to the LVGL. It can be called more times for adding more encoder inputs for different displays. This feature is available only when the component `espressif/knob` was added into the project. +``` c + + static const button_gpio_config_t encoder_btn_config = { + .gpio_num = GPIO_BTN_PRESS, + .active_level = 0, + }; + + const knob_config_t encoder_a_b_config = { + .default_direction = 0, + .gpio_encoder_a = GPIO_ENCODER_A, + .gpio_encoder_b = GPIO_ENCODER_B, + }; + + const button_config_t btn_cfg = {0}; + button_handle_t encoder_btn_handle = NULL; + BSP_ERROR_CHECK_RETURN_NULL(iot_button_new_gpio_device(&btn_cfg, &encoder_btn_config, &encoder_btn_handle)); + + /* Encoder configuration structure */ + const lvgl_port_encoder_cfg_t encoder = { + .disp = disp_handle, + .encoder_a_b = &encoder_a_b_config, + .encoder_enter = encoder_btn_handle + }; + + /* Add encoder input (for selected screen) */ + lv_indev_t* encoder_handle = lvgl_port_add_encoder(&encoder); + + /* ... the rest of the initialization ... */ + + /* If deinitializing LVGL port, remember to delete all encoders: */ + lvgl_port_remove_encoder(encoder_handle); +``` +> [!NOTE] +> When you use encoder for control LVGL objects, these objects must be added to LVGL groups. See [LVGL documentation](https://docs.lvgl.io/master/overview/indev.html?highlight=lv_indev_get_act#keypad-and-encoder) for more info. + +### Add USB HID keyboard and mouse input + +Add mouse and keyboard input to the LVGL. This feature is available only when the component [usb_host_hid](https://components.espressif.com/components/espressif/usb_host_hid) was added into the project. + +``` c + /* USB initialization */ + usb_host_config_t host_config = { + .skip_phy_setup = false, + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + ESP_ERROR_CHECK(usb_host_install(&host_config)); + + ... + + /* Add mouse input device */ + const lvgl_port_hid_mouse_cfg_t mouse_cfg = { + .disp = display, + .sensitivity = 1, /* Sensitivity of the mouse moving */ + }; + lvgl_port_add_usb_hid_mouse_input(&mouse_cfg); + + /* Add keyboard input device */ + const lvgl_port_hid_keyboard_cfg_t kb_cfg = { + .disp = display, + }; + kb_indev = lvgl_port_add_usb_hid_keyboard_input(&kb_cfg); +``` + +Keyboard special behavior (when objects are in group): +- **TAB**: Select next object +- **SHIFT** + **TAB**: Select previous object +- **ENTER**: Control object (e.g. click to button) +- **ARROWS** or **HOME** or **END**: Move in text area +- **DEL** or **Backspace**: Remove character in textarea + +> [!NOTE] +> When you use keyboard for control LVGL objects, these objects must be added to LVGL groups. See [LVGL documentation](https://docs.lvgl.io/master/overview/indev.html?highlight=lv_indev_get_act#keypad-and-encoder) for more info. + +### LVGL API usage + +Every LVGL calls must be protected with these lock/unlock commands: +``` c + /* Wait for the other task done the screen operation */ + lvgl_port_lock(0); + ... + lv_obj_t * screen = lv_disp_get_scr_act(disp_handle); + lv_obj_t * obj = lv_label_create(screen); + ... + /* Screen operation done -> release for the other task */ + lvgl_port_unlock(); +``` + +### Rotating screen + +LVGL port supports rotation of the display. You can select whether you'd like software rotation or hardware rotation. +Software rotation requires no additional logic in your `flush_cb` callback. + +Rotation mode can be selected in the `lvgl_port_display_cfg_t` structure. +``` c + const lvgl_port_display_cfg_t disp_cfg = { + ... + .flags = { + ... + .sw_rotate = true / false, // true: software; false: hardware + } + } +``` +Display rotation can be changed at runtime. + +``` c + lv_disp_set_rotation(disp_handle, LV_DISP_ROT_90); +``` + +> [!NOTE] +> Software rotation consumes more RAM. Software rotation uses [PPA](https://docs.espressif.com/projects/esp-idf/en/latest/esp32p4/api-reference/peripherals/ppa.html) if available on the chip (e.g. ESP32P4). + +> [!NOTE] +> During the hardware rotating, the component call [`esp_lcd`](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html) API. When using software rotation, you cannot use neither `direct_mode` nor `full_refresh` in the driver. See [LVGL documentation](https://docs.lvgl.io/8.3/porting/display.html?highlight=sw_rotate) for more info. + +### Detecting gestures + +LVGL (version 9.4 and higher) includes support for software detection of multi-touch gestures. +This detection can be enabled by setting the `LV_USE_GESTURE_RECOGNITION` config and having `ESP_LCD_TOUCH_MAX_POINTS` > 1. +Example usage of the gesture callback can be found in LVGL [documentation](https://docs.lvgl.io/master/details/main-modules/indev/gestures.html). + +The LVGL port task is responsible for passing the touch coordinates to the gesture recognizers and calling the registered callback when a gesture is detected. + +To correctly distinguish two finger swipe from rotation, we recommend changing the default value (which is 0) for the rotation threshold. +From our testing we recommend starting with 0.15 radians. + +```c + lv_indev_t indev = bsp_display_get_input_dev(); + lv_indev_set_rotation_rad_threshold(indev, 0.15f); +``` + +### Using PSRAM canvas + +If the SRAM is insufficient, you can use the PSRAM as a canvas and use a small trans_buffer to carry it, this makes drawing more efficient. +``` c + const lvgl_port_display_cfg_t disp_cfg = { + ... + .buffer_size = DISP_WIDTH * DISP_HEIGHT, // in PSRAM, not DMA-capable + .trans_size = size, // in SRAM, DMA-capable + .flags = { + .buff_spiram = true, + .buff_dma = false, + ... + } + } +``` + +### Generating images (C Array) + +Images can be generated during build by adding these lines to end of the main CMakeLists.txt: +``` +# Generate C array for each image +lvgl_port_create_c_image("images/logo.png" "images/" "ARGB8888" "NONE") +lvgl_port_create_c_image("images/image.png" "images/" "ARGB8888" "NONE") +# Add generated images to build +lvgl_port_add_images(${COMPONENT_LIB} "images/") +``` + +Usage of create C image function: +``` +lvgl_port_create_c_image(input_image output_folder color_format compression) +``` + +Available color formats: +L8,I1,I2,I4,I8,A1,A2,A4,A8,ARGB8888,XRGB8888,RGB565,RGB565A8,RGB888,TRUECOLOR,TRUECOLOR_ALPHA,AUTO + +Available compression: +NONE,RLE,LZ4 + +> [!NOTE] +> Parameters `color_format` and `compression` are used only in LVGL 9. + +## Power Saving + +The LVGL port can be optimized for power saving mode. There are two main features. + +### LVGL task sleep + +For optimization power saving, the LVGL task should sleep, when it does nothing. Set `task_max_sleep_ms` to big value, the LVGL task will wait for events only. + +The LVGL task can sleep till these situations: +* LVGL display invalidate +* LVGL animation in process +* Touch interrupt +* Button interrupt +* Knob interrupt +* USB mouse/keyboard interrupt +* Timeout (`task_max_sleep_ms` in configuration structure) +* User wake (by function `lvgl_port_task_wake`) + +> [!WARNING] +> This feature is available from LVGL 9. + +> [!NOTE] +> Don't forget to set the interrupt pin in LCD touch when you set a big time for sleep in `task_max_sleep_ms`. + +### Stopping the timer + +Timers can still work during light-sleep mode. You can stop LVGL timer before use light-sleep by function: + +``` +lvgl_port_stop(); +``` + +and resume LVGL timer after wake by function: + +``` +lvgl_port_resume(); +``` + +## Performance + +Key feature of every graphical application is performance. Recommended settings for improving LCD performance is described in a separate document [here](docs/performance.md). + +### Performance monitor + +For show performance monitor in LVGL9, please add these lines to sdkconfig.defaults and rebuild all. + +``` +CONFIG_LV_USE_OBSERVER=y +CONFIG_LV_USE_SYSMON=y +CONFIG_LV_USE_PERF_MONITOR=y +``` diff --git a/components/espressif__esp_lvgl_port/docs/frame_buffer_settings.png b/components/espressif__esp_lvgl_port/docs/frame_buffer_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..bb5f4efe9ec38ae8399b5ee9625818262ea96ac3 GIT binary patch literal 18224 zcmdtKXH-;Mvo_jhw*d?YNK^p{O3vAUV1s~UlqfkPIW{P$Y$RzTAXy|ymMmFBa*`aH zAT~il)8q!4aI0~@?>OhY=l;0gxcAR>VE16Hxz?I%&Z?)Xo~k17p_1I$Q#7X#2*g?B zeHm2*;&1~3aj5&`aX5l+Je!I@TtOgZZmYY;EsT0SjU}M)TjXD}J_Q|nc)=x~e&~v& zj>84ap-ZfH;zX#vAm^@OMkD2ZXSBL<7Y0c9ewvF*aMje+NRAifmo)Cj=sAk_^!$x2 zOeG8NQFDkFx}@9adMr)wFCAB#R-s#i1)PsQvK=m8Q8L;`BnBY3FCd@0KlP|FW0UA4 zCui{X{d#xKpV7zNP=WcvUx#3R_d4ucNZ+@(u23I=?`sF2=#%gUh-b`xyzq4e$x&R6 zKz!{U9v-GR^^zHBSE_S@oI#!WLf1rHK)Y=yo#@B%^78DyNdj@ovCMhxcUSf(E5hVR zE%mchReyub-k(zhqP<5|QemOm^IKnah5bxQib_g2XhOIw3~Zm`W@lB{C{&%C3MClA zhW19BI?q2iWO(*m@S({Uxw*MI`iV4-A?JG{BPokZOBG^H4`y5~DlXQLl$2cA^0wCT zJ{3^+q~E0X>E_ae#!IZb1Q*gWY0$#n!s5%Sk=Cqh=Ox7CjcMv5P2w&tE><=+>JkiE zJk;-)U*5S|Ho91}%D5|6n^uL?9GyVZJXOV%oobAF$hGE@ptN5`kA@8 zk<4ew$zyFBPR=_P6%{qjI&-}bH4K)ZP5#Kq$vN%AFNjKlX`#C}OoRQrs`PP~Qf^za zeBOxfQ;yMUf`^|!JN*3C@cr$b47$~H!_8eEk1G9oBVMQN7S-gM)$}hZc2V1d?TL7s zCC@4=D=RefG5^~x&d%=#c+Vcal?(gQq;TZO5%l!o0t`+Q)=NhRQ!sr=SU`a7+)1_z zfhi$;a>*)Xqjt#V53|lB1P#!FO8<<<|^sw>7Bk){*K{8$WL~b zx=aBoLoZLyHl7%o&=4L}*6caB#F9XJ=6IC1ZpdRbd)kvni|ogx3PTqc?F5B{V#01K zCvapLr)M`hjq@|@{j*q~hnsNI*ndC0PMDmfIo@954+#@H*-rGtgDsx3Cuqr3$ ze%{6Di?rcO8?lcz0;ofN+Exzo^s6zJ?h*+%XhLFo3=vnfc#O_mVQrIM{l0Tvg=vY) zrAk0pFex?lUYCWsPmOP_^WnRCdhwT2LVomPFgK9X<*ieEosPWw)scMrb#4uRTi&G9 zDdabiNmhXcI>xx@Btmz(1N|iU)%feyDOEQ&!fQDf%mQh2NBhHY=Dd&w#0^!-etuRd9Tao5}R-mQAXo7VTnXFgb5?qUDf^{FFHlqsMtfAV($uelqG zc{LApx*or55ItF=pIUy-w6oo-zW!o5(3us_s?rf(9%^*~8bw;p$Z4syQqAH+x6;mo zf>s&wd@-bXzMSK*S~u+7@X3*lLZ0|Wm-zy3=bB+Qt&@k=<5-7KQ~&cOBtf%XG|7C< zD=)r(B*50zc5Y!ocX{veQpElDJ8RH6}lQ{`}Yz>kRU-RM7sp#YIg4nm+zc3F8u4rqm3gZimE7{q%N6qWsU* zEsC(l_Oep40FVMbHwwy%^77v9Zbu9`8&~7=BPY5$6uq-7X=)9hc8PX0crT=-T^FSz z1C2|GZ+&mydE`xyO`y!RCobnnNZ9vYo`jNr8=isB`~MqA2L8WyDxlQ4*+}xb10F4F@b(?29yak(X3Nv+Fa9nEV;z#$nGU~ zWeH&o`DnD^$>$0F4i%{$^$EG;`+sNmvK6%CEl3VgL!JD$}%+z50M zrcM$+r=^Gf((R;=6+iQ8fN}I^pMKD_M z%HPoWh_4?cJQazHAU0?86tAzXeM(Pn)Fpp;2LM{r);349>JECFNYv2LvA!2bddr8A zA=Jd(y`rgx23e9~?E1L4xhbG&XlbXG-Iuox)4-e~5Smy2Z;j*s*}nf{QgMoktY*Gu0=73hnFrF-E{X|Duwcq)<(X5WW$y6v zH!B!RzJ0hN*KEp?@*+j%nuDCnkd4rg4Z8jMkYH)tuwCJh$-p8XW+cC#LO*F%U0M~x9ppO>32>oG=mw|B9R(#7_-=d*_dE6z_37RffGnQ^DPIC($D zUhJVsjO!X)nA-dv+;H7YKuT27OEkGTBieM#$y<>afqA8FRN8D;n(;k%NC7jFsyI7e zh)J~%cf_DgbcQ>3gq-wB%Y5w0icIXPoSR*VA8{G;sVqW@N~{9>;crIm3b$)doH|}h zjj+Scg&^zgA8`1fRJH6Lu#vv(mF9-q+4X3nyVu{AO<6OA(SdBj&?n z9!+?yPno*fW^(j_4bREDsiI*T*sm2C6P00mY06b2a!5nPPV3$yWQ%7YhZ&|-!G7Q2 z%~O%=?d|P8DGHDF-^8^Cd?L!778`uLZlbhtJ(!td7F9d(cpm?aHKTd6*vK(6&xw`Z zwUO=Kk2rpXBJL3%M=P_%V|Bg3*VD6ot}h0+ryV;~{=J&#VN~RxpeS!z2<=$(>E+d2 zW%5ZU+aSBrNudo7qV~;^iwmdE!mHda=M_t>-Td+@H+a4~;;*yyjV`mV^QCf=Hoq4f z!KbaKI@+)eOoy1lR}Xv*S0+7(&1@;k$W*5th>B^A?T^YVs5A-j3aF>G0}=0-Rro2f?-^+G(N(L)8|QShYK; zekWY5r`7vtZtg6$LbchjMrN>D?|vya{m9I?5Cd%&hDw^h&bRpjCh}&9ftTl!!I!F> z-|5rmf}fwErcdk-0a2zFQM0q0fxSa<#4``oY*pG}+9SA4c*S-O3(EgC4Tdo7M-akQ^Imsd{C+8J%2VvxP$lo4xN10^Tl0fjS`y=zfiNpBhw> zY(r1kj_M}a# zr-?{=TAJ6ESiRVuw1Jzh(oO^1C5*4|S?nGyPH4diO}GU{P1Dy4rYBFL1~g%_xn|9g9<4cZs*TO(gAp&5OE;>=E)>>SL_iLyGr0 z$JKw2(w6W1!n{tX$#}b5FUNp$jdksK#kK0#D(2*oBW-FE%r&l4sN}`yn4;&#E0p$p zwYgeGx+3HGs<5E5sjj`RyQ01C`@NQ-lVReS;Yqx_Qd}CNKgi5eg*0<;d0P>*;pH$a z>Q|xkY9w~R9<`D^r#NNyd-RcB27QB*ie84|{N?py2oso-hpn^sW4yPsBK+6G`@nZX zRa!>T`6d;lYDn@O0$w-q!n2ug_PO#(=RduhIo)Tbv#|Z5L!Y`!j!Jf|>t*crZ=)4H z(cLSkxkiJk%}C}9qmAAt06CV}a#h>x7$R?)_xp7E0!+7(fuLmgk3pH0R*u}T2OauK zv)i8>_Qu%TN6dU&ECUrWbeEiJ-SiRDXUGuGbZ_&>>PN@LsEiXGs&*#?tB74LRr}5B z$1o#RF7Z}wIRko^l0A16Dl{srQMGNtE_OGgoQ%sDvc}{3_hwU7^!{c^XVax)P@BX) z(Mx3O12hQ;_u^IL&gbFyA#IIJ+tA5?;Sx~}G^2a0-q`e~hwEu8f@KB>1Yd|@ za2%|A?j8X<=PR9CP55=X1F^g{O1r*6eU}5a&Cc zY-qa7&;M7|TEiQky@oe;7~A5@vkOLYtH{NAm)_j%a$NOAa+G7`SjRD0@k zSMjF29a`;=9JJl;H`ZC7OxEdL`hXSk0w3v%(@|sX>FuU>p*7A*(jHTlLmMxN!m~PI zwtb}xm`sjpuFK$Iov)>i6xr(*rT3kUlQr+;-sv5q+1(y+`@I)G&zTF}HN_1--h#RK zu-w^BAHEOD$HQl+_J-?}aQhpho^R}Lz}zXBsF|$yHhRtHM3&z&TcS(@_e#foo^90u zRbhTEIDh(jy4jk!bl_b54;Ey1o^-z2euG+NMmLcFAY<=h))i1Fzq+Fd4{iE&b0OCS6n!s@NO9?ca0U*Z5#2mpb= zf5rkuA8kgyb~qLT7*F(>9Pz#x#x zt+XCa1zI=l=B4+d{@xxB0~A&bNbq<#&$0P|9)W1$h6n@0EBj)8 zZjKqLl-^U>G4Yg*GU$;i73KCkm9)l(eRNCff)#4|kgc1hw%!HOQ>Scrqb>G0i@L9gKn0~vb>XDPLgA`qX&L_IWL_xEc8hvFf@P{`@>LWChKJ>&|v zmIz(Pebkm&&NgbJ`(1+M`6%YC>H>YtKs^Q7O#gkKe5o1L<&A$L3ia&W$lv1qvK_~; zaIZ35E(j0`b4p4y>FMdGJ$-%mGiAf)=jZuVjivqysI%_IX9=^eGwxw9G06MlzLm23 z8^TunK8$WwIok>3uZ!{Z!}fvIRL0{$#p4%H+zr*{^Yzb6YX|X!bWUL*A$c@J-SFdK zC4_xvdk}a`hh1kP?iJnm=yW&PGi8Ho*N5zt5yVpw1vjbPTwFMZ6U0>GUawNlgzgjV zrPTq*z(%a$ck2n6mq1n0;&zwl9eb9eIx<+@Z==@&_B_F<8Ts`Zgo)4JFxb>y%BB~TF)&5rnp!(}rv zOMb)q8+Vc2_FnidP@!ik00botg{x| zC=6TQ2pC{Sp75zlWt6BM{gnw*>L;tEwv?)Ts>Z#h->ba8^^w_fCQ`n5XUPYLYI@>e z3PY@e49~<@iF_1h(psPXs7$-Y{$tYcZ+c(t^G)f+Y7=Bbw8w~UlbD;`MB5giiAlL43f7ih6?3+N2_idcr+!Qz>j{4*WoC( zR!CiNjr}(9d2ZhAkuE!Mw2MsCGZ`g}tPh=pR4e`#}9f#f0cSBDN3KQ zyHlU`-Kd$GPAgTBIYBl{0bZra%Qfrt*A{x>P$N1ea(_|k4$!){UY!eGZ5vN0{{R62 z!}f^m?r=4|W)!@_L!Ofj9@90uJ?7|?{Rvue&Ro#A51(OpQ&5sZIW?^sE_dDYmx4|< z*9kcBfpp?@&HaQJ+-x52OL*Ru9=O3T=gG`5;%0d!o@0Q(n3rEp2Rr9?m#5?6{?hOV z=26?ri2?@Iakh%^jzu68J_<1`3ntP{Y{3a zWV1XOF z3^R;N9;5k^Sx6;U)UJ}0=QC#MA_^ytkxuA)`ga+@$e{l+riIk^*k^2o{DnQJcu@Yo!MkwX=n8k5YHxo(2gRxA`Q6>!%$%I!&mY?^i}w0^V(dxNF%O5)uN0q(TV+o;Yw|mJ+fDDk(RAfFZnP_xS+N zb3Rr5=+LoopK6~h3#w@jScFZv89}ASeoEuF{|%bpu=%kV7t&(>(X3DBgE>uKe<<_&6_)w$=Tn&86g)d zN-pm{m^=?@q`s%5=;IyEG!=NnB(t9brA+5|uQ;P#&vwfN6tVR|G8}KZcNBYbvvH{F zm%Xn??1E5ieIFb(*k4&g9YEPLQ&{~UWl1W# zzwy$M%p_w8OjAC|iX(1P)r$E0?5ude&8k(C^CTCFNH~eeK3Xes_c#{2J!IXS=d%~W z3g(0KaLw0-aaU^%$Q01k%T1J{g)Z@bmMryT!(~S+A!^^<({Ts*&3HbT|EH?AE-8*H zP_3xmyGKd{-jaf;e6_~cvmnFp4o!BY|J>EzFzyhDKsYmM>T1BjG8z`2Y;dzMyPhQc zO^X@1c;c@Hc>30BL+=26a)U15f5=A)Zv*x6XR)$mc&hIL#2K&09+(=0n;(oEJ82NX zjEW|igEs^6IhCv6p+UL~Qh>3x{~fo+3P$CrjE%RKzA0*GME+JNc6&r}U=fSYOqm)W zHRo6{YH9nQp094fs8xdn?|~|(v4FIm$Ke!Dx1K$FmWG;I8wRDHFq@$o zH~{n&y^i+(f>{-&6%k{>%yPkuPiT(80*w_qhdPwJteXhN&4(wklbD&DSa|`@DYf>5 zxNEGY1>;+UN|P)DM`kuLb9gYsM2xMg|Aj5AdQ~5_Q&opIZP;C8LTNq+v$T24A z+J}edg0*Vj8|<*n9NdQbDE888y~Kx`J+vG*iZ7BZ;;ECh?k|-BX3|s!Z7Hb$Po4!| zrOrEdunDIw!nv{cyFW*by_aAEKYW-R5gwl^2Y=`T=|9mex3rvuXC~)h;2vSzprNI?-!}!>NasE68duPFs779D(0$CG&oaU~4Gdm3DpJhMN;HmalO>@N! zn<_SHxtw#xZP642@{K668A} zAVbY`#7Ae4v6%$ExDakE{~#3E6RvvgD*M|?fk*{;7OxDOjqb9kZocM{xz6^(h-WO> z9Ml7SeY3y2lB08vHCgiuzY=u9q0Ms7ayY=3T|)$Q*~jR&S@OZUNUkt_4I?9&7&M@f|pmv+><7Bg;zM_dsLuFaa)hHwQ# z3us882zjx$?}bzKpFL}7^GT%h_1*EHynj0Z182`7*G~-W_<$?~yn5xly=DnE9WY^b zq|9l$Ecp2yL8tYPX1`_x>7}LJJf;eVc)XD@36F=!Y=wqeI8qDbhvZvSzgfuh#3!K5 z(%!v~j?qbod6UK(@jl^QgFQ!nQ*JA>qJHJ7)J*I;S~i>O+qJc-O$zH8mTmoDlmRzV zx~_V)c-(fGT6v8WM@y2OP+b|EvG)%y7;io3d+>Yqt= z_9GcIcPGTJB(linS#PfdSYCDa{C&6myR^=V&_;-ksoR2TpmEefp)^Zqd_nuGCpxqA zM@J(#*cC$gw6YDHKh{kSibeGGndc?YIdIYS=WhR+|L>ky=gYcBWr-Rmu%L-h41wC7 zO`k5fx^DgPP?n;ByMY?j*uYwS)5!g$;@Wy)E_RLPexgRfdiA?RB0O7Oi9|{rv(~6} z-`!p>e>&kOCMMQa(rX&kdXGnTgT+k1WlvAhVDlE~Hq&8as@e0|0{m5mYC4ZnFMH!M zZo9YIojwJ|yv$IecC$;4QA0~hE6w_lrpa|5eYKe}`$(t0v#4{2KCv{ z6R>F&t9I6}0YpWEL>roDd*J4ijp>5x%d|1uYq-hiE7R;584AaL(v#19>-EHq5$T6G z`gI-xHZ=(!HJ;4Q=p0jc^|R34Vg=hn67`C)>ah%T1*7KdD0!B%)=IZ#S#6~cAKi8E zKEb|GGnq3t@ZI~EAA?!G z8C_cYEjBm2e*UH5ld57`Y*74}4+gwE*`R$9i<0j84MU>mqgara!ghdI?EciPmst9R zG^Bn?m&eZ9S`Er6m_cCy7LjN%aq&whkN&G{sVH0artREL1W9FQl`Xnf2ues~KYxA*n7hKNq@>$W z#{=Of@A@o;W{W7@*SqD$f{X~mYxSv}NI~-jkw{MM>bO%4WC3+?vR8pQQ(JZ)(N!Ad zq6smplDaA2I`>{yf2W2S$%2{JSntJWwXI(Y$^t8*T?SV{4Gpk;J~iVutDaSqnlDlx zko$goTSHV+B-^>uy}-6i*pEM8L$ebgH_DvNWyB8E7{e8Ac|hk;1l6~Zk3flmJbJ6P za>{)wLaWGWrb95Ja8O-VzkB;(x=l}bOh#sZ$gA$1_WFer`InB=BNDK9tmv()$}(7`En_Oyp^eF zDDPHtRPWr)YK7%!dJoIN5N6)|wbdgihj&jltU-ysXKjfUjZXePUwLAa#vEdwr?A~j z!Ve7gv@hE4sblST)%mqfupm<_r1Dx=eYB>?QLVq`082>p7CPnopo^lcPtvlmZ)WrT zydnL9-9s8aE;SqsyMm{)NE_#M6njm`WszP{cK=>@mv2|^knnWw=crG=-Wq6ZG1X-9 zRkr{UPVtwY!}(|lVtJa>Da*O=Qi1hZ9r*{rX?g~(`}z@*YbhKIThDDS(ca7i7w0R@ zQS4zMdZPLz#$zs6+>GGmt(O;=w7PS-hst$QD@Hl8P`akMjixv93O=dAOy|!BpKkij z?VFYnbY7ACl^}{Y8`Py(rThEBItxaZBWpKP9>0s0$8TLQkA(5}A^LFQ*W$9+S+D8a zq-7Ow+I1Xu0mT$7*^;vL*_`Ld!la`)D>zA$xk5M}ygIl!OXa-!J@J0w_?(_+CzXaK zK`^4XH^bR0ej&8t5?MfT)ybNxfS!gBTzFVz6Qr#tClirqcq7>I;Ds9Yz+Y)(lxX=)~Arjs3T*|%;3I~MqT^1bZgP9(Zt za-CQp?)pggJoqzDNN2hd_g|Xj8fZSkVKYaFv41$Er!aBDZ6j^ts!m9zN^aFWCIwNq zo&eADyen$|4q82*Agm7Weh|TZ;5i@cH1a({HMuA4Q)};gT$@&#ARI&zJ%4oD2 zo6S7^AC~D0uuL|U_{Ny(&FAtZW$IhS<9~P|KUtm*4SqG6-E>BnCkpFZ^acFtXW!&_ zI#iPL_9D!3^;CyYb`ic4T7M{lpYYTlZR=N^ioUG;br5MEa83<-v+o5YGgbm>viJu6 z8OnAp*r{!8s*uJDS92e3zDu}C3Ozr#la3E&C?QqrqkKQ1>B+SZ-gEg1^~@WvMm?Le z`^sqbS<8Y04(=C;QEISon9|zi$fzFpWd-8%`gE;`+=F1rka1yhJsf$Pd za1PlV>=nW<|Je189bGlX#7I<+&>u46Xzk!(V_-Z3KdxBU*dCCCJd-1=)&DAxg_1KI z4u?+Z=ukR#_z)6}!S&brpWw_|R(cS(RhA~@p|DY5_3vFrw6$cM$3i8gg#Z*WaY4mh z^Q?1bdb->}%=*4sZ=@+v(xVtgJq$Z7jvumIw+6SQr{! ziA^>A{@sh)dM;epbX+~TC6DhhNof>F0U}gh(a_L9nJx>GL|3^>K!p3IXI7vTDj$K= z(~Kyya!Rg$VFV(l8?IKXO-fjZij9npawc|0<&#*mV4QVoi}YE`9`I?@rhc&MY{);# zu5aZN2W7uf1``=YyRKmUjJg~hCF%Ss5u2gsQhdBOBq8@{iL%Psn9DS5hXQswI#X3aghr+j-v03c2VwUdPBajGt|2oAiLrnSr3Vbn$f;WQ5HQQx%cXlt zX!T|b61~#FM4k6`d9UUzvxdZU_$XvtNXqq8{3uqCZ@GV50S$I-o>B|!+Ow%E)OpXr zH;XB|tTvn5nYEU{eE@WbNO<1y26MTf)|xfRpc|lZUmbzWv6*i8d|i5z6?B}c#@;~? z?yJq^=kQ9gnoz);u~Tc`q**+4@VL*qN3myOMOK}VszZ0j3%&s0AMi>3X1U~+eB0dU zlod(k10+Of`WeRE29;+))piFOt9{JR;rN3}lH^d?q zw=4fVJAy<}{fYce8rz01&^g)iG$^0l*oM*WsNd7?xIETg@8GCUD z*Zw3s7mWYNh>F6{^TK=&cO*@lA1q!f3o3*NnBb;A&EJqh*Zrw-6WhUSgfj;)3WHK zqjw69^8IaCd53nR^pPG&5~3dgg|+bSfZjqNp=1ywQ8l!kckv+GeBT;;NT@oMujs(J z&i}0S*L(*Y#IwT@YPDZ7GLWEE)1KDWS>PdG3X~z$C-?QSAVJYopu)Zpo1du;x1)F; z7cYPQ%<)U0u*>efB)IZgT(kH?uzeFFfj0K zES`#eDx&-D4_Wpf#iTY>)J@G96yP!|OYaty>FelZJ!CV`F1Z z(CzW#>(^T)t>A&*I@AjGbYkKOlqVNuP&BaRgG6EI$bjjy=$|JIm}to`yqVg`NQxAJ z$U4Y-f1rkt4M7Eb zKd6T;MXMPsfutj}57M^(Yk7Ri;WG`u*RK$F;RkRcocZ3h^M#z$XJRlVB_(M&Gfc_$ zq_z_rZ2jqN8wnx!wk%cF>!bqS(rh2PP1-jMc`QoOH(K2#R2kDdIy#Pp=}1}F+6oc} zdwPsZN{fr5ak4e4jA8egjqEVp-RP18xYe#)@U)o4lCV*oy&V^n8gkMj~Uev9VM+74UvC*F0|0q68>< zmJ{wXCrT&Rz`N3RnqPZ6HhxEXOgT>ENx$u6G{E2I#)__S(lc|+uw{y))0(&j<8aT) zbSeEC8V{Kos;a6WPcrTKr>LiG9jIJnvsPEqLyIo^!B$p=FgfsOPY)A`#9}wRz7s9d zGVcrMN6O?4E65&$mflp4eIysgAQ%O@!yL`TDw_ymm}Mal<0_tv@029ylHRWo%NvFP z%`B7#u(z_X;3;*g7!`Ax4ldKz)8k~{qJ|95YF7WMqK-~1Q~)LRYo2K&8oi!y$Is3} zYl9Hcf-Mp|y7cWd_xoMWSJ=^srztt&@f{yU#*(`}ML&TKhe^jW0TB^xcv}GCHIGv- z?+lmPCzgdQOK|tu@r1_K?}qr z0qkQ~Bh?G$43SVmT>v?^`{^>+UKsz7t>`yq=A}<%dW`fNwcm}zyFE|Yz$)@ zfPS0ED%4ead#fVET@6uX_CzLv(3f*mys-WgMZp}>+k ziqL^e09`)SIP>SHrrhmeat23fg56}O)c$THTBU3hLc7-qGCCf{y6NfZO97L9w7j9v zRN^SXpzQV1va3xb1Yh>cxzJt%?QI_*DD$Wq)7g`YZ3Qy~9ZFk?vJwo!c|ixa0+^aM zo_Hz(u21n#+fTZb;0QtEkxB}LbD^PRt7SJpP=C5sv|}rOgt=k$r2U}{Eo>DwN-b3> zo`+&(nITZ77jkw`^9S{3uypgA%UJZOefKEz?XgXVGTrHlqF6Gue%@ft z4yvncdeqZ6*MXM~e>|!+b!%aQwK10WLe+wKB)ZVP_=PCYisMC zu_~{2Y3i3tu6LD{!^$fvdWM5lmU>H#pw-lf;bw!5jVhluNnyL^#_T@JTa6xv1k;=` zT1+twoHTeb35Gu&21K8fy9ndlm~leyOuI;0>RPFij*iZH8-HgC{Sjz9!V#Fpp%ry5 zBRzqSsQ;kRYi>LnUL-YjKaHwxd2D!?qZCtRNF#HV5wE)K#MQo@e_&JE^Qz9lUV89R zqZ0Ooxz|T`Q->3M_qK{GUx8kuyM3i2vwj{l&wi#kCKy3-w6!@8e!ueP^Z%@C2(J2i z2Q=l6C2bwt^Q$$K-Ea|yW=Dvq&@CzGV7hlx zFr~Pt=sgbT-pi-66rjBgfD4eI8`ue|(FQz$O7|JmJE|EQCj+8G=V3Z*7trXi$IU=_ z>*>dUZbQ%2`*~2F%U)8Ro}T?X)UC%F@YK(C9)gdD$adP^TKS^?dpP+gPb!8ruFqM@f}o7-r_3k8^`Z#=GsM!YPACUJ%^)*1aJ zgf{fxuo;o~?~N_*X-9C^QcTXlTWJW;2VN#x~4{787~=>@Wu zEEg72t0f1j@>?ja1$VwvVl|kIQOZW&zV||)jPvHw z6L{HZ6mTx2bZ;4BRBOb>W{Q*&ga;%fgv5ENp)U|KVA0}+F_`wokAUUSuL^y051}a+ zey*H@IaGKMx5C_bsC%2kSt0Hr0m4S=cIpZ$ z)JYZ^SD~}|-8fzuV*stTU;??r8pAifP#52HNd!jUdtrK@K)b5JmeR{R*to?4^h|e+ z*BXe8Fch|$g>x`qnQmik4cbFN%{dstxIHRi3kR>SZz-d6r^nr_5~ug25KSMCKKtkb ze#mz)B^LoeEOCvY^a^z1t~RwL;9|%#UkD_*Hf^TZa_|^jYxlt49V*(m8$cF4ze>ng zN0kmwgF9mupRN=yzc5}S?J(ENzFIv)xS;|5eHkw4Za{aW8om%(RL_$IP>uM2FLQcP z9GoYpW-PPGPVax~X&@WVsuXu0gEuOTAFtV>oX6*E6c1v>YBsitQ>8jdo=%>)%iOy% z{8r;PqsDF{OsdqYhV_j3Qp`9^n@U1CNYZST(HFZFzb}z0B`eF>u%|{$u-dD>82GZY zrYjQGx^d8G_xKz7U=aPbnbl|}7g$0%&N%K1fuAZWFm$(RAwxrIYMpv)BX$e=NWn_A zt*0k=%EN}XY45^rki;Wpl5KSd(Q(o~THjPAS*T)d^)z+aS2Ywu||d>q6^1$Q@*8*0^hX5QyXBq`wOQ zhK}T5yz^GNlk{+Sw|sOFvuF=gUXBlxX@LE?&y3#K9O&GM+gOP4<+iglTEB zhQCpBO-nz2es~oS0Nz8-oT~_(C5mSQPa04OhN^5_M^c$2ssP(8EWV|`mfeS&s=KOt z9Zrz)dxx54Rx`@WAA^N95TKUc-&qBV_`n)`vPaQDaqJD43E3~oyXS_W_jnE3@}mzy zM63y(o1<9#qr`o$EK7Fld~V~+zn68n2>wR!R->b%06lYfyt3D!qTbWl4|vjsoLyaG zdY)=pWw5gZDH62B!`U>7WIB*c8PE>plCDN}p#yIYA=2>%**L8Hq|@7qxmXM&-#XyAO6`xJ35;9N-re z!hoR`O?MuzMky?Iz7L{5@UEcYvki5bsU%~!h90s=P$KxYrzFS3paEMj7`Lbs!dx+G zCkcySnLG!`07kSmlh|?v&il--^$b3hS%NPA*@f zVPLO~OGxQpU$zM=mAQYi$lmVPm$G=0_sWto70grOT;(%(oa$tB5Xs&}U?*C7Qy0zH|DdY`c zwznuuADKxo7d2 zq1BbCR%T?)c-|4GN0Fl9;#o5SvF$gk?R?nF=&BD%312e)*bSv7AzS&Dfxi zUshf|0c(gS5O?!Ft{?p}svi72ezQ^9x5;;ph&3M?FxX#7mq0o0`(raR<>5I7(%zdq zS$%W2lS;Hd_G`#yLnY^=^zJ-noBUu^kr#j`7IME?x~c@FW~VtP6-=-{Zs(i5?@|x-nG_nupcgW za~GhQ+FG57?woOXhT7eBVUYHTV=jZ}SnsUfP0EbSOnu`~@k-Gd_(LG;C9=u+#f$i5 zoLeW_AnQwFt5JSa5Eb{_*sngT^$w{CLc6U1ji{s~3jh8i8#uMkxSt!I84-oejA(-c znNzj3w~zbRYDPGXF8;79<%hkSUst85*St02U2rd}PaZ^MPO-Yu&QY8rb^a87x=c^_ z`yvi!TBd6k?^H8BPUGb4{BGi@_V|ZVk3y!eT;5I&4i5SA{)nIRe6j70K0bS6>7EGh zK;S6tzE4a{^tT625% of the screen does not bring relevant performance boost +* Frame buffer size <10% will have severe negative effect on performance + +*Note:* The measurements are valid for frame buffer in internal SRAM. Placing the frame buffer into external PSRAM will yield worse results. + +### Compiler optimization level + +Recommended level is "Performance" for good results. The "Performance" setting causes the compiled code to be larger and faster. + +* `CONFIG_COMPILER_OPTIMIZATION_PERF=y` + +### CPU frequency + +The CPU frequency has a big impact on LCD performance. The recommended value is maximum -> 240 MHz. + +* `CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y` + +### Flash frequency and mode + +The flash clock frequency and mode (bus width) has a big impact on LCD performance. Your choices will be limited by your hardware flash configuration. The higher the frequency and bus width, the better. + +* `CONFIG_ESPTOOLPY_FLASHFREQ_120M=y` +* `CONFIG_ESPTOOLPY_FLASHMODE_QIO=y` or `CONFIG_ESPTOOLPY_OCT_FLASH` on supported chips + +More information about SPI Flash configuration can be found in [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/flash_psram_config.html). + +### Fast LVGL memory + +This option puts the most frequently used LVGL functions into IRAM for execution speed up. + +* `CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y` + +### Affinity main task to second core + +The main LVGL task can be processed on the second core of the CPU. It can increase performance. (It is available only on dual-core chips) + +* `CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1=y` + +### Using esp-idf `memcpy` and `memset` instead LVGL's configuration + +Native esp-idf implementation are a little (~1-3 FPS) faster (only for LVGL8). + +* `CONFIG_LV_MEMCPY_MEMSET_STD=y` + +### Default LVGL display refresh period + +This setting can improve subjective performance during screen transitions (scrolling, etc.). + +LVGL8 +* `CONFIG_LV_DISP_DEF_REFR_PERIOD=10` + +LVGL9 +* `CONFIG_LV_DEF_REFR_PERIOD=10` + +## Example FPS improvement vs graphical settings + +The LVGL9 benchmark demo uses a different algorithm for measuring FPS. In this case, we used the same algorithm for measurement in LVGL8 for comparison. + +### RGB LCD, PSRAM (octal) with GDMA - ESP32-S3-LCD-EV-BOARD + + + +Default settings: +* BSP example `display_lvgl_demos` +* LCD: 4.3" 800x480 +* Interface: RGB +* LVGL buffer size: 800 x 480 +* LVGL buffer mode: Direct (avoid-tearing) +* LVGL double buffer: NO +* Optimization: Debug +* CPU frequency: 160 MHz +* Flash frequency: 80 MHz +* PSRAM frequency: 80 MHz +* Flash mode: DIO +* LVGL display refresh period: 30 ms + +| Average FPS (LVGL8) | Average FPS (LVGL 9.2) | Changed settings | +| :---: | :---: | ---------------- | +| 12 | 9 | Default | +| 13 | 9 | + Optimization: Performance (`CONFIG_COMPILER_OPTIMIZATION_PERF=y`) | +| 14 | 11 | + CPU frequency: 240 MHz (`CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y`) | +| 14 | 11 | + Flash mode: QIO (`CONFIG_ESPTOOLPY_FLASHMODE_QIO=y`) | +| 15 | 11 | + LVGL fast memory enabled (`CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y`) | +| 16 | 11 | + (`CONFIG_LV_DISP_DEF_REFR_PERIOD=10` / `CONFIG_LV_DEF_REFR_PERIOD=10`) | + +### Parallel 8080 LCD (only for LVGL8) + + + +Default settings: +* BSP example `display_lvgl_demos` with `ws_7inch` component +* LCD: 7" 800x480 +* Intarface: 16bit parallel Intel 8080 +* LCD IO clock: 20 MHz +* LVGL buffer size: 800 x 50 +* LVGL double buffer: YES +* Optimization: Debug +* CPU frequency: 160 MHz +* Flash frequency: 80 MHz +* Flash mode: DIO +* LVGL display refresh period: 30 ms + +#### Internal RAM with DMA + +| Average FPS | Weighted FPS | Changed settings | +| :---: | :---: | ---------------- | +| 17 | 32 | Default | +| 18 | 36 | + Optimization: Performance (`CONFIG_COMPILER_OPTIMIZATION_PERF=y`) | +| 21 | 46 | + CPU frequency: 240 MHz (`CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y`) | +| 26 | 56 | + Flash frequency: 120 MHz, Flash mode: QIO (`CONFIG_ESPTOOLPY_FLASHFREQ_120M=y` + `CONFIG_ESPTOOLPY_FLASHMODE_QIO=y`) | +| 28 | **60** | + LVGL fast memory enabled (`CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y`) | +| 41 | 55 | + (`CONFIG_LV_DISP_DEF_REFR_PERIOD=10`) | + +#### PSRAM (QUAD) without DMA + +Default changes: +* LCD IO clock: 2 MHz +* PSRAM frequency: 80 MHz + +| Average FPS | Weighted FPS | Changed settings | +| :---: | :---: | ---------------- | +| 11 | 7 | Default | +| 11 | 7 | + Optimization: Performance (`CONFIG_COMPILER_OPTIMIZATION_PERF=y`) | +| 12 | 8 | + CPU frequency: 240 MHz (`CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y`) | +| 12 | 9 | + Flash frequency: 120 MHz, PSRAM frequency: 120 MHz, Flash mode: QIO (`CONFIG_ESPTOOLPY_FLASHFREQ_120M=y` + `CONFIG_SPIRAM_SPEED_120M=y` + `CONFIG_ESPTOOLPY_FLASHMODE_QIO=y`) | +| 12 | 9 | + LVGL fast memory enabled (`CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y`) | +| 12 | 8 | + LVGL buffer size: 800 x 480 | +| 26 | 8 | + (`CONFIG_LV_DISP_DEF_REFR_PERIOD=10`) | +| 31 | 23 | + LCD clock: 10 MHz [^1] | + +[^1]: This is not working in default and sometimes in fast changes on screen is not working properly. + +## Conclusion + +The graphical performance depends on a lot of things and settings, many of which affect the whole system (Compiler, Flash, CPU, PSRAM configuration...). The user should primarily focus on trade-off between frame-buffer(s) size and RAM consumption of the buffer, before optimizing the design further. + +Other configuration options not covered in this document are: +* Hardware interfaces, color depth, screen definition (size), clocks, LCD controller and more. +* Complexity of the graphical application (number of LVGL objects and their styles). diff --git a/components/espressif__esp_lvgl_port/examples/i2c_oled/CMakeLists.txt b/components/espressif__esp_lvgl_port/examples/i2c_oled/CMakeLists.txt new file mode 100644 index 00000000..50138627 --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/i2c_oled/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(i2c_oled) diff --git a/components/espressif__esp_lvgl_port/examples/i2c_oled/README.md b/components/espressif__esp_lvgl_port/examples/i2c_oled/README.md new file mode 100644 index 00000000..fe7c09c0 --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/i2c_oled/README.md @@ -0,0 +1,73 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | + +# I2C OLED example + +[esp_lcd](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html) supports I2C interfaced OLED LCD, whose color depth is usually 1bpp. + +This example shows how to make use of the SSD1306 panel driver from `esp_lcd` component to facilitate the porting of LVGL library. In the end, example will display a scrolling text on the OLED screen. + +## LVGL Version + +This example is using the **LVGL8** version. For use it with LVGL9 version, please delete file [sdkconfig.defaults](sdkconfig.defaults) and change version to `"^9"` on this line in [idf_component.yml](main/idf_component.yml) file: +``` +lvgl/lvgl: "^8" +``` + +NOTE: When you are changing LVGL versions, please remove these files and folders before new build: +- build/ +- managed_components/ +- dependencies.lock +- sdkconfig + +## How to use the example + +### Hardware Required + +* An ESP development board +* An SSD1306 OLED LCD, with I2C interface +* An USB cable for power supply and programming + +### Hardware Connection + +The connection between ESP Board and the LCD is as follows: + +``` + ESP Board OLED LCD (I2C) ++------------------+ +-------------------+ +| GND+--------------+GND | +| | | | +| 3V3+--------------+VCC | +| | | | +| SDA+--------------+SDA | +| | | | +| SCL+--------------+SCL | ++------------------+ +-------------------+ +``` + +The GPIO number used by this example can be changed in [lvgl_example_main.c](main/i2c_oled_example_main.c). Please pay attention to the I2C hardware device address as well, you should refer to your module's spec and schematic to determine that address. + +### Build and Flash + +Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project. A scrolling text will show up on the LCD as expected. + +The first time you run `idf.py` for the example will cost extra time as the build system needs to address the component dependencies and downloads the missing components from registry into `managed_components` folder. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +### Example Output + +```bash +... +I (0) cpu_start: Starting scheduler on APP CPU. +I (345) example: Initialize I2C bus +I (345) example: Install panel IO +I (345) example: Install SSD1306 panel driver +I (455) example: Initialize LVGL library +I (455) example: Register display driver to LVGL +I (455) example: Install LVGL tick timer +I (455) example: Display LVGL Scroll Text +... +``` diff --git a/components/espressif__esp_lvgl_port/examples/i2c_oled/main/CMakeLists.txt b/components/espressif__esp_lvgl_port/examples/i2c_oled/main/CMakeLists.txt new file mode 100644 index 00000000..9d884ac1 --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/i2c_oled/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "i2c_oled_example_main.c" "lvgl_demo_ui.c" + INCLUDE_DIRS "." + REQUIRES driver +) diff --git a/components/espressif__esp_lvgl_port/examples/i2c_oled/main/Kconfig.projbuild b/components/espressif__esp_lvgl_port/examples/i2c_oled/main/Kconfig.projbuild new file mode 100644 index 00000000..f868a521 --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/i2c_oled/main/Kconfig.projbuild @@ -0,0 +1,35 @@ +menu "Example Configuration" + + choice EXAMPLE_LCD_CONTROLLER + prompt "LCD controller model" + default EXAMPLE_LCD_CONTROLLER_SSD1306 + help + Select LCD controller model + + config EXAMPLE_LCD_CONTROLLER_SSD1306 + bool "SSD1306" + + config EXAMPLE_LCD_CONTROLLER_SH1107 + bool "SH1107" + endchoice + + if EXAMPLE_LCD_CONTROLLER_SSD1306 + choice EXAMPLE_SSD1306_HEIGHT + prompt "SSD1306 Height in pixels" + default EXAMPLE_SSD1306_HEIGHT_64 + help + Height of the display in pixels. a.k.a vertical resolution + + config EXAMPLE_SSD1306_HEIGHT_64 + bool "64" + config EXAMPLE_SSD1306_HEIGHT_32 + bool "32" + endchoice + + config EXAMPLE_SSD1306_HEIGHT + int + default 64 if EXAMPLE_SSD1306_HEIGHT_64 + default 32 if EXAMPLE_SSD1306_HEIGHT_32 + endif + +endmenu diff --git a/components/espressif__esp_lvgl_port/examples/i2c_oled/main/i2c_oled_example_main.c b/components/espressif__esp_lvgl_port/examples/i2c_oled/main/i2c_oled_example_main.c new file mode 100644 index 00000000..1cb9904b --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/i2c_oled/main/i2c_oled_example_main.c @@ -0,0 +1,155 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_timer.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "driver/i2c_master.h" +#include "esp_err.h" +#include "esp_log.h" +#include "lvgl.h" +#include "esp_lvgl_port.h" + +#if CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107 +#include "esp_lcd_sh1107.h" +#else +#include "esp_lcd_panel_vendor.h" +#endif + +static const char *TAG = "example"; + +#define I2C_HOST 0 + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define EXAMPLE_LCD_PIXEL_CLOCK_HZ (400 * 1000) +#define EXAMPLE_PIN_NUM_SDA 18 +#define EXAMPLE_PIN_NUM_SCL 23 +#define EXAMPLE_PIN_NUM_RST -1 +#define EXAMPLE_I2C_HW_ADDR 0x3C + +// The pixel number in horizontal and vertical +#if CONFIG_EXAMPLE_LCD_CONTROLLER_SSD1306 +#define EXAMPLE_LCD_H_RES 128 +#define EXAMPLE_LCD_V_RES CONFIG_EXAMPLE_SSD1306_HEIGHT +#elif CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107 +#define EXAMPLE_LCD_H_RES 64 +#define EXAMPLE_LCD_V_RES 128 +#endif +// Bit number used to represent command and parameter +#define EXAMPLE_LCD_CMD_BITS 8 +#define EXAMPLE_LCD_PARAM_BITS 8 + +extern void example_lvgl_demo_ui(lv_disp_t *disp); + +void app_main(void) +{ + ESP_LOGI(TAG, "Initialize I2C bus"); + i2c_master_bus_handle_t i2c_bus = NULL; + i2c_master_bus_config_t bus_config = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .i2c_port = I2C_HOST, + .sda_io_num = EXAMPLE_PIN_NUM_SDA, + .scl_io_num = EXAMPLE_PIN_NUM_SCL, + .flags.enable_internal_pullup = true, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &i2c_bus)); + + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = EXAMPLE_I2C_HW_ADDR, + .scl_speed_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ, + .control_phase_bytes = 1, // According to SSD1306 datasheet + .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS, // According to SSD1306 datasheet + .lcd_param_bits = EXAMPLE_LCD_PARAM_BITS, // According to SSD1306 datasheet +#if CONFIG_EXAMPLE_LCD_CONTROLLER_SSD1306 + .dc_bit_offset = 6, // According to SSD1306 datasheet +#elif CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107 + .dc_bit_offset = 0, // According to SH1107 datasheet + .flags = + { + .disable_control_phase = 1, + } +#endif + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus, &io_config, &io_handle)); + + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_panel_dev_config_t panel_config = { + .bits_per_pixel = 1, + .reset_gpio_num = EXAMPLE_PIN_NUM_RST, +#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5,0,0)) + .color_space = ESP_LCD_COLOR_SPACE_MONOCHROME, +#endif + }; +#if CONFIG_EXAMPLE_LCD_CONTROLLER_SSD1306 +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,3,0)) + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = EXAMPLE_LCD_V_RES, + }; + panel_config.vendor_config = &ssd1306_config; +#endif + ESP_LOGI(TAG, "Install SSD1306 panel driver"); + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(io_handle, &panel_config, &panel_handle)); +#elif CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107 + ESP_LOGI(TAG, "Install SH1107 panel driver"); + ESP_ERROR_CHECK(esp_lcd_new_panel_sh1107(io_handle, &panel_config, &panel_handle)); +#endif + + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); + +#if CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107 + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); +#endif + + ESP_LOGI(TAG, "Initialize LVGL"); + const lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + lvgl_port_init(&lvgl_cfg); + + const lvgl_port_display_cfg_t disp_cfg = { + .io_handle = io_handle, + .panel_handle = panel_handle, + .buffer_size = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES, + .double_buffer = true, + .hres = EXAMPLE_LCD_H_RES, + .vres = EXAMPLE_LCD_V_RES, + .monochrome = true, +#if LVGL_VERSION_MAJOR >= 9 + .color_format = LV_COLOR_FORMAT_RGB565, +#endif + .rotation = { + .swap_xy = false, + .mirror_x = false, + .mirror_y = false, + }, + .flags = { +#if LVGL_VERSION_MAJOR >= 9 + .swap_bytes = false, +#endif + .sw_rotate = false, + } + }; + lv_disp_t *disp = lvgl_port_add_disp(&disp_cfg); + + ESP_LOGI(TAG, "Display LVGL Scroll Text"); + // Lock the mutex due to the LVGL APIs are not thread-safe + if (lvgl_port_lock(0)) { + /* Rotation of the screen */ + lv_disp_set_rotation(disp, LV_DISPLAY_ROTATION_0); + + example_lvgl_demo_ui(disp); + // Release the mutex + lvgl_port_unlock(); + } +} diff --git a/components/espressif__esp_lvgl_port/examples/i2c_oled/main/idf_component.yml b/components/espressif__esp_lvgl_port/examples/i2c_oled/main/idf_component.yml new file mode 100644 index 00000000..5c24ccc6 --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/i2c_oled/main/idf_component.yml @@ -0,0 +1,6 @@ +dependencies: + esp_lcd_sh1107: ^1 + esp_lvgl_port: + version: '*' + idf: '>=4.4' + lvgl/lvgl: ^8 diff --git a/components/espressif__esp_lvgl_port/examples/i2c_oled/main/lvgl_demo_ui.c b/components/espressif__esp_lvgl_port/examples/i2c_oled/main/lvgl_demo_ui.c new file mode 100644 index 00000000..df53d272 --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/i2c_oled/main/lvgl_demo_ui.c @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include "lvgl.h" + +void example_lvgl_demo_ui(lv_disp_t *disp) +{ + lv_obj_t *scr = lv_disp_get_scr_act(disp); + lv_obj_t *label = lv_label_create(scr); + lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR); /* Circular scroll */ + lv_label_set_text(label, "Hello Espressif, Hello LVGL."); + /* Size of the screen (if you use rotation 90 or 270, please set disp->driver->ver_res) */ +#if LVGL_VERSION_MAJOR >= 9 + lv_obj_set_width(label, lv_display_get_physical_horizontal_resolution(disp)); +#else + lv_obj_set_width(label, disp->driver->hor_res); +#endif + lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 0); +} diff --git a/components/espressif__esp_lvgl_port/examples/i2c_oled/sdkconfig.defaults b/components/espressif__esp_lvgl_port/examples/i2c_oled/sdkconfig.defaults new file mode 100644 index 00000000..d92efacc --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/i2c_oled/sdkconfig.defaults @@ -0,0 +1,5 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_LV_USE_USER_DATA=y +CONFIG_LV_COLOR_DEPTH_1=y diff --git a/components/espressif__esp_lvgl_port/examples/rgb_lcd/CMakeLists.txt b/components/espressif__esp_lvgl_port/examples/rgb_lcd/CMakeLists.txt new file mode 100644 index 00000000..89fc676b --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/rgb_lcd/CMakeLists.txt @@ -0,0 +1,9 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +# "Trim" the build. Include the minimal set of components, main and anything it depends on. +set(COMPONENTS main) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(rgb_lcd) diff --git a/components/espressif__esp_lvgl_port/examples/rgb_lcd/README.md b/components/espressif__esp_lvgl_port/examples/rgb_lcd/README.md new file mode 100644 index 00000000..af5ce968 --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/rgb_lcd/README.md @@ -0,0 +1,19 @@ +# ESP LVGL RGB Screen Example + +Very simple example for demonstration of initialization and usage of the `esp_lvgl_port` component with RGB LCD. This example contains four main parts: + +## 1. LCD HW initialization - `app_lcd_init()` + +Standard HW initialization of the LCD using [`esp_lcd`](https://github.com/espressif/esp-idf/tree/master/components/esp_lcd) component. Settings of this example are fully compatible with [ESP32-S3-LCD-EV-Board-2](https://github.com/espressif/esp-bsp/tree/master/bsp/esp32_s3_lcd_ev_board) board. + +## 2. Touch HW initialization - `app_touch_init()` + +Standard HW initialization of the LCD touch using [`esp_lcd_touch`](https://github.com/espressif/esp-bsp/tree/master/components/lcd_touch/esp_lcd_touch) component. Settings of this example are fully compatible with [ESP32-S3-LCD-EV-Board-2](https://github.com/espressif/esp-bsp/tree/master/bsp/esp32_s3_lcd_ev_board) board. + +## 3. LVGL port initialization - `app_lvgl_init()` + +Initialization of the LVGL port. + +## 4. LVGL objects example usage - `app_main_display()` + +Very simple demonstration code of using LVGL objects after LVGL port initialization. diff --git a/components/espressif__esp_lvgl_port/examples/rgb_lcd/main/CMakeLists.txt b/components/espressif__esp_lvgl_port/examples/rgb_lcd/main/CMakeLists.txt new file mode 100644 index 00000000..1c9b125f --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/rgb_lcd/main/CMakeLists.txt @@ -0,0 +1,11 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS "." ${LV_DEMO_DIR} + REQUIRES driver esp_lcd) + +lvgl_port_create_c_image("images/esp_logo.png" "images/" "ARGB8888" "NONE") +lvgl_port_add_images(${COMPONENT_LIB} "images/") + +set_source_files_properties( + PROPERTIES COMPILE_OPTIONS + "-DLV_LVGL_H_INCLUDE_SIMPLE;-Wno-format;" + ) diff --git a/components/espressif__esp_lvgl_port/examples/rgb_lcd/main/idf_component.yml b/components/espressif__esp_lvgl_port/examples/rgb_lcd/main/idf_component.yml new file mode 100644 index 00000000..7330533f --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/rgb_lcd/main/idf_component.yml @@ -0,0 +1,6 @@ +dependencies: + esp_lcd_touch_gt1151: + version: ^1 + esp_lvgl_port: + version: '*' + idf: '>=5.0' diff --git a/components/espressif__esp_lvgl_port/examples/rgb_lcd/main/images/.gitignore b/components/espressif__esp_lvgl_port/examples/rgb_lcd/main/images/.gitignore new file mode 100644 index 00000000..09b2ac1d --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/rgb_lcd/main/images/.gitignore @@ -0,0 +1 @@ +*.c \ No newline at end of file diff --git a/components/espressif__esp_lvgl_port/examples/rgb_lcd/main/images/esp_logo.png b/components/espressif__esp_lvgl_port/examples/rgb_lcd/main/images/esp_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..510b6a5299bea6eed35247037b09a0d84cdb8aa3 GIT binary patch literal 6639 zcmVQ`NQp)=p+JFd*-loB#sK`y?Zf$1sX^q*@=<)~liw)M~BITdP%jYxPpK zkE_3mg4$ZGz1ChIs8m{fR4Y|cc}+4AB?*X3feIuUP*8+nW|Hi6|40UtlatBGNe1fo z53u&$Yp<}**=O(d+Jvtt7@&5K^ED<+0Z<3PmTBq&&;ju00CfVq8$_FlX*08I1MxCU=}ft>H|@W$>E<8<6RI|5b=t7f8>LT3ReQYvI>a%)ccv~M-^d4 z_!pR;O_BBSQT`7;st8ZRQ-4Jfb*r-=a4mt(1yEB#)@un^R6lmgpJ~aGR0a9!;w!6w z9&DX9ZE#Ih@*)DRVqjVYS^tv>3%dI6e`4HpYXiX;mW5l{m(PI6h?uo-mR6hjZJEcwq!g7-QD!MH%+Sji?V02(~rgI9Va2J5{ zQJFU)3Bp2q%62;igNX|8GbdEcJ5>e5I{=0P*djou)(1jc3iXeB8@|qvPXnkoDQ6ml z{Q&;Lgr}6Fr@UeGBe~Nvb-EyNKY-@4=Km6Y0lKKS-)s~mD*Vd@_I)|CJzYnpzi1j5&46Sg*wt*Pl4_#~LmFGoNFNC8+1<^@V?#9yp)yrH3% zZK?SH-3-85=G+X>9ZDed3$qLiD}VutdtHA8_**#)7=NuFH|6~7)Tn6AIvx1q# z+eBDU?+?FH?8DveQ;%b&Cn~aaB|L8LKm2N1u_9UeP=knhoIeKP*P7vP?%#Xx%n5C6 zT{(Td&U*peQmQZ GOG%gENqs@^(n9zWhzJH&P;fqsngm&h0P@9mtY{l6#%VWd;M zv0)|%ztWI5SUTjl^(G|ZO0V~&Vx}Nk8S^?{+BHKRU#QcBwze)uAo5cN%?7CGPOb>f z8#uV;S)Mbeq~Bgt0sori8p1NQ7(i7PxdV_F)I}ouOO^64Ps!+Sv zbxNU5%DQ#C?Sb%_1o|b)Iq?#%iFurN813Gx0-}vBH-o;fAwOqt4L@#5xFZS17zuto z?o}Tx^fnM6?SasIqz{V+P0vNRGwx9@GSao60vHe^`H|c{6s;DWSD6;<$b%SkRWeZ( ziZ!Yy7wV)0!q1ZsvjMc5pNSrh`x?d->sC+!F`x4)0G=G4EN>px(zdS{Qz^wJfZhZU zWMFgV16Oq1VFP0fBrT77UAJ<9ouIvC{aPV$0)VaNrodok`D{d0t9$FGJNYhE9gylz z+W-v9>iVV<2z}j*Fg=JjG}NZ5lM^kVNi1kG7?TK?%zz7(KPelSePr)Jk|fQr2SXuKLKML+RUN?n#Y_o# z3P7g-m=1$mQHNvUIpMY!8|F5J%!w!c?#G(e!OkqkeYuoFfyy# zZlR-fSq3FXhAfWnXe#VLLX1p1?G#D*2tgr z8e`L^kANldwwdLKflC~L@KahkX*!9R*ZE=24`uwH(i;A5sS{Qj3{cFceuEgd19V~^ zPL%}cN+l3_La#CXK;Yei4y6JhVCNTmJCD^`4ssOG?s1+f39o07m{>H}Ti1t;P84-F z%z#Duh;b|kL%|pbfC>B=191jzmf-M6Srakq)QI{TP9&DM131mhcmrT2y2arS-LE^p z+clX4uLdxrB&MLN9f8pAvuQaBh%Mi`w86HjbMF)WAAb5e&64_9!b0%0|F@U0GfD&+!Nu1{JuYXtDz;1U{OmwXv;CQ`(da#Jp z#JZM^n}N(>hu!Wf5OQoI63E*!0YKENo=y_WaCrXn0iZvC%ONQg^E#i8x?Ng#Xb$Yb z4K3B8a~goMe61b;WRPsX>bBP3IM*A-OMakXon;d zRrA3p=-xX57|6h)__mGyXrs$rsFTAVZiU6#l(zR&0{q>WSG`WJrCM~}2;gHwT#$a~ z1<#pd0pSEq%6&;X<1qO{x+(zoCqLTnJH;u)ZLJ0 z6B^diwvQmb2O}Oe$|zvhwhzZ>*MCRr2F)Hkbv;Tua2#$_0ay_CI$zx0qz={VU|+p{ zm!*T7QKsKFgK84;$*s*}^S*`l!1_19c+vomW(I_ij7d3Uq7Sp9lOw8H4W`S>m-(>Z zENhb1?et95J)6`;BK!Ar@V6LPUV*u$_B$}}cD)7*@lR0P0W~bq6{pUELfue z=3E7&og`l|Fa{84S={HEtJgfNrEQ;8Ccg=w+~8U(F&@`*J1VK_hXC|?a3siy0!$Nf ziKN{vG?SBWXl%W6YA`(*TrY0MvnNXn)ExMNlx9 zAg0@T8_1F-3gq~G0TLp28%1Q!2}0D8u=24=AabVC8lJ5L!j5XuIf$g33E;;7y#~N= ztiMSRh#!o3U6<)K*1Z>L1Ly*z1ANPv1h+@s6NhW*^{t^70Tc`vCc*>(owW~p|(Cqku#|ksF>t z_{@wzGQp>o(tl_$__-1Y|IRLLlK{Fr9ZytI*qG`0*vx5TwG9MFvfxsb_noS;a_Szv z1|r<6KZFJf)>t)<7NFmC|CCQu|LlvtWg;~&aI%i`NM?v8n# zZ^eA-Ls75ms+dRhv4NN$3I-GQK}N^bPZ}sYv@$~x{eovAQree zj|eb>^Zd&sv06ZLV*9vXcg~UoHeHXQ2SWffgXsqZeiTG4aj$bn%@Po`l9{z=HPWDZtvrISL>P zUO}1LG#w}au4AS*;@dWEkNR9U#Tyz5I(P_RB@n((0L=vKtb_ne^k8iI^buP6$Ut~I zz@~G)08|O4ytBu+727%)csfT0=FbE`ISLShb3(W4Tf*%O=thfG2nsRZ$2Pe&?seVT zTOvh$AiP>iJ|24OY3UdaYvLij279VzA&Q)0dj;{;pEi!q+rA4)f5_~T;bmLica@fI_lI5p$aEGE0hj2}nQ$#E)?*ZZ_(Y{Oyh0Eo$M(bEyu$$OB$ zvLpqj`^+*W)?bFQA*=`ZLu3h`$ym`nC=tMW56a5`Ja1Mglc)yM6H$*-_XpYi;g^Yc zQ8^PZ_=kFSeVgUrVwgXTPipt6$Mx1hWO9ACeJKbA?`m{93k^^sk#76gDRT+52#2@w zAQ9oQLd(&o2XHF`&lwtWo#t68S9NCbE0N$0n-i7b0B>q1MGr0jvv zk3cvZzz(y*nSc$F{?ZdulMOWz>9(Y;siq$aCdyleGBDy~*XPO9t5Gr&7esGp=*>o$ zEB^2+ENznrxD6EwelmbTO!Aq{MvnE;Az|vVTqvy-$XpeJ4cJ7m8o>i>cNHIjGPZ>iN;Uz(*F!1#XN7hLO zo$LsN@|+!UuWKQLZ#2nwt341tR+~_lsrvN~lRWQJ0%6_iF^8UVBKmu716^!#vO{pJ z+xRb~HS%Y>KYWei4^LppCjxjGmHBuJ0{=$uO-%~A4?wp`zT;wzuDqzXgSGosA)VDz zItY!0nhpssiE}Jl2isyW^sa+HleMp3uNxCBBDAydfpBq#qt-(-lZ@u ziMrK1A3<-3d7RBruk-Z*Luw9C3L5|sW}EyZ?s5KQhgZ#e6$#*^)p{k&TQ#jLk>>!Z zleRz^{@uKwTL6H>CKJ561f_J1dU*;M*}7pJkh3MNf}lK4e<8#?&O1POi-1#+n=V?z zgmWw)f7Gj9sU0_Z%?CRH=*HC;0aL!sg@VBZm{yu(d8{r~Ydtmq0^Qq_6{&^pX;-8x z-DWYsH9I{Gll1y#Bj$C@2jR{l-Kq%mNZh05W#C&n_z?hmbjB11qe=Jl&b-zXKkzYn zTSo?$^ae1nS<33W3$^Pipxz%^lZj~VLA3=^H)X#EY3~o`#YYyJmx+F>4UkG(X8Ex~ zgNk5CyQg8QmPjU^*bMsZY;4HO#3ZIt95g9rzT6QE7BpuUwD%Cx4ajmqGT|FhkD7Nt zIRq)MKyGQvVi;vj?LSLPWQmtc;E@X<^L8%D5}(*ycATFV%w+sK#dKdWb5218*n^=E zr44IePENG2&~AnarI(ONnT>&r27UI`f?Ys_ay67urRxXPki+Dr#)_8gQ& zSADfTEiLoc7ve0}OFBP-n z(*shsHX8vYmw``2#s9;Ydl^;|Q)VY56pyfDx-UeGzxN&SSwX z=vnIt4WO=N5mo)B8i{n*y%%YVd7TSktZcmm(M@V3Twsl);bvS0#03g-=qx3MAX#_& zm`-b|3=G%ivvX7l|D;SRyVI>LpQD$k=1%vZGriMTToiOG~ z>yYT{B*M~)qdHnck3zdO15y}DLe?*jANWK!X2%HF?XD7_qVbf4AarsDQj$$aV#s7?TPgh+ zpqNL!kAX!<8}jNQ=^wql38o8V*F_@xtySHp>Qo+?Ghj%+zwPi%9;Q367qnZtuF0)g zZ5#}2t_wu+4se2~^jgnIXRkPdAAMr9|MsS)elf556o_up@aabay3QD&%nIcrR&DxX zZwH^1A2H3dEo^;1>Q-kN<;8&DGKxY8&s4FqLD?nZ)kvgUX${RoelqeX67*Kg=e*NUS}GtVa57-d8Y)TV6`0-Qb`JvdeX&L5 zAvScfkNMO~g^;ZvZBY}7Nbt;J1!yZ;7C!2AT}9wU&=}?zSZ3P@eOrI$om67fR2KXOxV zeE?wh^hu7SwfeNo?(Gz$FC$F?c!LN_x~ggEn3bVI20{Sor84eQFU}-xXl+kP5PoI% zhktJLPUJqh7-5&H4oKC~!_dzV*+SqO?SaTkMtG&Gv2!yq&jc_NfHny_MI42&D0@3P#$jC=MTrvRQ8z*BXB@S483zugGh8=Vc3aWNPdq42%KF%aKs_eY*ASI)zx zfF88_TuoB)?I4`l%lumycuo-iBVjpeO`#I{l>j$4TX!A!#FqlkrgZtTymB*H*a6@H zO9wwvse3dJy8?Q!!>dlSfNleDE_!$3bOBfk!U{<8TcrGG#8D%*nYp`{0TG)%Wwga& znJ5L#0HYbivowWk^$-T}f;|VYxYEny5mP`9cDtu2Nnx4Gz$Ik-(A14| z*w2E#VDRSz8U&WLL>K@@Etu*UI3|+=ZM5?HJ`k2M;VGphyu7cL#3QbNY{cCz7hCue zFr5kTR2+W!Ism-Hgy*{Y?|*gNif!hiDN4Y)NS-sr676rT*j$qY0D)E8m$p8{A7 z#=C;B>XQy`J={C7e_I7;ap)dO5)&CTi5ZiLP!I4R29C-UAFM_C@1Jpc$@mQEKlc)2 tJA=0XVk>|xDIwZM`ajrwq;^|9_= ESP_IDF_VERSION_VAL(6,0,0) + .in_color_format = LCD_COLOR_FMT_RGB565, +#else + .bits_per_pixel = 16, +#endif + .de_gpio_num = EXAMPLE_LCD_GPIO_DE, + .pclk_gpio_num = EXAMPLE_LCD_GPIO_PCLK, + .vsync_gpio_num = EXAMPLE_LCD_GPIO_VSYNC, + .hsync_gpio_num = EXAMPLE_LCD_GPIO_HSYNC, + .disp_gpio_num = EXAMPLE_LCD_GPIO_DISP, + .data_gpio_nums = { + EXAMPLE_LCD_GPIO_DATA0, + EXAMPLE_LCD_GPIO_DATA1, + EXAMPLE_LCD_GPIO_DATA2, + EXAMPLE_LCD_GPIO_DATA3, + EXAMPLE_LCD_GPIO_DATA4, + EXAMPLE_LCD_GPIO_DATA5, + EXAMPLE_LCD_GPIO_DATA6, + EXAMPLE_LCD_GPIO_DATA7, + EXAMPLE_LCD_GPIO_DATA8, + EXAMPLE_LCD_GPIO_DATA9, + EXAMPLE_LCD_GPIO_DATA10, + EXAMPLE_LCD_GPIO_DATA11, + EXAMPLE_LCD_GPIO_DATA12, + EXAMPLE_LCD_GPIO_DATA13, + EXAMPLE_LCD_GPIO_DATA14, + EXAMPLE_LCD_GPIO_DATA15, + }, + .timings = EXAMPLE_LCD_PANEL_35HZ_RGB_TIMING(), + .flags.fb_in_psram = 1, + .num_fbs = EXAMPLE_LCD_RGB_BUFFER_NUMS, +#if EXAMPLE_LCD_RGB_BOUNCE_BUFFER_MODE + .bounce_buffer_size_px = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_RGB_BOUNCE_BUFFER_HEIGHT, +#endif + }; + ESP_GOTO_ON_ERROR(esp_lcd_new_rgb_panel(&panel_conf, &lcd_panel), err, TAG, "RGB init failed"); + ESP_GOTO_ON_ERROR(esp_lcd_panel_init(lcd_panel), err, TAG, "LCD init failed"); + + return ret; + +err: + if (lcd_panel) { + esp_lcd_panel_del(lcd_panel); + } + return ret; +} + +static esp_err_t app_touch_init(void) +{ + /* Initilize I2C */ + i2c_master_bus_handle_t i2c_handle = NULL; + const i2c_master_bus_config_t i2c_config = { + .i2c_port = EXAMPLE_TOUCH_I2C_NUM, + .sda_io_num = EXAMPLE_TOUCH_I2C_SDA, + .scl_io_num = EXAMPLE_TOUCH_I2C_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + }; + ESP_RETURN_ON_ERROR(i2c_new_master_bus(&i2c_config, &i2c_handle), TAG, ""); + + /* Initialize touch HW */ + const esp_lcd_touch_config_t tp_cfg = { + .x_max = EXAMPLE_LCD_H_RES, + .y_max = EXAMPLE_LCD_V_RES, + .rst_gpio_num = GPIO_NUM_NC, + .int_gpio_num = GPIO_NUM_NC, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 0, + .mirror_x = 0, + .mirror_y = 0, + }, + }; + esp_lcd_panel_io_handle_t tp_io_handle = NULL; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT1151_CONFIG(); + tp_io_config.scl_speed_hz = EXAMPLE_TOUCH_I2C_CLK_HZ; + ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_i2c(i2c_handle, &tp_io_config, &tp_io_handle), TAG, ""); + return esp_lcd_touch_new_i2c_gt1151(tp_io_handle, &tp_cfg, &touch_handle); +} + +static esp_err_t app_lvgl_init(void) +{ + /* Initialize LVGL */ + const lvgl_port_cfg_t lvgl_cfg = { + .task_priority = 4, /* LVGL task priority */ + .task_stack = 6144, /* LVGL task stack size */ + .task_affinity = -1, /* LVGL task pinned to core (-1 is no affinity) */ + .task_max_sleep_ms = 500, /* Maximum sleep in LVGL task */ + .timer_period_ms = 5 /* LVGL timer tick period in ms */ + }; + ESP_RETURN_ON_ERROR(lvgl_port_init(&lvgl_cfg), TAG, "LVGL port initialization failed"); + + uint32_t buff_size = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_DRAW_BUFF_HEIGHT; +#if EXAMPLE_LCD_LVGL_FULL_REFRESH || EXAMPLE_LCD_LVGL_DIRECT_MODE + buff_size = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES; +#endif + + /* Add LCD screen */ + ESP_LOGD(TAG, "Add LCD screen"); + const lvgl_port_display_cfg_t disp_cfg = { + .panel_handle = lcd_panel, + .buffer_size = buff_size, + .double_buffer = EXAMPLE_LCD_DRAW_BUFF_DOUBLE, + .hres = EXAMPLE_LCD_H_RES, + .vres = EXAMPLE_LCD_V_RES, + .monochrome = false, +#if LVGL_VERSION_MAJOR >= 9 + .color_format = LV_COLOR_FORMAT_RGB565, +#endif + .rotation = { + .swap_xy = false, + .mirror_x = false, + .mirror_y = false, + }, + .flags = { + .buff_dma = false, + .buff_spiram = false, +#if EXAMPLE_LCD_LVGL_FULL_REFRESH + .full_refresh = true, +#elif EXAMPLE_LCD_LVGL_DIRECT_MODE + .direct_mode = true, +#endif +#if LVGL_VERSION_MAJOR >= 9 + .swap_bytes = false, +#endif + } + }; + const lvgl_port_display_rgb_cfg_t rgb_cfg = { + .flags = { +#if EXAMPLE_LCD_RGB_BOUNCE_BUFFER_MODE + .bb_mode = true, +#else + .bb_mode = false, +#endif +#if EXAMPLE_LCD_LVGL_AVOID_TEAR + .avoid_tearing = true, +#else + .avoid_tearing = false, +#endif + } + }; + lvgl_disp = lvgl_port_add_disp_rgb(&disp_cfg, &rgb_cfg); + + /* Add touch input (for selected screen) */ + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lvgl_disp, + .handle = touch_handle, + }; + lvgl_touch_indev = lvgl_port_add_touch(&touch_cfg); + + return ESP_OK; +} + +void app_main(void) +{ + /* LCD HW initialization */ + ESP_ERROR_CHECK(app_lcd_init()); + + /* Touch initialization */ + ESP_ERROR_CHECK(app_touch_init()); + + /* LVGL initialization */ + ESP_ERROR_CHECK(app_lvgl_init()); + + /* Show LVGL objects */ + lvgl_port_lock(0); + lv_demo_music(); + lvgl_port_unlock(); + +} diff --git a/components/espressif__esp_lvgl_port/examples/rgb_lcd/partitions.csv b/components/espressif__esp_lvgl_port/examples/rgb_lcd/partitions.csv new file mode 100644 index 00000000..07eb06e8 --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/rgb_lcd/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 2M, diff --git a/components/espressif__esp_lvgl_port/examples/rgb_lcd/sdkconfig.defaults b/components/espressif__esp_lvgl_port/examples/rgb_lcd/sdkconfig.defaults new file mode 100644 index 00000000..920991b4 --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/rgb_lcd/sdkconfig.defaults @@ -0,0 +1,39 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_COMPILER_OPTIMIZATION_PERF=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y +CONFIG_FREERTOS_HZ=1000 +#CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y +CONFIG_LV_FONT_MONTSERRAT_12=y +CONFIG_LV_FONT_MONTSERRAT_16=y +CONFIG_LV_USE_DEMO_WIDGETS=y +CONFIG_LV_USE_DEMO_BENCHMARK=y +CONFIG_LV_USE_DEMO_STRESS=y +CONFIG_LV_USE_DEMO_MUSIC=y +CONFIG_LV_DEMO_MUSIC_AUTO_PLAY=y + +## LVGL9 ## +CONFIG_LV_CONF_SKIP=y + +#CLIB default +CONFIG_LV_USE_CLIB_MALLOC=y +CONFIG_LV_USE_CLIB_SPRINTF=y +CONFIG_LV_USE_CLIB_STRING=y + +# Performance monitor +CONFIG_LV_USE_OBSERVER=y +CONFIG_LV_USE_SYSMON=y +CONFIG_LV_USE_PERF_MONITOR=y + +## LVGL8 - uncomment, when using LVGL8 ## +CONFIG_LV_MEM_SIZE_KILOBYTES=48 +# CONFIG_LV_MEM_CUSTOM=y +# CONFIG_LV_MEMCPY_MEMSET_STD=y diff --git a/components/espressif__esp_lvgl_port/examples/touchscreen/CMakeLists.txt b/components/espressif__esp_lvgl_port/examples/touchscreen/CMakeLists.txt new file mode 100644 index 00000000..773ae6d4 --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/touchscreen/CMakeLists.txt @@ -0,0 +1,9 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +# "Trim" the build. Include the minimal set of components, main and anything it depends on. +set(COMPONENTS main) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(touchscreen) diff --git a/components/espressif__esp_lvgl_port/examples/touchscreen/README.md b/components/espressif__esp_lvgl_port/examples/touchscreen/README.md new file mode 100644 index 00000000..7cba276e --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/touchscreen/README.md @@ -0,0 +1,19 @@ +# ESP LVGL Touch Screen Example + +Very simple example for demonstration of initialization and usage of the `esp_lvgl_port` component. This example contains four main parts: + +## 1. LCD HW initialization - `app_lcd_init()` + +Standard HW initialization of the LCD using [`esp_lcd`](https://github.com/espressif/esp-idf/tree/master/components/esp_lcd) component. Settings of this example are fully compatible with [ESP-BOX](https://github.com/espressif/esp-bsp/tree/master/esp-box) board. + +## 2. Touch HW initialization - `app_touch_init()` + +Standard HW initialization of the LCD touch using [`esp_lcd_touch`](https://github.com/espressif/esp-bsp/tree/master/components/lcd_touch/esp_lcd_touch) component. Settings of this example are fully compatible with [ESP-BOX](https://github.com/espressif/esp-bsp/tree/master/esp-box) board. + +## 3. LVGL port initialization - `app_lvgl_init()` + +Initialization of the LVGL port. + +## 4. LVGL objects example usage - `app_main_display()` + +Very simple demonstration code of using LVGL objects after LVGL port initialization. diff --git a/components/espressif__esp_lvgl_port/examples/touchscreen/main/CMakeLists.txt b/components/espressif__esp_lvgl_port/examples/touchscreen/main/CMakeLists.txt new file mode 100644 index 00000000..793dc232 --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/touchscreen/main/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS "." + REQUIRES driver esp_lcd) + +lvgl_port_create_c_image("images/esp_logo.png" "images/" "ARGB8888" "NONE") +lvgl_port_add_images(${COMPONENT_LIB} "images/") diff --git a/components/espressif__esp_lvgl_port/examples/touchscreen/main/idf_component.yml b/components/espressif__esp_lvgl_port/examples/touchscreen/main/idf_component.yml new file mode 100644 index 00000000..446425f7 --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/touchscreen/main/idf_component.yml @@ -0,0 +1,6 @@ +dependencies: + esp_lcd_touch_tt21100: + version: ^1 + esp_lvgl_port: + version: '*' + idf: '>=4.4' diff --git a/components/espressif__esp_lvgl_port/examples/touchscreen/main/images/.gitignore b/components/espressif__esp_lvgl_port/examples/touchscreen/main/images/.gitignore new file mode 100644 index 00000000..09b2ac1d --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/touchscreen/main/images/.gitignore @@ -0,0 +1 @@ +*.c \ No newline at end of file diff --git a/components/espressif__esp_lvgl_port/examples/touchscreen/main/images/esp_logo.png b/components/espressif__esp_lvgl_port/examples/touchscreen/main/images/esp_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..510b6a5299bea6eed35247037b09a0d84cdb8aa3 GIT binary patch literal 6639 zcmVQ`NQp)=p+JFd*-loB#sK`y?Zf$1sX^q*@=<)~liw)M~BITdP%jYxPpK zkE_3mg4$ZGz1ChIs8m{fR4Y|cc}+4AB?*X3feIuUP*8+nW|Hi6|40UtlatBGNe1fo z53u&$Yp<}**=O(d+Jvtt7@&5K^ED<+0Z<3PmTBq&&;ju00CfVq8$_FlX*08I1MxCU=}ft>H|@W$>E<8<6RI|5b=t7f8>LT3ReQYvI>a%)ccv~M-^d4 z_!pR;O_BBSQT`7;st8ZRQ-4Jfb*r-=a4mt(1yEB#)@un^R6lmgpJ~aGR0a9!;w!6w z9&DX9ZE#Ih@*)DRVqjVYS^tv>3%dI6e`4HpYXiX;mW5l{m(PI6h?uo-mR6hjZJEcwq!g7-QD!MH%+Sji?V02(~rgI9Va2J5{ zQJFU)3Bp2q%62;igNX|8GbdEcJ5>e5I{=0P*djou)(1jc3iXeB8@|qvPXnkoDQ6ml z{Q&;Lgr}6Fr@UeGBe~Nvb-EyNKY-@4=Km6Y0lKKS-)s~mD*Vd@_I)|CJzYnpzi1j5&46Sg*wt*Pl4_#~LmFGoNFNC8+1<^@V?#9yp)yrH3% zZK?SH-3-85=G+X>9ZDed3$qLiD}VutdtHA8_**#)7=NuFH|6~7)Tn6AIvx1q# z+eBDU?+?FH?8DveQ;%b&Cn~aaB|L8LKm2N1u_9UeP=knhoIeKP*P7vP?%#Xx%n5C6 zT{(Td&U*peQmQZ GOG%gENqs@^(n9zWhzJH&P;fqsngm&h0P@9mtY{l6#%VWd;M zv0)|%ztWI5SUTjl^(G|ZO0V~&Vx}Nk8S^?{+BHKRU#QcBwze)uAo5cN%?7CGPOb>f z8#uV;S)Mbeq~Bgt0sori8p1NQ7(i7PxdV_F)I}ouOO^64Ps!+Sv zbxNU5%DQ#C?Sb%_1o|b)Iq?#%iFurN813Gx0-}vBH-o;fAwOqt4L@#5xFZS17zuto z?o}Tx^fnM6?SasIqz{V+P0vNRGwx9@GSao60vHe^`H|c{6s;DWSD6;<$b%SkRWeZ( ziZ!Yy7wV)0!q1ZsvjMc5pNSrh`x?d->sC+!F`x4)0G=G4EN>px(zdS{Qz^wJfZhZU zWMFgV16Oq1VFP0fBrT77UAJ<9ouIvC{aPV$0)VaNrodok`D{d0t9$FGJNYhE9gylz z+W-v9>iVV<2z}j*Fg=JjG}NZ5lM^kVNi1kG7?TK?%zz7(KPelSePr)Jk|fQr2SXuKLKML+RUN?n#Y_o# z3P7g-m=1$mQHNvUIpMY!8|F5J%!w!c?#G(e!OkqkeYuoFfyy# zZlR-fSq3FXhAfWnXe#VLLX1p1?G#D*2tgr z8e`L^kANldwwdLKflC~L@KahkX*!9R*ZE=24`uwH(i;A5sS{Qj3{cFceuEgd19V~^ zPL%}cN+l3_La#CXK;Yei4y6JhVCNTmJCD^`4ssOG?s1+f39o07m{>H}Ti1t;P84-F z%z#Duh;b|kL%|pbfC>B=191jzmf-M6Srakq)QI{TP9&DM131mhcmrT2y2arS-LE^p z+clX4uLdxrB&MLN9f8pAvuQaBh%Mi`w86HjbMF)WAAb5e&64_9!b0%0|F@U0GfD&+!Nu1{JuYXtDz;1U{OmwXv;CQ`(da#Jp z#JZM^n}N(>hu!Wf5OQoI63E*!0YKENo=y_WaCrXn0iZvC%ONQg^E#i8x?Ng#Xb$Yb z4K3B8a~goMe61b;WRPsX>bBP3IM*A-OMakXon;d zRrA3p=-xX57|6h)__mGyXrs$rsFTAVZiU6#l(zR&0{q>WSG`WJrCM~}2;gHwT#$a~ z1<#pd0pSEq%6&;X<1qO{x+(zoCqLTnJH;u)ZLJ0 z6B^diwvQmb2O}Oe$|zvhwhzZ>*MCRr2F)Hkbv;Tua2#$_0ay_CI$zx0qz={VU|+p{ zm!*T7QKsKFgK84;$*s*}^S*`l!1_19c+vomW(I_ij7d3Uq7Sp9lOw8H4W`S>m-(>Z zENhb1?et95J)6`;BK!Ar@V6LPUV*u$_B$}}cD)7*@lR0P0W~bq6{pUELfue z=3E7&og`l|Fa{84S={HEtJgfNrEQ;8Ccg=w+~8U(F&@`*J1VK_hXC|?a3siy0!$Nf ziKN{vG?SBWXl%W6YA`(*TrY0MvnNXn)ExMNlx9 zAg0@T8_1F-3gq~G0TLp28%1Q!2}0D8u=24=AabVC8lJ5L!j5XuIf$g33E;;7y#~N= ztiMSRh#!o3U6<)K*1Z>L1Ly*z1ANPv1h+@s6NhW*^{t^70Tc`vCc*>(owW~p|(Cqku#|ksF>t z_{@wzGQp>o(tl_$__-1Y|IRLLlK{Fr9ZytI*qG`0*vx5TwG9MFvfxsb_noS;a_Szv z1|r<6KZFJf)>t)<7NFmC|CCQu|LlvtWg;~&aI%i`NM?v8n# zZ^eA-Ls75ms+dRhv4NN$3I-GQK}N^bPZ}sYv@$~x{eovAQree zj|eb>^Zd&sv06ZLV*9vXcg~UoHeHXQ2SWffgXsqZeiTG4aj$bn%@Po`l9{z=HPWDZtvrISL>P zUO}1LG#w}au4AS*;@dWEkNR9U#Tyz5I(P_RB@n((0L=vKtb_ne^k8iI^buP6$Ut~I zz@~G)08|O4ytBu+727%)csfT0=FbE`ISLShb3(W4Tf*%O=thfG2nsRZ$2Pe&?seVT zTOvh$AiP>iJ|24OY3UdaYvLij279VzA&Q)0dj;{;pEi!q+rA4)f5_~T;bmLica@fI_lI5p$aEGE0hj2}nQ$#E)?*ZZ_(Y{Oyh0Eo$M(bEyu$$OB$ zvLpqj`^+*W)?bFQA*=`ZLu3h`$ym`nC=tMW56a5`Ja1Mglc)yM6H$*-_XpYi;g^Yc zQ8^PZ_=kFSeVgUrVwgXTPipt6$Mx1hWO9ACeJKbA?`m{93k^^sk#76gDRT+52#2@w zAQ9oQLd(&o2XHF`&lwtWo#t68S9NCbE0N$0n-i7b0B>q1MGr0jvv zk3cvZzz(y*nSc$F{?ZdulMOWz>9(Y;siq$aCdyleGBDy~*XPO9t5Gr&7esGp=*>o$ zEB^2+ENznrxD6EwelmbTO!Aq{MvnE;Az|vVTqvy-$XpeJ4cJ7m8o>i>cNHIjGPZ>iN;Uz(*F!1#XN7hLO zo$LsN@|+!UuWKQLZ#2nwt341tR+~_lsrvN~lRWQJ0%6_iF^8UVBKmu716^!#vO{pJ z+xRb~HS%Y>KYWei4^LppCjxjGmHBuJ0{=$uO-%~A4?wp`zT;wzuDqzXgSGosA)VDz zItY!0nhpssiE}Jl2isyW^sa+HleMp3uNxCBBDAydfpBq#qt-(-lZ@u ziMrK1A3<-3d7RBruk-Z*Luw9C3L5|sW}EyZ?s5KQhgZ#e6$#*^)p{k&TQ#jLk>>!Z zleRz^{@uKwTL6H>CKJ561f_J1dU*;M*}7pJkh3MNf}lK4e<8#?&O1POi-1#+n=V?z zgmWw)f7Gj9sU0_Z%?CRH=*HC;0aL!sg@VBZm{yu(d8{r~Ydtmq0^Qq_6{&^pX;-8x z-DWYsH9I{Gll1y#Bj$C@2jR{l-Kq%mNZh05W#C&n_z?hmbjB11qe=Jl&b-zXKkzYn zTSo?$^ae1nS<33W3$^Pipxz%^lZj~VLA3=^H)X#EY3~o`#YYyJmx+F>4UkG(X8Ex~ zgNk5CyQg8QmPjU^*bMsZY;4HO#3ZIt95g9rzT6QE7BpuUwD%Cx4ajmqGT|FhkD7Nt zIRq)MKyGQvVi;vj?LSLPWQmtc;E@X<^L8%D5}(*ycATFV%w+sK#dKdWb5218*n^=E zr44IePENG2&~AnarI(ONnT>&r27UI`f?Ys_ay67urRxXPki+Dr#)_8gQ& zSADfTEiLoc7ve0}OFBP-n z(*shsHX8vYmw``2#s9;Ydl^;|Q)VY56pyfDx-UeGzxN&SSwX z=vnIt4WO=N5mo)B8i{n*y%%YVd7TSktZcmm(M@V3Twsl);bvS0#03g-=qx3MAX#_& zm`-b|3=G%ivvX7l|D;SRyVI>LpQD$k=1%vZGriMTToiOG~ z>yYT{B*M~)qdHnck3zdO15y}DLe?*jANWK!X2%HF?XD7_qVbf4AarsDQj$$aV#s7?TPgh+ zpqNL!kAX!<8}jNQ=^wql38o8V*F_@xtySHp>Qo+?Ghj%+zwPi%9;Q367qnZtuF0)g zZ5#}2t_wu+4se2~^jgnIXRkPdAAMr9|MsS)elf556o_up@aabay3QD&%nIcrR&DxX zZwH^1A2H3dEo^;1>Q-kN<;8&DGKxY8&s4FqLD?nZ)kvgUX${RoelqeX67*Kg=e*NUS}GtVa57-d8Y)TV6`0-Qb`JvdeX&L5 zAvScfkNMO~g^;ZvZBY}7Nbt;J1!yZ;7C!2AT}9wU&=}?zSZ3P@eOrI$om67fR2KXOxV zeE?wh^hu7SwfeNo?(Gz$FC$F?c!LN_x~ggEn3bVI20{Sor84eQFU}-xXl+kP5PoI% zhktJLPUJqh7-5&H4oKC~!_dzV*+SqO?SaTkMtG&Gv2!yq&jc_NfHny_MI42&D0@3P#$jC=MTrvRQ8z*BXB@S483zugGh8=Vc3aWNPdq42%KF%aKs_eY*ASI)zx zfF88_TuoB)?I4`l%lumycuo-iBVjpeO`#I{l>j$4TX!A!#FqlkrgZtTymB*H*a6@H zO9wwvse3dJy8?Q!!>dlSfNleDE_!$3bOBfk!U{<8TcrGG#8D%*nYp`{0TG)%Wwga& znJ5L#0HYbivowWk^$-T}f;|VYxYEny5mP`9cDtu2Nnx4Gz$Ik-(A14| z*w2E#VDRSz8U&WLL>K@@Etu*UI3|+=ZM5?HJ`k2M;VGphyu7cL#3QbNY{cCz7hCue zFr5kTR2+W!Ism-Hgy*{Y?|*gNif!hiDN4Y)NS-sr676rT*j$qY0D)E8m$p8{A7 z#=C;B>XQy`J={C7e_I7;ap)dO5)&CTi5ZiLP!I4R29C-UAFM_C@1Jpc$@mQEKlc)2 tJA=0XVk>|xDIwZM`ajrwq;^|9_= 9 + .color_format = LV_COLOR_FORMAT_RGB565, +#endif + .rotation = { + .swap_xy = false, + .mirror_x = true, + .mirror_y = true, + }, + .flags = { + .buff_dma = true, +#if LVGL_VERSION_MAJOR >= 9 + .swap_bytes = true, +#endif + } + }; + lvgl_disp = lvgl_port_add_disp(&disp_cfg); + + /* Add touch input (for selected screen) */ + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lvgl_disp, + .handle = touch_handle, + }; + lvgl_touch_indev = lvgl_port_add_touch(&touch_cfg); + + return ESP_OK; +} + +static void _app_button_cb(lv_event_t *e) +{ + lv_disp_rotation_t rotation = lv_disp_get_rotation(lvgl_disp); + rotation++; + if (rotation > LV_DISPLAY_ROTATION_270) { + rotation = LV_DISPLAY_ROTATION_0; + } + + /* LCD HW rotation */ + lv_disp_set_rotation(lvgl_disp, rotation); +} + +static void app_main_display(void) +{ + lv_obj_t *scr = lv_scr_act(); + + /* Task lock */ + lvgl_port_lock(0); + + /* Your LVGL objects code here .... */ + + /* Create image */ + lv_obj_t *img_logo = lv_img_create(scr); + lv_img_set_src(img_logo, &esp_logo); + lv_obj_align(img_logo, LV_ALIGN_TOP_MID, 0, 20); + + /* Label */ + lv_obj_t *label = lv_label_create(scr); + lv_obj_set_width(label, EXAMPLE_LCD_H_RES); + lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0); +#if LVGL_VERSION_MAJOR == 8 + lv_label_set_recolor(label, true); + lv_label_set_text(label, + "#FF0000 "LV_SYMBOL_BELL" Hello world Espressif and LVGL "LV_SYMBOL_BELL"#\n#FF9400 "LV_SYMBOL_WARNING" For simplier initialization, use BSP "LV_SYMBOL_WARNING" #"); +#else + lv_label_set_text(label, + LV_SYMBOL_BELL" Hello world Espressif and LVGL "LV_SYMBOL_BELL"\n "LV_SYMBOL_WARNING" For simplier initialization, use BSP "LV_SYMBOL_WARNING); +#endif + lv_obj_align(label, LV_ALIGN_CENTER, 0, 20); + + /* Button */ + lv_obj_t *btn = lv_btn_create(scr); + label = lv_label_create(btn); + lv_label_set_text_static(label, "Rotate screen"); + lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -30); + lv_obj_add_event_cb(btn, _app_button_cb, LV_EVENT_CLICKED, NULL); + + /* Task unlock */ + lvgl_port_unlock(); +} + +void app_main(void) +{ + /* LCD HW initialization */ + ESP_ERROR_CHECK(app_lcd_init()); + + /* Touch initialization */ + ESP_ERROR_CHECK(app_touch_init()); + + /* LVGL initialization */ + ESP_ERROR_CHECK(app_lvgl_init()); + + /* Show LVGL objects */ + app_main_display(); +} diff --git a/components/espressif__esp_lvgl_port/examples/touchscreen/sdkconfig.defaults b/components/espressif__esp_lvgl_port/examples/touchscreen/sdkconfig.defaults new file mode 100644 index 00000000..23761495 --- /dev/null +++ b/components/espressif__esp_lvgl_port/examples/touchscreen/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y diff --git a/components/espressif__esp_lvgl_port/idf_component.yml b/components/espressif__esp_lvgl_port/idf_component.yml new file mode 100644 index 00000000..6423ddf9 --- /dev/null +++ b/components/espressif__esp_lvgl_port/idf_component.yml @@ -0,0 +1,12 @@ +dependencies: + idf: '>=5.1' + lvgl/lvgl: + public: true + version: '>=8,<10' +description: ESP LVGL port +repository: git://github.com/espressif/esp-bsp.git +repository_info: + commit_sha: ee66ac9af9bb8edd19ba48fb7b8c52c49dea74d2 + path: components/esp_lvgl_port +url: https://github.com/espressif/esp-bsp/tree/master/components/esp_lvgl_port +version: 2.7.2 diff --git a/components/espressif__esp_lvgl_port/images/img_cursor.png b/components/espressif__esp_lvgl_port/images/img_cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..ecc383ae0edc5b08214ce10b716d742fcf8cbd43 GIT binary patch literal 1810 zcmaJ?c~BE)6yIPvLq3$f=4wK zS`bl3IjjeW6|Gbeu@09l+IqG!j@T9zDANupG8U1dZ8sd&KOA@F+i&0Zz4v?9@6B2h z;v+rWeB1y4@Q_7G6RCZ!eYw)9_Y(NjVQQlp6C+mur*8QEP6YIINPGtQ`!Kqbh$+@S7hTPh~a9D zE`&97v4sU{lp(AXK^z>X55u!GQTYZuDL+10k)N#)DOt;xdAKdLh$#duPRc=xHb-X^ zTS8b9z+&p#e#~Kk6DnkO2uou32&Tp*fMJ9I2Zd}HQoyhfL`7^w$VUad#UK~vA{>~@ zK?D$jhy`3R!ULxs7Ud2Eml~8RaiTP0${i(zu(C)}FXnK}W;5H&V-p572N8)xb{H-f zqBJ1mdL1dZKsw{RNsuWRDQ;94GE8NG9U{$MRZjWAb`E&Pl|LM4mx`Q|j}kjj0;G*^yn5iblNBprN`W z&yf;}JT0yxWzrB9b;DL_lwv-Ha)p>m2w@_H0-`DkL%~8GA41_^A&lTW9K-NwoY&b$ zLBywO%1X29hn zVMr#3oM|>BWDz7`%p&w)SQyoP1O)?AG&&_=HZGj*71gcDXv3!{9L3#njK1<6HxJ|ssK!4S-qBYd2T355usHD#}SrHqrbrB0}H^wUv^ahMOI zB1{2^P%cVw5*I-tE{{ivA^|KCU`j-#Vu73qZc>4A>^ypfSCgEbIHgEy9~jQdiLpyB zrw-Rqqh_FH%E>sN1OSKUGN~jvD(2kYbHS}@M=@{mYdCnOHF`4fCLMP^j)im#gXIs7X9=;APH3OlQsn;V*XtUZj zzf)gx>HJum%{1En@bR7A;gbxyV`mm$Y`we5(*-T`-spHRQ;vvi<&TD+C3u}kUR0mD z_~`;q*C_VB7+d?>dn67Oba$(I5n`>1K9(5dQ>@FL8QR$PuBY>Y0_~ZSzRKKMR?o8H z&fZnas{i$J+g>By^`P{U6WY2eQ+6_A=5FbR@VQAIJ31uned9h1x;by=Z5L;~pD(Q@ zpTV#Rlg{6NQ5s*nL_8AOo#|KM(%cO6bv=m*RR<({6g?eGb4jm_4WK*Mzk*z<2CNNu8KTSIjgW=W^%t` zG6wIoG#)Mc`IhNi7Ds*hveh56S-1Od;+I$QQg%6R?s3K8HJs{4<#oTwZlp&v9#oV_ z{us$mpV401)o0SGC6D)(25*>C;BBp@jVH(7IAr*1PPD&Q%VqP`PaY0#+rJIUJ$5f= zZpVeEyS{ytdv?a=&WwSUi0mA{M$dHXP|?}u=D+I_0s|FKiYz{#j@C6qZ#Pp|rr~R< z?$0D=tu3V=Irv^R7t~pbhnKD1{iE~9V1Mr8YYgpNkvSRn_urUEUBHBwfe3Ko2-~?x*#a`xFVa=`!_l= e3JWhWJJ;f5)zU*-zHqVsm}KGc(o-ukxBLg*lAa6z literal 0 HcmV?d00001 diff --git a/components/espressif__esp_lvgl_port/images/img_cursor_20px.png b/components/espressif__esp_lvgl_port/images/img_cursor_20px.png new file mode 100644 index 0000000000000000000000000000000000000000..ee2660adc3dc14ad18db45e48af68e318c073003 GIT binary patch literal 1607 zcmaJ>eNYr-7+(lcsH?$`A(<1mRY}6V{kXkd_TrNJfa8)wL{dHs*t^@~Zs2y$-K9Gi zbx_h7GDV?KD-;zq(RA<&i}D~t$0q!&QATsjG6$_en?@ORn%?8X{KIKy-hKCZ-{<)~ zU%zMTb91b!*o0UJf>gFFOCH!Khu4H?@LpFxO$D|HInQc>4xCjF03oW}Xg5MoN5hLx zxN$&^@n;pv5cKl5;T17u_S>}(^uji8et}Y8&(=FdpN4aZ4qg-V`GGbBrOgfcIcEj0 zzz)9LD;SW$t`GuyT?S;a&W_vtX5Qn?s*(7GH97gtnhGcDLgv1uicJgZ0fLWLI5_C5 z6l8tSfD8lc!FTu=L*QW*rNV$1!ye%RdoFAiB_5_VIO@c48fI7xK~s!QI|n9llE82h zBXlT1=yjx?(88k+0^GrHn&fip^DLR8?to-KJc{DiV^|;%&;+y^Q7Xp>mSw{*B#8nI zRIU~jE{F>9j1iDg7z-~uC9hxciUJ&l|l<zuM1ZdAuxENlqT9&locXHT-`^VMP#)e!k=d-Qg-NqO;1! z3yRHRK){X0<#p*P2Sd^hH;p=2rxOJg#nDt+OQ8&&O5+5t$HySe;QpZ@vY;A(9aDtN`^X&z`M{RZgYpXX)Jg10Kz9?3Xu_4zZ zDx&NW{jk{#x=%3h?8RQeB?jc=v0j00jU;d3CGUH@D^n7E@Nl{H-sgnk(2+PpF(m57 z88^zY6oqn(GZn>2j-YtbLDK|;>FLz*0vI(3%v6*-OM#$B z!e%k%r_OKrC{$6jK;2u45Oonf&5brRD!$IV$+QVkHf-4S?w^0`xY0T<-kR)EQoR2} zR(m#@E@!15nP>FG50teQHl}5lEDQCq=PJ(ZJ2!n`Wy8>KE6%<7a7E~H?Mz!~dd&OX zNynq3hs5RMCzY*~;kvY7Y$BTEI>hu>F6z4_#n~@CP@iVi4X3NG9xLtIxBIKevwz=} zzKOhjw5acs^pn5j4l4Sb?YYP6Pyb4t@5cNuU0($kv`>TgT=KTKsg^U8eP_wRlwQY$ z?3oj5Qq{j-SW-L54{fbe;d_=*300f3gVnCW=KjHlRfWxiEDxbl>Cl5yE;=Y@rPCpUH6-PWTS+E-}0Gk)l2q0^kzy=BR)lG^#TG0TsRTiWY0qSMIvZle&z)fs+(7yM9DX+ z?kx56-jJvgsrcIUkd5eEbhKz1r&5JhvG>)%mg6^G+1xj^Q+|Xe_kDls(3;{?bK_17 zO^tt4A85KfQ1_UcQhEeyEV`D|6mwWC+uon8t_dCZ(iRa&Z%!(<3VGep_kKv+Y`bVU XW_`Tz)7qVT!+&#IMvmoxschB1OlChQ literal 0 HcmV?d00001 diff --git a/components/espressif__esp_lvgl_port/images/lvgl8/img_cursor.c b/components/espressif__esp_lvgl_port/images/lvgl8/img_cursor.c new file mode 100644 index 00000000..8455bd17 --- /dev/null +++ b/components/espressif__esp_lvgl_port/images/lvgl8/img_cursor.c @@ -0,0 +1,132 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifdef __has_include +#if __has_include("lvgl.h") +#ifndef LV_LVGL_H_INCLUDE_SIMPLE +#define LV_LVGL_H_INCLUDE_SIMPLE +#endif +#endif +#endif + +#if defined(LV_LVGL_H_INCLUDE_SIMPLE) +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + + +#ifndef LV_ATTRIBUTE_MEM_ALIGN +#define LV_ATTRIBUTE_MEM_ALIGN +#endif + +#ifndef LV_ATTRIBUTE_IMG_IMG_CURSOR +#define LV_ATTRIBUTE_IMG_IMG_CURSOR +#endif + +const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMG_IMG_CURSOR uint8_t img_cursor_map[] = { +#if LV_COLOR_DEPTH == 1 || LV_COLOR_DEPTH == 8 + /*Pixel format: Alpha 8 bit, Red: 3 bit, Green: 3 bit, Blue: 2 bit*/ + 0x00, 0xb2, 0x00, 0xcc, 0x00, 0x71, 0x00, 0x3a, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xcc, 0x00, 0xff, 0x00, 0xfc, 0x00, 0xe4, 0x00, 0xae, 0x00, 0x6b, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x71, 0x00, 0xfc, 0x6d, 0xf1, 0xb6, 0xfc, 0x49, 0xf9, 0x24, 0xfe, 0x00, 0xf4, 0x00, 0xb9, 0x00, 0x5c, 0x00, 0x2e, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3a, 0x00, 0xe4, 0xb6, 0xfc, 0xff, 0xff, 0xff, 0xf9, 0xdb, 0xfe, 0x6e, 0xf3, 0x25, 0xfe, 0x00, 0xf8, 0x00, 0xd8, 0x00, 0x9c, 0x00, 0x51, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0c, 0x00, 0xae, 0x49, 0xf9, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xb7, 0xf0, 0x6e, 0xfb, 0x25, 0xf9, 0x00, 0xff, 0x00, 0xe8, 0x00, 0x9e, 0x00, 0x4a, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x6b, 0x24, 0xfe, 0xdb, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xff, 0xf9, 0xb7, 0xf6, 0x49, 0xf5, 0x24, 0xff, 0x00, 0xf0, 0x00, 0xcb, 0x00, 0x88, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x13, 0x00, 0xf4, 0x6e, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x92, 0xf5, 0x24, 0xfd, 0x00, 0xff, 0x00, 0xed, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x25, 0xfe, 0xff, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf4, 0x92, 0xfc, 0x24, 0xfd, 0x00, 0xe7, 0x00, 0x78, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0xf8, 0xb7, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xf9, 0x25, 0xfd, 0x00, 0xee, 0x00, 0x9d, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0xd7, 0x6e, 0xfb, 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xf9, 0x25, 0xfd, 0x00, 0xdd, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x9c, 0x25, 0xf9, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xfa, 0x25, 0xfc, 0x00, 0xdd, 0x00, 0x2c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0xff, 0xb7, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xfa, 0x25, 0xfc, 0x00, 0xc2, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0xe8, 0x49, 0xf5, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xf9, 0xdb, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xfa, 0x25, 0xfd, 0x00, 0xdd, 0x00, 0x2c, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x24, 0xff, 0xff, 0xf5, 0xff, 0xf4, 0x25, 0xfd, 0x25, 0xfd, 0xdb, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xfa, 0x25, 0xfc, 0x00, 0xc2, 0x00, 0x2c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0xf0, 0x92, 0xf5, 0x92, 0xfc, 0x00, 0xee, 0x00, 0xdd, 0x25, 0xfc, 0xdb, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xef, 0x00, 0xff, 0x00, 0xd7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0xcb, 0x24, 0xfd, 0x24, 0xfd, 0x00, 0x9d, 0x00, 0x37, 0x00, 0xdd, 0x25, 0xfd, 0xdb, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x92, 0xf2, 0x00, 0xff, 0x00, 0xb6, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x88, 0x00, 0xff, 0x00, 0xe7, 0x00, 0x18, 0x00, 0x00, 0x00, 0x2c, 0x00, 0xc2, 0x25, 0xfc, 0xdb, 0xfa, 0xff, 0xff, 0xff, 0xfc, 0x92, 0xf8, 0x00, 0xfe, 0x00, 0x9d, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0xed, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x2c, 0x00, 0xdd, 0x25, 0xfc, 0xdb, 0xef, 0x92, 0xf2, 0x00, 0xfe, 0x00, 0xb9, 0x00, 0x16, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0xc2, 0x00, 0xff, 0x00, 0xff, 0x00, 0x9d, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x2c, 0x00, 0xd7, 0x00, 0xb6, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +#endif +#if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP == 0 + /*Pixel format: Alpha 8 bit, Red: 5 bit, Green: 6 bit, Blue: 5 bit*/ + 0x00, 0x00, 0xb2, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x71, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xcc, 0x00, 0x00, 0xff, 0x00, 0x00, 0xfc, 0x00, 0x00, 0xe4, 0x00, 0x00, 0xae, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x71, 0x00, 0x00, 0xfc, 0xeb, 0x5a, 0xf1, 0xb2, 0x94, 0xfc, 0x08, 0x42, 0xf9, 0x04, 0x21, 0xfe, 0x41, 0x08, 0xf4, 0x00, 0x00, 0xb9, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3a, 0x00, 0x00, 0xe4, 0xb2, 0x94, 0xfc, 0xff, 0xff, 0xff, 0x3c, 0xe7, 0xf9, 0x59, 0xce, 0xfe, 0x6e, 0x73, 0xf3, 0x04, 0x21, 0xfe, 0x00, 0x00, 0xf8, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x51, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0xae, 0x08, 0x42, 0xf9, 0x3c, 0xe7, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x76, 0xb5, 0xf0, 0x8e, 0x73, 0xfb, 0x45, 0x29, 0xf9, 0x82, 0x10, 0xff, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x21, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x04, 0x21, 0xfe, 0x59, 0xce, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xbb, 0xde, 0xf9, 0x96, 0xb5, 0xf6, 0x49, 0x4a, 0xf5, 0x04, 0x21, 0xff, 0x00, 0x00, 0xf0, 0x00, 0x00, 0xcb, 0x00, 0x00, 0x88, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x41, 0x08, 0xf4, 0x6e, 0x73, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbe, 0xf7, 0xf5, 0x10, 0x84, 0xf5, 0xa2, 0x10, 0xfd, 0x00, 0x00, 0xff, 0x00, 0x00, 0xed, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x04, 0x21, 0xfe, 0xff, 0xff, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9e, 0xf7, 0xf4, 0xae, 0x73, 0xfc, 0xa2, 0x10, 0xfd, 0x00, 0x00, 0xe7, 0x00, 0x00, 0x78, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0xf8, 0x76, 0xb5, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x59, 0xce, 0xf9, 0x86, 0x31, 0xfd, 0x00, 0x00, 0xee, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0xd7, 0x8e, 0x73, 0xfb, 0xff, 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x59, 0xce, 0xf9, 0x86, 0x31, 0xfd, 0x00, 0x00, 0xdd, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x9c, 0x45, 0x29, 0xf9, 0xbb, 0xde, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x79, 0xce, 0xfa, 0x65, 0x29, 0xfc, 0x20, 0x00, 0xdd, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x82, 0x10, 0xff, 0x96, 0xb5, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x96, 0xb5, 0xfa, 0x65, 0x29, 0xfc, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0xe8, 0x49, 0x4a, 0xf5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x59, 0xce, 0xf9, 0x59, 0xce, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x79, 0xce, 0xfa, 0x65, 0x29, 0xfd, 0x20, 0x00, 0xdd, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x04, 0x21, 0xff, 0xbe, 0xf7, 0xf5, 0x9e, 0xf7, 0xf4, 0x86, 0x31, 0xfd, 0x86, 0x31, 0xfd, 0x79, 0xce, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x96, 0xb5, 0xfa, 0x65, 0x29, 0xfc, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x2c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0xf0, 0x10, 0x84, 0xf5, 0xae, 0x73, 0xfc, 0x00, 0x00, 0xee, 0x00, 0x00, 0xdd, 0x65, 0x29, 0xfc, 0x96, 0xb5, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x96, 0xb5, 0xef, 0x00, 0x00, 0xff, 0x00, 0x00, 0xd7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0xcb, 0xa2, 0x10, 0xfd, 0xa2, 0x10, 0xfd, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x37, 0x20, 0x00, 0xdd, 0x65, 0x29, 0xfd, 0x79, 0xce, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x51, 0x8c, 0xf2, 0x00, 0x00, 0xff, 0x00, 0x00, 0xb6, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x88, 0x00, 0x00, 0xff, 0x00, 0x00, 0xe7, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0xc2, 0x65, 0x29, 0xfc, 0x96, 0xb5, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xaf, 0x7b, 0xf8, 0x41, 0x08, 0xfe, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0xed, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x2c, 0x20, 0x00, 0xdd, 0x65, 0x29, 0xfc, 0x96, 0xb5, 0xef, 0x51, 0x8c, 0xf2, 0x41, 0x08, 0xfe, 0x00, 0x00, 0xb9, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0xc2, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x2c, 0x00, 0x00, 0xd7, 0x00, 0x00, 0xb6, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +#endif +#if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP != 0 + /*Pixel format: Alpha 8 bit, Red: 5 bit, Green: 6 bit, Blue: 5 bit BUT the 2 color bytes are swapped*/ + 0x00, 0x00, 0xb2, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x71, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xcc, 0x00, 0x00, 0xff, 0x00, 0x00, 0xfc, 0x00, 0x00, 0xe4, 0x00, 0x00, 0xae, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x71, 0x00, 0x00, 0xfc, 0x5a, 0xeb, 0xf1, 0x94, 0xb2, 0xfc, 0x42, 0x08, 0xf9, 0x21, 0x04, 0xfe, 0x08, 0x41, 0xf4, 0x00, 0x00, 0xb9, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3a, 0x00, 0x00, 0xe4, 0x94, 0xb2, 0xfc, 0xff, 0xff, 0xff, 0xe7, 0x3c, 0xf9, 0xce, 0x59, 0xfe, 0x73, 0x6e, 0xf3, 0x21, 0x04, 0xfe, 0x00, 0x00, 0xf8, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x51, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0xae, 0x42, 0x08, 0xf9, 0xe7, 0x3c, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xb5, 0x76, 0xf0, 0x73, 0x8e, 0xfb, 0x29, 0x45, 0xf9, 0x10, 0x82, 0xff, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x21, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x21, 0x04, 0xfe, 0xce, 0x59, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xde, 0xbb, 0xf9, 0xb5, 0x96, 0xf6, 0x4a, 0x49, 0xf5, 0x21, 0x04, 0xff, 0x00, 0x00, 0xf0, 0x00, 0x00, 0xcb, 0x00, 0x00, 0x88, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x08, 0x41, 0xf4, 0x73, 0x6e, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xbe, 0xf5, 0x84, 0x10, 0xf5, 0x10, 0xa2, 0xfd, 0x00, 0x00, 0xff, 0x00, 0x00, 0xed, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x21, 0x04, 0xfe, 0xff, 0xff, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0x9e, 0xf4, 0x73, 0xae, 0xfc, 0x10, 0xa2, 0xfd, 0x00, 0x00, 0xe7, 0x00, 0x00, 0x78, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0xf8, 0xb5, 0x76, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xce, 0x59, 0xf9, 0x31, 0x86, 0xfd, 0x00, 0x00, 0xee, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0xd7, 0x73, 0x8e, 0xfb, 0xff, 0xff, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xce, 0x59, 0xf9, 0x31, 0x86, 0xfd, 0x00, 0x00, 0xdd, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x9c, 0x29, 0x45, 0xf9, 0xde, 0xbb, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xce, 0x79, 0xfa, 0x29, 0x65, 0xfc, 0x00, 0x20, 0xdd, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x10, 0x82, 0xff, 0xb5, 0x96, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb5, 0x96, 0xfa, 0x29, 0x65, 0xfc, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0xe8, 0x4a, 0x49, 0xf5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xce, 0x59, 0xf9, 0xce, 0x59, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xce, 0x79, 0xfa, 0x29, 0x65, 0xfd, 0x00, 0x20, 0xdd, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x21, 0x04, 0xff, 0xf7, 0xbe, 0xf5, 0xf7, 0x9e, 0xf4, 0x31, 0x86, 0xfd, 0x31, 0x86, 0xfd, 0xce, 0x79, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb5, 0x96, 0xfa, 0x29, 0x65, 0xfc, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x2c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0xf0, 0x84, 0x10, 0xf5, 0x73, 0xae, 0xfc, 0x00, 0x00, 0xee, 0x00, 0x00, 0xdd, 0x29, 0x65, 0xfc, 0xb5, 0x96, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb5, 0x96, 0xef, 0x00, 0x00, 0xff, 0x00, 0x00, 0xd7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0xcb, 0x10, 0xa2, 0xfd, 0x10, 0xa2, 0xfd, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x37, 0x00, 0x20, 0xdd, 0x29, 0x65, 0xfd, 0xce, 0x79, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x8c, 0x51, 0xf2, 0x00, 0x00, 0xff, 0x00, 0x00, 0xb6, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x88, 0x00, 0x00, 0xff, 0x00, 0x00, 0xe7, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0xc2, 0x29, 0x65, 0xfc, 0xb5, 0x96, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x7b, 0xaf, 0xf8, 0x08, 0x41, 0xfe, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0xed, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x2c, 0x00, 0x20, 0xdd, 0x29, 0x65, 0xfc, 0xb5, 0x96, 0xef, 0x8c, 0x51, 0xf2, 0x08, 0x41, 0xfe, 0x00, 0x00, 0xb9, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0xc2, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x2c, 0x00, 0x00, 0xd7, 0x00, 0x00, 0xb6, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +#endif +#if LV_COLOR_DEPTH == 32 + 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xae, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0xfc, 0x5b, 0x5b, 0x5b, 0xf1, 0x93, 0x93, 0x93, 0xfc, 0x41, 0x41, 0x41, 0xf9, 0x1e, 0x1e, 0x1e, 0xfe, 0x06, 0x06, 0x06, 0xf4, 0x00, 0x00, 0x00, 0xb9, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0xe4, 0x93, 0x93, 0x93, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xe3, 0xe3, 0xf9, 0xc8, 0xc8, 0xc8, 0xfe, 0x6c, 0x6c, 0x6c, 0xf3, 0x20, 0x20, 0x20, 0xfe, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xae, 0x41, 0x41, 0x41, 0xf9, 0xe3, 0xe3, 0xe3, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xfb, 0xfb, 0xfa, 0xac, 0xac, 0xac, 0xf0, 0x6f, 0x6f, 0x6f, 0xfb, 0x26, 0x26, 0x26, 0xf9, 0x0f, 0x0f, 0x0f, 0xff, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x1e, 0x1e, 0x1e, 0xfe, 0xc8, 0xc8, 0xc8, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfb, 0xd4, 0xd4, 0xd4, 0xf9, 0xae, 0xae, 0xae, 0xf6, 0x48, 0x48, 0x48, 0xf5, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xcb, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x06, 0x06, 0x06, 0xf4, 0x6c, 0x6c, 0x6c, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xf3, 0xf3, 0xf5, 0x7e, 0x7e, 0x7e, 0xf5, 0x12, 0x12, 0x12, 0xfd, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xed, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x20, 0x20, 0x20, 0xfe, 0xfb, 0xfb, 0xfb, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xef, 0xef, 0xf4, 0x73, 0x73, 0x73, 0xfc, 0x12, 0x12, 0x12, 0xfd, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0xf8, 0xac, 0xac, 0xac, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xf9, 0x2e, 0x2e, 0x2e, 0xfd, 0x00, 0x00, 0x00, 0xee, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0xd7, 0x6f, 0x6f, 0x6f, 0xfb, 0xfc, 0xfc, 0xfc, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xf9, 0x2e, 0x2e, 0x2e, 0xfd, 0x00, 0x00, 0x00, 0xdd, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x9c, 0x26, 0x26, 0x26, 0xf9, 0xd4, 0xd4, 0xd4, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xca, 0xca, 0xca, 0xfa, 0x2b, 0x2b, 0x2b, 0xfc, 0x03, 0x03, 0x03, 0xdd, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x0f, 0x0f, 0x0f, 0xff, 0xae, 0xae, 0xae, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb0, 0xb0, 0xb0, 0xfa, 0x2b, 0x2b, 0x2b, 0xfc, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xe8, 0x48, 0x48, 0x48, 0xf5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xf9, 0xc8, 0xc8, 0xc8, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xca, 0xca, 0xca, 0xfa, 0x2b, 0x2b, 0x2b, 0xfd, 0x03, 0x03, 0x03, 0xdd, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x1f, 0x1f, 0x1f, 0xff, 0xf3, 0xf3, 0xf3, 0xf5, 0xf0, 0xf0, 0xf0, 0xf4, 0x2e, 0x2e, 0x2e, 0xfd, 0x2e, 0x2e, 0x2e, 0xfd, 0xca, 0xca, 0xca, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb0, 0xb0, 0xb0, 0xfa, 0x2b, 0x2b, 0x2b, 0xfc, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0x2c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0xf0, 0x7e, 0x7e, 0x7e, 0xf5, 0x73, 0x73, 0x73, 0xfc, 0x00, 0x00, 0x00, 0xee, 0x00, 0x00, 0x00, 0xdd, 0x2b, 0x2b, 0x2b, 0xfc, 0xb0, 0xb0, 0xb0, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb1, 0xb1, 0xb1, 0xef, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xd7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xcb, 0x12, 0x12, 0x12, 0xfd, 0x12, 0x12, 0x12, 0xfd, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x37, 0x03, 0x03, 0x03, 0xdd, 0x2b, 0x2b, 0x2b, 0xfd, 0xca, 0xca, 0xca, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfc, 0x86, 0x86, 0x86, 0xf2, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xb6, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0xc2, 0x2b, 0x2b, 0x2b, 0xfc, 0xb0, 0xb0, 0xb0, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfc, 0x74, 0x74, 0x74, 0xf8, 0x06, 0x06, 0x06, 0xfe, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0xed, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x03, 0x03, 0x03, 0xdd, 0x2b, 0x2b, 0x2b, 0xfc, 0xb1, 0xb1, 0xb1, 0xef, 0x86, 0x86, 0x86, 0xf2, 0x06, 0x06, 0x06, 0xfe, 0x00, 0x00, 0x00, 0xb9, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0xd7, 0x00, 0x00, 0x00, 0xb6, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +#endif +}; + +const lv_img_dsc_t img_cursor = { + .header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA, + .header.always_zero = 0, + .header.reserved = 0, + .header.w = 20, + .header.h = 20, + .data_size = 400 * LV_IMG_PX_SIZE_ALPHA_BYTE, + .data = img_cursor_map, +}; diff --git a/components/espressif__esp_lvgl_port/images/lvgl9/img_cursor.c b/components/espressif__esp_lvgl_port/images/lvgl9/img_cursor.c new file mode 100644 index 00000000..0e16e53c --- /dev/null +++ b/components/espressif__esp_lvgl_port/images/lvgl9/img_cursor.c @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifdef __has_include +#if __has_include("lvgl.h") +#ifndef LV_LVGL_H_INCLUDE_SIMPLE +#define LV_LVGL_H_INCLUDE_SIMPLE +#endif +#endif +#endif + +#if defined(LV_LVGL_H_INCLUDE_SIMPLE) +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + + +#ifndef LV_ATTRIBUTE_MEM_ALIGN +#define LV_ATTRIBUTE_MEM_ALIGN +#endif + +#ifndef LV_ATTRIBUTE_IMG_DUST +#define LV_ATTRIBUTE_IMG_DUST +#endif + +static const +LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMG_DUST +uint8_t img_cursor_20px_map[] = { + + 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xae, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0xfc, 0x5b, 0x5b, 0x5b, 0xf1, 0x93, 0x93, 0x93, 0xfc, 0x41, 0x41, 0x41, 0xf9, 0x1e, 0x1e, 0x1e, 0xfe, 0x06, 0x06, 0x06, 0xf4, 0x00, 0x00, 0x00, 0xb9, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0xe4, 0x93, 0x93, 0x93, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xe3, 0xe3, 0xf9, 0xc8, 0xc8, 0xc8, 0xfe, 0x6c, 0x6c, 0x6c, 0xf3, 0x20, 0x20, 0x20, 0xfe, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xae, 0x41, 0x41, 0x41, 0xf9, 0xe3, 0xe3, 0xe3, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xfb, 0xfb, 0xfa, 0xac, 0xac, 0xac, 0xf0, 0x6f, 0x6f, 0x6f, 0xfb, 0x26, 0x26, 0x26, 0xf9, 0x0f, 0x0f, 0x0f, 0xff, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x1e, 0x1e, 0x1e, 0xfe, 0xc8, 0xc8, 0xc8, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfb, 0xd4, 0xd4, 0xd4, 0xf9, 0xae, 0xae, 0xae, 0xf6, 0x48, 0x48, 0x48, 0xf5, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xcb, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x06, 0x06, 0x06, 0xf4, 0x6c, 0x6c, 0x6c, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf2, 0xf2, 0xf2, 0xf5, 0x7e, 0x7e, 0x7e, 0xf5, 0x12, 0x12, 0x12, 0xfd, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xed, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x20, 0x20, 0x20, 0xfe, 0xfb, 0xfb, 0xfb, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xef, 0xef, 0xf4, 0x73, 0x73, 0x73, 0xfc, 0x12, 0x12, 0x12, 0xfd, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0xf8, 0xac, 0xac, 0xac, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xf9, 0x2e, 0x2e, 0x2e, 0xfd, 0x00, 0x00, 0x00, 0xee, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0xd7, 0x6f, 0x6f, 0x6f, 0xfb, 0xfc, 0xfc, 0xfc, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xf9, 0x2e, 0x2e, 0x2e, 0xfd, 0x00, 0x00, 0x00, 0xdd, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x9c, 0x26, 0x26, 0x26, 0xf9, 0xd4, 0xd4, 0xd4, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xca, 0xca, 0xca, 0xfa, 0x2b, 0x2b, 0x2b, 0xfc, 0x03, 0x03, 0x03, 0xdd, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x0f, 0x0f, 0x0f, 0xff, 0xae, 0xae, 0xae, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb0, 0xb0, 0xb0, 0xfa, 0x2b, 0x2b, 0x2b, 0xfc, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xe8, 0x48, 0x48, 0x48, 0xf5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xf9, 0xc8, 0xc8, 0xc8, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xca, 0xca, 0xca, 0xfa, 0x2b, 0x2b, 0x2b, 0xfd, 0x03, 0x03, 0x03, 0xdd, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x1f, 0x1f, 0x1f, 0xff, 0xf2, 0xf2, 0xf2, 0xf5, 0xf0, 0xf0, 0xf0, 0xf4, 0x2e, 0x2e, 0x2e, 0xfd, 0x2e, 0x2e, 0x2e, 0xfd, 0xca, 0xca, 0xca, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb0, 0xb0, 0xb0, 0xfa, 0x2b, 0x2b, 0x2b, 0xfc, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0x2c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0xf0, 0x7e, 0x7e, 0x7e, 0xf5, 0x73, 0x73, 0x73, 0xfc, 0x00, 0x00, 0x00, 0xee, 0x00, 0x00, 0x00, 0xdd, 0x2b, 0x2b, 0x2b, 0xfc, 0xb0, 0xb0, 0xb0, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb1, 0xb1, 0xb1, 0xef, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xd7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xcb, 0x12, 0x12, 0x12, 0xfd, 0x12, 0x12, 0x12, 0xfd, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x37, 0x03, 0x03, 0x03, 0xdd, 0x2b, 0x2b, 0x2b, 0xfd, 0xca, 0xca, 0xca, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfc, 0x86, 0x86, 0x86, 0xf2, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xb6, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0xc2, 0x2b, 0x2b, 0x2b, 0xfc, 0xb0, 0xb0, 0xb0, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfc, 0x74, 0x74, 0x74, 0xf8, 0x06, 0x06, 0x06, 0xfe, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0xed, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x03, 0x03, 0x03, 0xdd, 0x2b, 0x2b, 0x2b, 0xfc, 0xb1, 0xb1, 0xb1, 0xef, 0x86, 0x86, 0x86, 0xf2, 0x06, 0x06, 0x06, 0xfe, 0x00, 0x00, 0x00, 0xb9, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0xd7, 0x00, 0x00, 0x00, 0xb6, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + +}; + +const lv_img_dsc_t img_cursor = { + .header.magic = LV_IMAGE_HEADER_MAGIC, + .header.cf = LV_COLOR_FORMAT_ARGB8888, + .header.flags = 0, + .header.w = 20, + .header.h = 20, + .header.stride = 80, + .data_size = 1600, + .data = img_cursor_20px_map, +}; diff --git a/components/espressif__esp_lvgl_port/include/esp_lvgl_port.h b/components/espressif__esp_lvgl_port/include/esp_lvgl_port.h new file mode 100644 index 00000000..8c0d2345 --- /dev/null +++ b/components/espressif__esp_lvgl_port/include/esp_lvgl_port.h @@ -0,0 +1,161 @@ +/* + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LVGL port + */ + +#pragma once + +#include "esp_err.h" +#include "esp_heap_caps.h" +#include "lvgl.h" +#include "esp_lvgl_port_disp.h" +#include "esp_lvgl_port_touch.h" +#include "esp_lvgl_port_knob.h" +#include "esp_lvgl_port_button.h" +#include "esp_lvgl_port_usbhid.h" + +#if LVGL_VERSION_MAJOR == 8 +#include "esp_lvgl_port_compatibility.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LVGL Port task event type + */ +typedef enum { + LVGL_PORT_EVENT_DISPLAY = 0x01, + LVGL_PORT_EVENT_TOUCH = 0x02, + LVGL_PORT_EVENT_ENCODER = 0x04, + LVGL_PORT_EVENT_USER = 0x80, +} lvgl_port_event_type_t; + +/** + * @brief LVGL Port task events + */ +typedef struct { + lvgl_port_event_type_t type; + void *param; +} lvgl_port_event_t; + +/** + * @brief Init configuration structure + */ +typedef struct { + int task_priority; /*!< LVGL task priority */ + int task_stack; /*!< LVGL task stack size */ + int task_affinity; /*!< LVGL task pinned to core (-1 is no affinity) */ + int task_max_sleep_ms; /*!< Maximum sleep in LVGL task */ + unsigned task_stack_caps; /*!< LVGL task stack memory capabilities (see esp_heap_caps.h) */ + int timer_period_ms; /*!< LVGL timer tick period in ms */ +} lvgl_port_cfg_t; + +/** + * @brief LVGL port configuration structure + * + */ +#define ESP_LVGL_PORT_INIT_CONFIG() \ + { \ + .task_priority = 4, \ + .task_stack = 7168, \ + .task_affinity = -1, \ + .task_max_sleep_ms = 500, \ + .task_stack_caps = MALLOC_CAP_INTERNAL | MALLOC_CAP_DEFAULT, \ + .timer_period_ms = 5, \ + } + +/** + * @brief Initialize LVGL portation + * + * @note This function initialize LVGL and create timer and task for LVGL right working. + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if some of the create_args are not valid + * - ESP_ERR_INVALID_STATE if esp_timer library is not initialized yet + * - ESP_ERR_NO_MEM if memory allocation fails + */ +esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg); + +/** + * @brief Deinitialize LVGL portation + * + * @note This function deinitializes LVGL and stops the task if running. + * Some deinitialization will be done after the task will be stopped. + * + * @return + * - ESP_OK on success + * - ESP_ERR_TIMEOUT when stopping the LVGL task times out + */ +esp_err_t lvgl_port_deinit(void); + +/** + * @brief Take LVGL mutex + * + * @param timeout_ms Timeout in [ms]. 0 will block indefinitely. + * @return + * - true Mutex was taken + * - false Mutex was NOT taken + */ +bool lvgl_port_lock(uint32_t timeout_ms); + +/** + * @brief Give LVGL mutex + * + */ +void lvgl_port_unlock(void); + +/** + * @brief Notify LVGL, that data was flushed to LCD display + * + * @note It should be used only when not called inside LVGL port (more in README). + * + * @param disp LVGL display handle (returned from lvgl_port_add_disp) + */ +void lvgl_port_flush_ready(lv_display_t *disp); + +/** + * @brief Stop lvgl timer + * + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if the timer is not running + */ +esp_err_t lvgl_port_stop(void); + +/** + * @brief Resume lvgl timer + * + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if the timer is not running + */ +esp_err_t lvgl_port_resume(void); + +/** + * @brief Notify LVGL task, that display need reload + * + * @note It is called from LVGL events and touch interrupts + * + * @param event event type + * @param param parameter is not used, keep for backwards compatibility + * @return + * - ESP_OK on success + * - ESP_ERR_NOT_SUPPORTED if it is not implemented + * - ESP_ERR_INVALID_STATE if queue is not initialized (can be returned after LVGL deinit) + */ +esp_err_t lvgl_port_task_wake(lvgl_port_event_type_t event, void *param); + +#ifdef __cplusplus +} +#endif diff --git a/components/espressif__esp_lvgl_port/include/esp_lvgl_port_button.h b/components/espressif__esp_lvgl_port/include/esp_lvgl_port_button.h new file mode 100644 index 00000000..e653b2e9 --- /dev/null +++ b/components/espressif__esp_lvgl_port/include/esp_lvgl_port_button.h @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LVGL port button + */ + +#pragma once + +#include "esp_err.h" +#include "lvgl.h" + +#if __has_include ("iot_button.h") +#include "iot_button.h" +#define ESP_LVGL_PORT_BUTTON_COMPONENT 1 +#endif + +#if LVGL_VERSION_MAJOR == 8 +#include "esp_lvgl_port_compatibility.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef ESP_LVGL_PORT_BUTTON_COMPONENT +/** + * @brief Configuration of the navigation buttons structure + */ +typedef struct { + lv_display_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ +#if BUTTON_VER_MAJOR < 4 + const button_config_t *button_prev; /*!< Navigation button for previous */ + const button_config_t *button_next; /*!< Navigation button for next */ + const button_config_t *button_enter; /*!< Navigation button for enter */ +#else + button_handle_t button_prev; /*!< Handle for navigation button for previous */ + button_handle_t button_next; /*!< Handle for navigation button for next */ + button_handle_t button_enter; /*!< Handle for navigation button for enter */ +#endif +} lvgl_port_nav_btns_cfg_t; + +/** + * @brief Add buttons as an input device + * + * @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_navigation_buttons for free all memory! + * + * @param buttons_cfg Buttons configuration structure + * @return Pointer to LVGL buttons input device or NULL when error occurred + */ +lv_indev_t *lvgl_port_add_navigation_buttons(const lvgl_port_nav_btns_cfg_t *buttons_cfg); + +/** + * @brief Remove selected buttons from input devices + * + * @note Free all memory used for this input device. + * + * @return + * - ESP_OK on success + */ +esp_err_t lvgl_port_remove_navigation_buttons(lv_indev_t *buttons); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/components/espressif__esp_lvgl_port/include/esp_lvgl_port_compatibility.h b/components/espressif__esp_lvgl_port/include/esp_lvgl_port_compatibility.h new file mode 100644 index 00000000..008a09ec --- /dev/null +++ b/components/espressif__esp_lvgl_port/include/esp_lvgl_port_compatibility.h @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LVGL port compatibility + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Backward compatibility with LVGL 8 + */ +typedef lv_disp_t lv_display_t; +typedef enum { + LV_DISPLAY_ROTATION_0 = LV_DISP_ROT_NONE, + LV_DISPLAY_ROTATION_90 = LV_DISP_ROT_90, + LV_DISPLAY_ROTATION_180 = LV_DISP_ROT_180, + LV_DISPLAY_ROTATION_270 = LV_DISP_ROT_270 +} lv_disp_rotation_t; +typedef lv_disp_rotation_t lv_display_rotation_t; + +#ifdef __cplusplus +} +#endif diff --git a/components/espressif__esp_lvgl_port/include/esp_lvgl_port_disp.h b/components/espressif__esp_lvgl_port/include/esp_lvgl_port_disp.h new file mode 100644 index 00000000..9e9de438 --- /dev/null +++ b/components/espressif__esp_lvgl_port/include/esp_lvgl_port_disp.h @@ -0,0 +1,143 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LVGL port display + */ + +#pragma once + +#include "esp_err.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "lvgl.h" + +#if LVGL_VERSION_MAJOR == 8 +#include "esp_lvgl_port_compatibility.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Rotation configuration + */ +typedef struct { + bool swap_xy; /*!< LCD Screen swapped X and Y (in esp_lcd driver) */ + bool mirror_x; /*!< LCD Screen mirrored X (in esp_lcd driver) */ + bool mirror_y; /*!< LCD Screen mirrored Y (in esp_lcd driver) */ +} lvgl_port_rotation_cfg_t; + +/** + * @brief Rounder callback + */ +typedef void (*lvgl_port_rounder_cb_t)(lv_area_t *area); + +/** + * @brief Configuration display structure + */ +typedef struct { + esp_lcd_panel_io_handle_t io_handle; /*!< LCD panel IO handle */ + esp_lcd_panel_handle_t panel_handle; /*!< LCD panel handle */ + esp_lcd_panel_handle_t control_handle; /*!< LCD panel control handle */ + + uint32_t buffer_size; /*!< Size of the buffer for the screen in pixels */ + bool double_buffer; /*!< True, if should be allocated two buffers */ + uint32_t trans_size; /*!< Allocated buffer will be in SRAM to move framebuf (optional) */ + + uint32_t hres; /*!< LCD display horizontal resolution */ + uint32_t vres; /*!< LCD display vertical resolution */ + + bool monochrome; /*!< True, if display is monochrome and using 1bit for 1px */ + + lvgl_port_rotation_cfg_t + rotation; /*!< Default values of the screen rotation (Only HW state. Not supported for default SW rotation!) */ + lvgl_port_rounder_cb_t rounder_cb; /*!< Rounder callback for display area */ +#if LVGL_VERSION_MAJOR >= 9 + lv_color_format_t color_format; /*!< The color format of the display */ +#endif + struct { + unsigned int buff_dma: 1; /*!< Allocated LVGL buffer will be DMA capable */ + unsigned int buff_spiram: 1; /*!< Allocated LVGL buffer will be in PSRAM */ + unsigned int sw_rotate: 1; /*!< Use software rotation (slower) or PPA if available */ +#if LVGL_VERSION_MAJOR >= 9 + unsigned int swap_bytes: 1; /*!< Swap bytes in RGB565 (16-bit) color format before send to LCD driver */ +#endif + unsigned int full_refresh: 1;/*!< 1: Always make the whole screen redrawn */ + unsigned int direct_mode: 1; /*!< 1: Use screen-sized buffers and draw to absolute coordinates */ + } flags; +} lvgl_port_display_cfg_t; + +/** + * @brief Configuration RGB display structure + */ +typedef struct { + struct { + unsigned int bb_mode: 1; /*!< 1: Use bounce buffer mode */ +unsigned int avoid_tearing: + 1; /*!< 1: Use internal RGB buffers as a LVGL draw buffers to avoid tearing effect, enabling this option requires over two LCD buffers and may reduce the frame rate */ + } flags; +} lvgl_port_display_rgb_cfg_t; + +/** + * @brief Configuration MIPI-DSI display structure + */ +typedef struct { + struct { +unsigned int avoid_tearing: + 1; /*!< 1: Use internal MIPI-DSI buffers as a LVGL draw buffers to avoid tearing effect, enabling this option requires over two LCD buffers and may reduce the frame rate */ + } flags; +} lvgl_port_display_dsi_cfg_t; + +/** + * @brief Add I2C/SPI/I8080 display handling to LVGL + * + * @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_disp for free all memory! + * + * @param disp_cfg Display configuration structure + * @return Pointer to LVGL display or NULL when error occurred + */ +lv_display_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg); + +/** + * @brief Add MIPI-DSI display handling to LVGL + * + * @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_disp for free all memory! + * + * @param disp_cfg Display configuration structure + * @param dsi_cfg MIPI-DSI display specific configuration structure + * @return Pointer to LVGL display or NULL when error occurred + */ +lv_display_t *lvgl_port_add_disp_dsi(const lvgl_port_display_cfg_t *disp_cfg, + const lvgl_port_display_dsi_cfg_t *dsi_cfg); + +/** + * @brief Add RGB display handling to LVGL + * + * @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_disp for free all memory! + * + * @param disp_cfg Display configuration structure + * @param rgb_cfg RGB display specific configuration structure + * @return Pointer to LVGL display or NULL when error occurred + */ +lv_display_t *lvgl_port_add_disp_rgb(const lvgl_port_display_cfg_t *disp_cfg, + const lvgl_port_display_rgb_cfg_t *rgb_cfg); + +/** + * @brief Remove display handling from LVGL + * + * @note Free all memory used for this display. + * + * @return + * - ESP_OK on success + */ +esp_err_t lvgl_port_remove_disp(lv_display_t *disp); + +#ifdef __cplusplus +} +#endif diff --git a/components/espressif__esp_lvgl_port/include/esp_lvgl_port_knob.h b/components/espressif__esp_lvgl_port/include/esp_lvgl_port_knob.h new file mode 100644 index 00000000..c20b90eb --- /dev/null +++ b/components/espressif__esp_lvgl_port/include/esp_lvgl_port_knob.h @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LVGL port knob + */ + +#pragma once + +#include "esp_err.h" +#include "lvgl.h" + +#if __has_include ("iot_knob.h") +#if !__has_include("iot_button.h") +#error LVLG Knob requires button component. Please add it with idf.py add-dependency espressif/button +#endif +#include "iot_knob.h" +#include "iot_button.h" +#define ESP_LVGL_PORT_KNOB_COMPONENT 1 +#endif + +#if LVGL_VERSION_MAJOR == 8 +#include "esp_lvgl_port_compatibility.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef ESP_LVGL_PORT_KNOB_COMPONENT +/** + * @brief Configuration of the encoder structure + */ +typedef struct { + lv_display_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ + const knob_config_t *encoder_a_b; /*!< Encoder knob configuration */ +#if BUTTON_VER_MAJOR < 4 + const button_config_t *encoder_enter; /*!< Navigation button for enter */ +#else + button_handle_t encoder_enter; /*!< Handle for enter button */ +#endif +} lvgl_port_encoder_cfg_t; + +/** + * @brief Add encoder as an input device + * + * @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_encoder for free all memory! + * + * @param encoder_cfg Encoder configuration structure + * @return Pointer to LVGL encoder input device or NULL when error occurred + */ +lv_indev_t *lvgl_port_add_encoder(const lvgl_port_encoder_cfg_t *encoder_cfg); + +/** + * @brief Remove encoder from input devices + * + * @note Free all memory used for this input device. + * + * @return + * - ESP_OK on success + */ +esp_err_t lvgl_port_remove_encoder(lv_indev_t *encoder); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/components/espressif__esp_lvgl_port/include/esp_lvgl_port_lv_blend.h b/components/espressif__esp_lvgl_port/include/esp_lvgl_port_lv_blend.h new file mode 100644 index 00000000..9d58c4e6 --- /dev/null +++ b/components/espressif__esp_lvgl_port/include/esp_lvgl_port_lv_blend.h @@ -0,0 +1,159 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#if !CONFIG_LV_DRAW_SW_ASM_CUSTOM +#warning "esp_lvgl_port_lv_blend.h included, but CONFIG_LV_DRAW_SW_ASM_CUSTOM not set. Assembly rendering not used" +#else + +/********************* + * DEFINES + *********************/ + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888 +#define LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888(dsc) \ + _lv_color_blend_to_argb8888_esp(dsc) +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB565 +#define LV_DRAW_SW_COLOR_BLEND_TO_RGB565(dsc) \ + _lv_color_blend_to_rgb565_esp(dsc) +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB888 +#define LV_DRAW_SW_COLOR_BLEND_TO_RGB888(dsc, dest_px_size) \ + _lv_color_blend_to_rgb888_esp(dsc, dest_px_size) +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565 +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565(dsc) \ + _lv_rgb565_blend_normal_to_rgb565_esp(dsc) +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888 +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888(dsc, dest_px_size, src_px_size) \ + _lv_rgb888_blend_normal_to_rgb888_esp(dsc, dest_px_size, src_px_size) +#endif + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + uint32_t opa; + void *dst_buf; + uint32_t dst_w; + uint32_t dst_h; + uint32_t dst_stride; + const void *src_buf; + uint32_t src_stride; + const lv_opa_t *mask_buf; + uint32_t mask_stride; +} asm_dsc_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +extern int lv_color_blend_to_argb8888_esp(asm_dsc_t *asm_dsc); + +static inline lv_result_t _lv_color_blend_to_argb8888_esp(_lv_draw_sw_blend_fill_dsc_t *dsc) +{ + asm_dsc_t asm_dsc = { + .dst_buf = dsc->dest_buf, + .dst_w = dsc->dest_w, + .dst_h = dsc->dest_h, + .dst_stride = dsc->dest_stride, + .src_buf = &dsc->color, + }; + + return lv_color_blend_to_argb8888_esp(&asm_dsc); +} + +extern int lv_color_blend_to_rgb565_esp(asm_dsc_t *asm_dsc); + +static inline lv_result_t _lv_color_blend_to_rgb565_esp(_lv_draw_sw_blend_fill_dsc_t *dsc) +{ + asm_dsc_t asm_dsc = { + .dst_buf = dsc->dest_buf, + .dst_w = dsc->dest_w, + .dst_h = dsc->dest_h, + .dst_stride = dsc->dest_stride, + .src_buf = &dsc->color, + }; + + return lv_color_blend_to_rgb565_esp(&asm_dsc); +} + +extern int lv_color_blend_to_rgb888_esp(asm_dsc_t *asm_dsc); + +static inline lv_result_t _lv_color_blend_to_rgb888_esp(_lv_draw_sw_blend_fill_dsc_t *dsc, uint32_t dest_px_size) +{ + if (dest_px_size != 3) { + return LV_RESULT_INVALID; + } + asm_dsc_t asm_dsc = { + .dst_buf = dsc->dest_buf, + .dst_w = dsc->dest_w, + .dst_h = dsc->dest_h, + .dst_stride = dsc->dest_stride, + .src_buf = &dsc->color, + }; + + return lv_color_blend_to_rgb888_esp(&asm_dsc); +} + +extern int lv_rgb565_blend_normal_to_rgb565_esp(asm_dsc_t *asm_dsc); + +static inline lv_result_t _lv_rgb565_blend_normal_to_rgb565_esp(_lv_draw_sw_blend_image_dsc_t *dsc) +{ + asm_dsc_t asm_dsc = { + .dst_buf = dsc->dest_buf, + .dst_w = dsc->dest_w, + .dst_h = dsc->dest_h, + .dst_stride = dsc->dest_stride, + .src_buf = dsc->src_buf, + .src_stride = dsc->src_stride + }; + + return lv_rgb565_blend_normal_to_rgb565_esp(&asm_dsc); +} + +extern int lv_rgb888_blend_normal_to_rgb888_esp(asm_dsc_t *asm_dsc); + +static inline lv_result_t _lv_rgb888_blend_normal_to_rgb888_esp(_lv_draw_sw_blend_image_dsc_t *dsc, + uint32_t dest_px_size, uint32_t src_px_size) +{ + if (!(dest_px_size == 3 && src_px_size == 3)) { + return LV_RESULT_INVALID; + } + + asm_dsc_t asm_dsc = { + .dst_buf = dsc->dest_buf, + .dst_w = dsc->dest_w, + .dst_h = dsc->dest_h, + .dst_stride = dsc->dest_stride, + .src_buf = dsc->src_buf, + .src_stride = dsc->src_stride + }; + + return lv_rgb888_blend_normal_to_rgb888_esp(&asm_dsc); +} + +#endif // CONFIG_LV_DRAW_SW_ASM_CUSTOM + +#ifdef __cplusplus +} /*extern "C"*/ +#endif diff --git a/components/espressif__esp_lvgl_port/include/esp_lvgl_port_touch.h b/components/espressif__esp_lvgl_port/include/esp_lvgl_port_touch.h new file mode 100644 index 00000000..5abb9a11 --- /dev/null +++ b/components/espressif__esp_lvgl_port/include/esp_lvgl_port_touch.h @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LVGL port touch + */ + +#pragma once + +#include "esp_err.h" +#include "lvgl.h" + +#if __has_include ("esp_lcd_touch.h") +#include "esp_lcd_touch.h" +#define ESP_LVGL_PORT_TOUCH_COMPONENT 1 +#endif + +#if LVGL_VERSION_MAJOR == 8 +#include "esp_lvgl_port_compatibility.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef ESP_LVGL_PORT_TOUCH_COMPONENT +/** + * @brief Configuration touch structure + */ +typedef struct { + lv_display_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ + esp_lcd_touch_handle_t handle; /*!< LCD touch IO handle */ + struct { + float x; + float y; + } scale; /*!< Touch scale */ +} lvgl_port_touch_cfg_t; + +/** + * @brief Add LCD touch as an input device + * + * @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_touch for free all memory! + * + * @param touch_cfg Touch configuration structure + * @return Pointer to LVGL touch input device or NULL when error occurred + */ +lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg); + +/** + * @brief Remove selected LCD touch from input devices + * + * @note Free all memory used for this display. + * + * @return + * - ESP_OK on success + */ +esp_err_t lvgl_port_remove_touch(lv_indev_t *touch); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/components/espressif__esp_lvgl_port/include/esp_lvgl_port_usbhid.h b/components/espressif__esp_lvgl_port/include/esp_lvgl_port_usbhid.h new file mode 100644 index 00000000..f8d303cd --- /dev/null +++ b/components/espressif__esp_lvgl_port/include/esp_lvgl_port_usbhid.h @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LVGL port USB HID + */ + +#pragma once + +#include "esp_err.h" +#include "lvgl.h" + +#if __has_include ("usb/hid_host.h") +#define ESP_LVGL_PORT_USB_HOST_HID_COMPONENT 1 +#endif + +#if LVGL_VERSION_MAJOR == 8 +#include "esp_lvgl_port_compatibility.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef ESP_LVGL_PORT_USB_HOST_HID_COMPONENT +/** + * @brief Configuration of the mouse input + */ +typedef struct { + lv_display_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ + uint8_t sensitivity; /*!< Mouse sensitivity (cannot be zero) */ + lv_obj_t *cursor_img; /*!< Mouse cursor image, if NULL then used default */ +} lvgl_port_hid_mouse_cfg_t; + +/** + * @brief Configuration of the keyboard input + */ +typedef struct { + lv_display_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ +} lvgl_port_hid_keyboard_cfg_t; + +/** + * @brief Add USB HID mouse as an input device + * + * @note The USB host must be initialized before. Use `usb_host_install` for host initialization. + * + * @param mouse_cfg mouse configuration structure + * @return Pointer to LVGL buttons input device or NULL when error occurred + */ +lv_indev_t *lvgl_port_add_usb_hid_mouse_input(const lvgl_port_hid_mouse_cfg_t *mouse_cfg); + +/** + * @brief Add USB HID keyboard as an input device + * + * @note The USB host must be initialized before. Use `usb_host_install` for host initialization. + * + * @param keyboard_cfg keyboard configuration structure + * @return Pointer to LVGL buttons input device or NULL when error occurred + */ +lv_indev_t *lvgl_port_add_usb_hid_keyboard_input(const lvgl_port_hid_keyboard_cfg_t *keyboard_cfg); + +/** + * @brief Remove selected USB HID from input devices + * + * @note Free all memory used for this input device. When removed all HID devices, the HID task will be freed. + * + * @return + * - ESP_OK on success + */ +esp_err_t lvgl_port_remove_usb_hid_input(lv_indev_t *hid); +#endif + + +#ifdef __cplusplus +} +#endif diff --git a/components/espressif__esp_lvgl_port/license.txt b/components/espressif__esp_lvgl_port/license.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/components/espressif__esp_lvgl_port/license.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/components/espressif__esp_lvgl_port/priv_include/esp_lvgl_port_priv.h b/components/espressif__esp_lvgl_port/priv_include/esp_lvgl_port_priv.h new file mode 100644 index 00000000..16bdca3b --- /dev/null +++ b/components/espressif__esp_lvgl_port/priv_include/esp_lvgl_port_priv.h @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LVGL port private + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Rotation configuration + */ +typedef enum { + LVGL_PORT_DISP_TYPE_OTHER, + LVGL_PORT_DISP_TYPE_DSI, + LVGL_PORT_DISP_TYPE_RGB, +} lvgl_port_disp_type_t; + +/** + * @brief Rotation configuration + */ +typedef struct { + unsigned int avoid_tearing: 1; /*!< Use internal RGB buffers as a LVGL draw buffers to avoid tearing effect */ +} lvgl_port_disp_priv_cfg_t; + +/** + * @brief Notify LVGL task + * + * @note It is called from RGB vsync ready + * + * @param value notification value + * @return + * - true, whether a high priority task has been waken up by this function + */ +bool lvgl_port_task_notify(uint32_t value); + +#ifdef __cplusplus +} +#endif diff --git a/components/espressif__esp_lvgl_port/project_include.cmake b/components/espressif__esp_lvgl_port/project_include.cmake new file mode 100644 index 00000000..02ce6e38 --- /dev/null +++ b/components/espressif__esp_lvgl_port/project_include.cmake @@ -0,0 +1,78 @@ +# lvgl_port_create_c_image +# +# Create a C array of image for using with LVGL +function(lvgl_port_create_c_image image_path output_path color_format compression) + + #Get Python + idf_build_get_property(python PYTHON) + + #Get LVGL version + idf_build_get_property(build_components BUILD_COMPONENTS) + if(lvgl IN_LIST build_components) + set(lvgl_name lvgl) # Local component + set(lvgl_ver $ENV{LVGL_VERSION}) # Get the version from env variable (set from LVGL v9.2) + else() + set(lvgl_name lvgl__lvgl) # Managed component + idf_component_get_property(lvgl_ver ${lvgl_name} COMPONENT_VERSION) # Get the version from esp-idf build system + endif() + + if("${lvgl_ver}" STREQUAL "") + message("Could not determine LVGL version, assuming v9.x") + set(lvgl_ver "9.0.0") + endif() + + get_filename_component(image_full_path ${image_path} ABSOLUTE) + get_filename_component(output_full_path ${output_path} ABSOLUTE) + if(NOT EXISTS ${image_full_path}) + message(FATAL_ERROR "Input image (${image_full_path}) not exists!") + endif() + + message(STATUS "Generating C array image: ${image_path}") + + #Create C array image by LVGL version + if(lvgl_ver VERSION_LESS "9.0.0") + + if(CONFIG_LV_COLOR_16_SWAP) + set(color_format "RGB565SWAP") + else() + set(color_format "RGB565") + endif() + + execute_process(COMMAND git clone https://github.com/W-Mai/lvgl_image_converter.git "${CMAKE_BINARY_DIR}/lvgl_image_converter") + execute_process(COMMAND git checkout 9174634e9dcc1b21a63668969406897aad650f35 WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/lvgl_image_converter" OUTPUT_QUIET) + execute_process(COMMAND ${python} -m pip install --upgrade pip) + execute_process(COMMAND ${python} -m pip install pillow==10.3.0) + #execute_process(COMMAND python -m pip install -r "${CMAKE_BINARY_DIR}/lvgl_image_converter/requirements.txt") + execute_process(COMMAND ${python} "${CMAKE_BINARY_DIR}/lvgl_image_converter/lv_img_conv.py" + -ff C + -f true_color_alpha + -cf ${color_format} + -o ${output_full_path} + ${image_full_path}) + else() + idf_component_get_property(lvgl_dir ${lvgl_name} COMPONENT_DIR) + get_filename_component(script_path ${lvgl_dir}/scripts/LVGLImage.py ABSOLUTE) + set(lvglimage_py ${python} ${script_path}) + + #Install dependencies + execute_process(COMMAND ${python} -m pip install pypng lz4 OUTPUT_QUIET) + + execute_process(COMMAND ${lvglimage_py} + --ofmt=C + --cf=${color_format} + --compress=${compression} + -o ${output_full_path} + ${image_full_path}) + endif() + +endfunction() + +# lvgl_port_add_images +# +# Add all images to build +function(lvgl_port_add_images component output_path) + #Add images to sources + file(GLOB_RECURSE IMAGE_SOURCES ${output_path}*.c) + target_sources(${component} PRIVATE ${IMAGE_SOURCES}) + idf_build_set_property(COMPILE_OPTIONS "-DLV_LVGL_H_INCLUDE_SIMPLE=1" APPEND) +endfunction() diff --git a/components/espressif__esp_lvgl_port/src/common/ppa/lcd_ppa.c b/components/espressif__esp_lvgl_port/src/common/ppa/lcd_ppa.c new file mode 100644 index 00000000..a538e651 --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/common/ppa/lcd_ppa.c @@ -0,0 +1,202 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include +#include "esp_err.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "soc/soc_caps.h" +#include "lcd_ppa.h" + +#define PPA_LCD_ENABLE_CB 0 + +#if SOC_PPA_SUPPORTED +#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) + +struct lvgl_port_ppa_t { + uint8_t *buffer; + uint32_t buffer_size; + ppa_client_handle_t srm_handle; + ppa_srm_color_mode_t color_mode; +}; + +static const char *TAG = "PPA"; +/******************************************************************************* +* Function definitions +*******************************************************************************/ +#if PPA_LCD_ENABLE_CB +static bool _lvgl_port_ppa_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *event_data, void *user_data); +#endif +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lvgl_port_ppa_handle_t lvgl_port_ppa_create(const lvgl_port_ppa_cfg_t *cfg) +{ + esp_err_t ret = ESP_OK; + assert(cfg != NULL); + + lvgl_port_ppa_t *ppa_ctx = malloc(sizeof(lvgl_port_ppa_t)); + ESP_GOTO_ON_FALSE(ppa_ctx, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for PPA context allocation!"); + memset(ppa_ctx, 0, sizeof(lvgl_port_ppa_t)); + + uint32_t buffer_caps = 0; + if (cfg->flags.buff_dma) { + buffer_caps |= MALLOC_CAP_DMA; + } + if (cfg->flags.buff_spiram) { + buffer_caps |= MALLOC_CAP_SPIRAM; + } + if (buffer_caps == 0) { + buffer_caps |= MALLOC_CAP_DEFAULT; + } + + ppa_ctx->buffer_size = ALIGN_UP(cfg->buffer_size, CONFIG_CACHE_L2_CACHE_LINE_SIZE); + ppa_ctx->buffer = heap_caps_aligned_calloc(CONFIG_CACHE_L2_CACHE_LINE_SIZE, ppa_ctx->buffer_size, sizeof(uint8_t), + buffer_caps); + assert(ppa_ctx->buffer != NULL); + + ppa_client_config_t ppa_client_config = { + .oper_type = PPA_OPERATION_SRM, + }; + ESP_GOTO_ON_ERROR(ppa_register_client(&ppa_client_config, &ppa_ctx->srm_handle), err, TAG, + "Error when registering PPA client!"); + +#if PPA_LCD_ENABLE_CB + ppa_event_callbacks_t ppa_cbs = { + .on_trans_done = _lvgl_port_ppa_callback, + }; + ESP_GOTO_ON_ERROR(ppa_client_register_event_callbacks(ppa_ctx->srm_handle, &ppa_cbs), err, TAG, + "Error when registering PPA callbacks!"); +#endif + + ppa_ctx->color_mode = cfg->color_mode; + +err: + if (ret != ESP_OK) { + if (ppa_ctx->buffer) { + free(ppa_ctx->buffer); + } + if (ppa_ctx) { + free(ppa_ctx); + } + } + + return ppa_ctx; +} + +void lvgl_port_ppa_delete(lvgl_port_ppa_handle_t handle) +{ + lvgl_port_ppa_t *ppa_ctx = (lvgl_port_ppa_t *)handle; + assert(ppa_ctx != NULL); + + if (ppa_ctx->buffer) { + free(ppa_ctx->buffer); + } + + ppa_unregister_client(ppa_ctx->srm_handle); + + free(ppa_ctx); +} + +uint8_t *lvgl_port_ppa_get_output_buffer(lvgl_port_ppa_handle_t handle) +{ + lvgl_port_ppa_t *ppa_ctx = (lvgl_port_ppa_t *)handle; + assert(ppa_ctx != NULL); + return ppa_ctx->buffer; +} + +esp_err_t lvgl_port_ppa_rotate(lvgl_port_ppa_handle_t handle, lvgl_port_ppa_disp_rotate_t *rotate_cfg) +{ + lvgl_port_ppa_t *ppa_ctx = (lvgl_port_ppa_t *)handle; + assert(ppa_ctx != NULL); + assert(rotate_cfg != NULL); + const int w = rotate_cfg->area.x2 - rotate_cfg->area.x1 + 1; + const int h = rotate_cfg->area.y2 - rotate_cfg->area.y1 + 1; + + /* Set dimension by screen size and rotation */ + int out_w = w; + int out_h = h; + + int x1 = rotate_cfg->area.x1; + int x2 = rotate_cfg->area.x2; + int y1 = rotate_cfg->area.y1; + int y2 = rotate_cfg->area.y2; + + /* Rotate coordinates */ + switch (rotate_cfg->rotation) { + case PPA_SRM_ROTATION_ANGLE_0: + break; + case PPA_SRM_ROTATION_ANGLE_90: + out_w = h; + out_h = w; + x1 = rotate_cfg->area.y1; + x2 = rotate_cfg->area.y2; + y1 = rotate_cfg->disp_size.hres - rotate_cfg->area.x2 - 1; + y2 = rotate_cfg->disp_size.hres - rotate_cfg->area.x1 - 1; + break; + case PPA_SRM_ROTATION_ANGLE_180: + x1 = rotate_cfg->disp_size.hres - rotate_cfg->area.x2 - 1; + x2 = rotate_cfg->disp_size.hres - rotate_cfg->area.x1 - 1; + y1 = rotate_cfg->disp_size.vres - rotate_cfg->area.y2 - 1; + y2 = rotate_cfg->disp_size.vres - rotate_cfg->area.y1 - 1; + break; + case PPA_SRM_ROTATION_ANGLE_270: + out_w = h; + out_h = w; + x1 = rotate_cfg->disp_size.vres - rotate_cfg->area.y2 - 1; + x2 = rotate_cfg->disp_size.vres - rotate_cfg->area.y1 - 1; + y1 = rotate_cfg->area.x1; + y2 = rotate_cfg->area.x2; + break; + } + /* Return new coordinates */ + rotate_cfg->area.x1 = x1; + rotate_cfg->area.x2 = x2; + rotate_cfg->area.y1 = y1; + rotate_cfg->area.y2 = y2; + + /* Prepare Operation */ + ppa_srm_oper_config_t srm_oper_config = { + .in.buffer = rotate_cfg->in_buff, + .in.pic_w = w, + .in.pic_h = h, + .in.block_w = w, + .in.block_h = h, + .in.block_offset_x = 0, + .in.block_offset_y = 0, + .in.srm_cm = ppa_ctx->color_mode, + + .out.buffer = ppa_ctx->buffer, + .out.buffer_size = ppa_ctx->buffer_size, + .out.pic_w = out_w, + .out.pic_h = out_h, + .out.block_offset_x = 0, + .out.block_offset_y = 0, + .out.srm_cm = ppa_ctx->color_mode, + + .rotation_angle = rotate_cfg->rotation, + .scale_x = 1.0, + .scale_y = 1.0, + + .byte_swap = rotate_cfg->swap_bytes, + + .mode = rotate_cfg->ppa_mode, + .user_data = rotate_cfg->user_data, + }; + + return ppa_do_scale_rotate_mirror(ppa_ctx->srm_handle, &srm_oper_config); +} + +#if PPA_LCD_ENABLE_CB +static bool _lvgl_port_ppa_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *event_data, void *user_data) +{ + return false; +} +#endif + +#endif diff --git a/components/espressif__esp_lvgl_port/src/common/ppa/lcd_ppa.h b/components/espressif__esp_lvgl_port/src/common/ppa/lcd_ppa.h new file mode 100644 index 00000000..440a6424 --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/common/ppa/lcd_ppa.h @@ -0,0 +1,110 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief LCD PPA + */ + +#pragma once +#include "driver/ppa.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct lvgl_port_ppa_t lvgl_port_ppa_t; +typedef lvgl_port_ppa_t *lvgl_port_ppa_handle_t; + +/** + * @brief Init configuration structure + */ +typedef struct { + uint32_t buffer_size; /*!< Size of the buffer for the PPA */ + ppa_srm_color_mode_t color_mode; /*!< Color mode of input/output data */ + struct { + unsigned int buff_dma: 1; /*!< Allocated buffer will be DMA capable */ + unsigned int buff_spiram: 1; /*!< Allocated buffer will be in PSRAM */ + } flags; +} lvgl_port_ppa_cfg_t; + +/** + * @brief Display area structure + */ +typedef struct { + uint16_t x1; + uint16_t x2; + uint16_t y1; + uint16_t y2; +} lvgl_port_ppa_disp_area_t; + +/** + * @brief Display size structure + */ +typedef struct { + uint32_t hres; + uint32_t vres; +} lvgl_port_ppa_disp_size_t; + +/** + * @brief Rotation configuration + */ +typedef struct { + uint8_t *in_buff; /*!< Input buffer for rotation */ + lvgl_port_ppa_disp_area_t area; /*!< Coordinates of area */ + lvgl_port_ppa_disp_size_t disp_size; /*!< Display size */ + ppa_srm_rotation_angle_t rotation; /*!< Output rotation */ + ppa_trans_mode_t ppa_mode; /*!< Blocking or non-blocking mode */ + bool swap_bytes; /*!< SWAP bytes */ + void *user_data; +} lvgl_port_ppa_disp_rotate_t; + + +/** + * @brief Initialize PPA + * + * @note This function initialize PPA SRM Client and create buffer for process. + * + * @param cfg Configuration structure + * + * @return + * - PPA LCD handle + */ +lvgl_port_ppa_handle_t lvgl_port_ppa_create(const lvgl_port_ppa_cfg_t *cfg); + +/** + * @brief Remove PPA + * + * @param handle PPA LCD handle + * + * @note This function free buffer and deinitialize PPA. + */ +void lvgl_port_ppa_delete(lvgl_port_ppa_handle_t handle); + +/** + * @brief Get output buffer + * + * @param handle PPA LCD handle + * + * @note This function get allocated buffer for output of PPA operation. + */ +uint8_t *lvgl_port_ppa_get_output_buffer(lvgl_port_ppa_handle_t handle); + +/** + * @brief Do rotation + * + * @param handle PPA LCD handle + * @param rotate_cfg Rotation settings + * + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if memory allocation fails + */ +esp_err_t lvgl_port_ppa_rotate(lvgl_port_ppa_handle_t handle, lvgl_port_ppa_disp_rotate_t *rotate_cfg); + +#ifdef __cplusplus +} +#endif diff --git a/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port.c b/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port.c new file mode 100644 index 00000000..7a2be3b4 --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port.c @@ -0,0 +1,229 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/idf_additions.h" +#include "esp_lvgl_port.h" +#include "esp_lvgl_port_priv.h" +#include "lvgl.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct lvgl_port_ctx_s { + TaskHandle_t lvgl_task; + SemaphoreHandle_t lvgl_mux; + esp_timer_handle_t tick_timer; + bool running; + int task_max_sleep_ms; + int timer_period_ms; +} lvgl_port_ctx_t; + +/******************************************************************************* +* Local variables +*******************************************************************************/ +static lvgl_port_ctx_t lvgl_port_ctx; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ +static void lvgl_port_task(void *arg); +static esp_err_t lvgl_port_tick_init(void); +static void lvgl_port_task_deinit(void); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(cfg->task_affinity < (configNUM_CORES), ESP_ERR_INVALID_ARG, err, TAG, + "Bad core number for task! Maximum core number is %d", (configNUM_CORES - 1)); + + memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx)); + + /* LVGL init */ + lv_init(); + /* Tick init */ + lvgl_port_ctx.timer_period_ms = cfg->timer_period_ms; + ESP_RETURN_ON_ERROR(lvgl_port_tick_init(), TAG, ""); + /* Create task */ + lvgl_port_ctx.task_max_sleep_ms = cfg->task_max_sleep_ms; + if (lvgl_port_ctx.task_max_sleep_ms == 0) { + lvgl_port_ctx.task_max_sleep_ms = 500; + } + /* LVGL semaphore */ + lvgl_port_ctx.lvgl_mux = xSemaphoreCreateRecursiveMutex(); + ESP_GOTO_ON_FALSE(lvgl_port_ctx.lvgl_mux, ESP_ERR_NO_MEM, err, TAG, "Create LVGL mutex fail!"); + + BaseType_t res; + const uint32_t caps = cfg->task_stack_caps ? cfg->task_stack_caps : MALLOC_CAP_INTERNAL | + MALLOC_CAP_DEFAULT; // caps cannot be zero + if (cfg->task_affinity < 0) { + res = xTaskCreateWithCaps(lvgl_port_task, "taskLVGL", cfg->task_stack, NULL, cfg->task_priority, + &lvgl_port_ctx.lvgl_task, caps); + } else { + res = xTaskCreatePinnedToCoreWithCaps(lvgl_port_task, "taskLVGL", cfg->task_stack, NULL, cfg->task_priority, + &lvgl_port_ctx.lvgl_task, cfg->task_affinity, caps); + } + ESP_GOTO_ON_FALSE(res == pdPASS, ESP_FAIL, err, TAG, "Create LVGL task fail!"); + +err: + if (ret != ESP_OK) { + lvgl_port_deinit(); + } + + return ret; +} + +esp_err_t lvgl_port_resume(void) +{ + esp_err_t ret = ESP_ERR_INVALID_STATE; + + if (lvgl_port_ctx.tick_timer != NULL) { + lv_timer_enable(true); + ret = esp_timer_start_periodic(lvgl_port_ctx.tick_timer, lvgl_port_ctx.timer_period_ms * 1000); + } + + return ret; +} + +esp_err_t lvgl_port_stop(void) +{ + esp_err_t ret = ESP_ERR_INVALID_STATE; + + if (lvgl_port_ctx.tick_timer != NULL) { + lv_timer_enable(false); + ret = esp_timer_stop(lvgl_port_ctx.tick_timer); + } + + return ret; +} + +esp_err_t lvgl_port_deinit(void) +{ + /* Stop running task */ + if (lvgl_port_ctx.running) { + lvgl_port_ctx.running = false; + } + + return ESP_OK; +} + +bool lvgl_port_lock(uint32_t timeout_ms) +{ + assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first"); + + const TickType_t timeout_ticks = (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + return xSemaphoreTakeRecursive(lvgl_port_ctx.lvgl_mux, timeout_ticks) == pdTRUE; +} + +void lvgl_port_unlock(void) +{ + assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first"); + xSemaphoreGiveRecursive(lvgl_port_ctx.lvgl_mux); +} + +esp_err_t lvgl_port_task_wake(lvgl_port_event_type_t event, void *param) +{ + ESP_LOGE(TAG, "Task wake is not supported, when used LVGL8!"); + return ESP_ERR_NOT_SUPPORTED; +} + +IRAM_ATTR bool lvgl_port_task_notify(uint32_t value) +{ + BaseType_t need_yield = pdFALSE; + + // Notify LVGL task + if (xPortInIsrContext() == pdTRUE) { + xTaskNotifyFromISR(lvgl_port_ctx.lvgl_task, value, eNoAction, &need_yield); + } else { + xTaskNotify(lvgl_port_ctx.lvgl_task, value, eNoAction); + } + + return (need_yield == pdTRUE); +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static void lvgl_port_task(void *arg) +{ + uint32_t task_delay_ms = lvgl_port_ctx.task_max_sleep_ms; + + ESP_LOGI(TAG, "Starting LVGL task"); + lvgl_port_ctx.running = true; + while (lvgl_port_ctx.running) { + if (lvgl_port_lock(0)) { + task_delay_ms = lv_timer_handler(); + lvgl_port_unlock(); + } + if (task_delay_ms > lvgl_port_ctx.task_max_sleep_ms) { + task_delay_ms = lvgl_port_ctx.task_max_sleep_ms; + } else if (task_delay_ms < 5) { + task_delay_ms = 5; + } + vTaskDelay(pdMS_TO_TICKS(task_delay_ms)); + } + + ESP_LOGI(TAG, "Stopped LVGL task"); + + lvgl_port_task_deinit(); + + /* Close task */ + vTaskDelete( NULL ); +} + +static void lvgl_port_task_deinit(void) +{ + /* Stop and delete timer */ + if (lvgl_port_ctx.tick_timer != NULL) { + esp_timer_stop(lvgl_port_ctx.tick_timer); + esp_timer_delete(lvgl_port_ctx.tick_timer); + lvgl_port_ctx.tick_timer = NULL; + } + + if (lvgl_port_ctx.lvgl_mux) { + vSemaphoreDelete(lvgl_port_ctx.lvgl_mux); + } + memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx)); +#if LV_ENABLE_GC || !LV_MEM_CUSTOM + /* Deinitialize LVGL */ + lv_deinit(); +#endif +} + +static void lvgl_port_tick_increment(void *arg) +{ + /* Tell LVGL how many milliseconds have elapsed */ + lv_tick_inc(lvgl_port_ctx.timer_period_ms); +} + +static esp_err_t lvgl_port_tick_init(void) +{ + // Tick interface for LVGL (using esp_timer to generate 2ms periodic event) + const esp_timer_create_args_t lvgl_tick_timer_args = { + .callback = &lvgl_port_tick_increment, + .name = "LVGL tick", + }; + ESP_RETURN_ON_ERROR(esp_timer_create(&lvgl_tick_timer_args, &lvgl_port_ctx.tick_timer), TAG, + "Creating LVGL timer filed!"); + return esp_timer_start_periodic(lvgl_port_ctx.tick_timer, lvgl_port_ctx.timer_period_ms * 1000); +} diff --git a/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_button.c b/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_button.c new file mode 100644 index 00000000..58e484eb --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_button.c @@ -0,0 +1,217 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lvgl_port.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef enum { + LVGL_PORT_NAV_BTN_PREV, + LVGL_PORT_NAV_BTN_NEXT, + LVGL_PORT_NAV_BTN_ENTER, + LVGL_PORT_NAV_BTN_CNT, +} lvgl_port_nav_btns_t; + +typedef struct { + button_handle_t btn[LVGL_PORT_NAV_BTN_CNT]; /* Button handlers */ + lv_indev_drv_t indev_drv; /* LVGL input device driver */ + bool btn_prev; /* Button prev state */ + bool btn_next; /* Button next state */ + bool btn_enter; /* Button enter state */ +} lvgl_port_nav_btns_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static void lvgl_port_navigation_buttons_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_btn_down_handler(void *arg, void *arg2); +static void lvgl_port_btn_up_handler(void *arg, void *arg2); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_navigation_buttons(const lvgl_port_nav_btns_cfg_t *buttons_cfg) +{ + esp_err_t ret = ESP_OK; + lv_indev_t *indev = NULL; + assert(buttons_cfg != NULL); + assert(buttons_cfg->disp != NULL); + + /* Touch context */ + lvgl_port_nav_btns_ctx_t *buttons_ctx = malloc(sizeof(lvgl_port_nav_btns_ctx_t)); + if (buttons_ctx == NULL) { + ESP_LOGE(TAG, "Not enough memory for buttons context allocation!"); + return NULL; + } + +#if BUTTON_VER_MAJOR < 4 + /* Previous button */ + if (buttons_cfg->button_prev != NULL) { + buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV] = iot_button_create(buttons_cfg->button_prev); + ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV], ESP_ERR_NO_MEM, err, TAG, + "Not enough memory for button create!"); + } + + /* Next button */ + if (buttons_cfg->button_next != NULL) { + buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT] = iot_button_create(buttons_cfg->button_next); + ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT], ESP_ERR_NO_MEM, err, TAG, + "Not enough memory for button create!"); + } + + /* Enter button */ + if (buttons_cfg->button_enter != NULL) { + buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER] = iot_button_create(buttons_cfg->button_enter); + ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER], ESP_ERR_NO_MEM, err, TAG, + "Not enough memory for button create!"); + } +#else + ESP_GOTO_ON_FALSE(buttons_cfg->button_prev && buttons_cfg->button_next + && buttons_cfg->button_enter, ESP_ERR_INVALID_ARG, err, TAG, "Invalid some button handler!"); + + buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV] = buttons_cfg->button_prev; + buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT] = buttons_cfg->button_next; + buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER] = buttons_cfg->button_enter; +#endif + + /* Button handlers */ + for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) { +#if BUTTON_VER_MAJOR < 4 + ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_DOWN, lvgl_port_btn_down_handler, + buttons_ctx)); + ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_UP, lvgl_port_btn_up_handler, buttons_ctx)); +#else + ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_DOWN, NULL, lvgl_port_btn_down_handler, + buttons_ctx)); + ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_UP, NULL, lvgl_port_btn_up_handler, + buttons_ctx)); +#endif + } + + buttons_ctx->btn_prev = false; + buttons_ctx->btn_next = false; + buttons_ctx->btn_enter = false; + + /* Register a touchpad input device */ + lv_indev_drv_init(&buttons_ctx->indev_drv); + buttons_ctx->indev_drv.type = LV_INDEV_TYPE_ENCODER; + buttons_ctx->indev_drv.disp = buttons_cfg->disp; + buttons_ctx->indev_drv.read_cb = lvgl_port_navigation_buttons_read; + buttons_ctx->indev_drv.user_data = buttons_ctx; + buttons_ctx->indev_drv.long_press_repeat_time = 300; + indev = lv_indev_drv_register(&buttons_ctx->indev_drv); + +err: + if (ret != ESP_OK) { + for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) { + if (buttons_ctx->btn[i] != NULL) { + iot_button_delete(buttons_ctx->btn[i]); + } + } + + if (buttons_ctx != NULL) { + free(buttons_ctx); + } + } + + return indev; +} + +esp_err_t lvgl_port_remove_navigation_buttons(lv_indev_t *buttons) +{ + assert(buttons); + lv_indev_drv_t *indev_drv = buttons->driver; + assert(indev_drv); + lvgl_port_nav_btns_ctx_t *buttons_ctx = (lvgl_port_nav_btns_ctx_t *)indev_drv->user_data; + + /* Remove input device driver */ + lv_indev_delete(buttons); + + if (buttons_ctx) { + free(buttons_ctx); + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static void lvgl_port_navigation_buttons_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) +{ + static uint32_t last_key = 0; + assert(indev_drv); + lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *)indev_drv->user_data; + assert(ctx); + + /* Buttons */ + if (ctx->btn_prev) { + data->key = LV_KEY_LEFT; + last_key = LV_KEY_LEFT; + data->state = LV_INDEV_STATE_PRESSED; + } else if (ctx->btn_next) { + data->key = LV_KEY_RIGHT; + last_key = LV_KEY_RIGHT; + data->state = LV_INDEV_STATE_PRESSED; + } else if (ctx->btn_enter) { + data->key = LV_KEY_ENTER; + last_key = LV_KEY_ENTER; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->key = last_key; + data->state = LV_INDEV_STATE_RELEASED; + } +} + +static void lvgl_port_btn_down_handler(void *arg, void *arg2) +{ + lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2; + button_handle_t button = (button_handle_t)arg; + if (ctx && button) { + /* PREV */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) { + ctx->btn_prev = true; + } + /* NEXT */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) { + ctx->btn_next = true; + } + /* ENTER */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) { + ctx->btn_enter = true; + } + } +} + +static void lvgl_port_btn_up_handler(void *arg, void *arg2) +{ + lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2; + button_handle_t button = (button_handle_t)arg; + if (ctx && button) { + /* PREV */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) { + ctx->btn_prev = false; + } + /* NEXT */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) { + ctx->btn_next = false; + } + /* ENTER */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) { + ctx->btn_enter = false; + } + } +} diff --git a/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_disp.c b/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_disp.c new file mode 100644 index 00000000..08bedb1c --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_disp.c @@ -0,0 +1,603 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "esp_idf_version.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lvgl_port.h" +#include "esp_lvgl_port_priv.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#include "esp_lcd_panel_rgb.h" +#endif + +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) +#include "esp_lcd_mipi_dsi.h" +#endif + +#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 4)) || (ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(5, 0, 0)) +#define LVGL_PORT_HANDLE_FLUSH_READY 0 +#else +#define LVGL_PORT_HANDLE_FLUSH_READY 1 +#endif + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + lvgl_port_disp_type_t disp_type; /* Display type */ + esp_lcd_panel_io_handle_t io_handle; /* LCD panel IO handle */ + esp_lcd_panel_handle_t panel_handle; /* LCD panel handle */ + esp_lcd_panel_handle_t control_handle; /* LCD panel control handle */ + lvgl_port_rotation_cfg_t rotation; /* Default values of the screen rotation */ + lv_disp_drv_t disp_drv; /* LVGL display driver */ + lv_color_t *trans_buf; /* Buffer send to driver */ + uint32_t trans_size; /* Maximum size for one transport */ + SemaphoreHandle_t trans_sem; /* Idle transfer mutex */ + lvgl_port_rounder_cb_t rounder_cb; /* Rounder callback for display area */ +} lvgl_port_display_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ +static lv_disp_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp_cfg, + const lvgl_port_disp_priv_cfg_t *priv_cfg); +static lvgl_port_display_ctx_t *lvgl_port_get_display_ctx(lv_disp_t *disp); +#if LVGL_PORT_HANDLE_FLUSH_READY +static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, + void *user_ctx); +#if (CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) +static bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, + const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx); +#endif +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) +static bool lvgl_port_flush_dpi_panel_ready_callback(esp_lcd_panel_handle_t panel_io, + esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx); +static bool lvgl_port_flush_dpi_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, + esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx); +#endif +#endif +static void lvgl_port_flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map); +static void lvgl_port_rounder_callback(lv_disp_drv_t *drv, lv_area_t *area); +static void lvgl_port_update_callback(lv_disp_drv_t *drv); +static void lvgl_port_pix_monochrome_callback(lv_disp_drv_t *drv, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, + lv_coord_t y, lv_color_t color, lv_opa_t opa); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_disp_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg) +{ + lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, NULL); + + if (disp != NULL) { + lvgl_port_display_ctx_t *disp_ctx = lvgl_port_get_display_ctx(disp); + /* Set display type */ + disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_OTHER; + + assert(disp_ctx->io_handle != NULL); + +#if LVGL_PORT_HANDLE_FLUSH_READY + const esp_lcd_panel_io_callbacks_t cbs = { + .on_color_trans_done = lvgl_port_flush_io_ready_callback, + }; + /* Register done callback */ + esp_lcd_panel_io_register_event_callbacks(disp_ctx->io_handle, &cbs, &disp_ctx->disp_drv); +#endif + } + + return disp; +} + +lv_display_t *lvgl_port_add_disp_dsi(const lvgl_port_display_cfg_t *disp_cfg, + const lvgl_port_display_dsi_cfg_t *dsi_cfg) +{ + assert(dsi_cfg != NULL); + const lvgl_port_disp_priv_cfg_t priv_cfg = { + .avoid_tearing = dsi_cfg->flags.avoid_tearing, + }; + lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, &priv_cfg); + + if (disp != NULL) { + lvgl_port_display_ctx_t *disp_ctx = lvgl_port_get_display_ctx(disp); + /* Set display type */ + disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_DSI; + +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) + esp_lcd_dpi_panel_event_callbacks_t cbs = {0}; + if (dsi_cfg->flags.avoid_tearing) { + cbs.on_refresh_done = lvgl_port_flush_dpi_vsync_ready_callback; + } else { + cbs.on_color_trans_done = lvgl_port_flush_dpi_panel_ready_callback; + } + /* Register done callback */ + esp_lcd_dpi_panel_register_event_callbacks(disp_ctx->panel_handle, &cbs, &disp_ctx->disp_drv); +#else + ESP_RETURN_ON_FALSE(false, NULL, TAG, "MIPI-DSI is supported only on ESP32P4 and from IDF 5.3!"); +#endif + } + + return disp; +} + +lv_display_t *lvgl_port_add_disp_rgb(const lvgl_port_display_cfg_t *disp_cfg, + const lvgl_port_display_rgb_cfg_t *rgb_cfg) +{ + assert(rgb_cfg != NULL); + const lvgl_port_disp_priv_cfg_t priv_cfg = { + .avoid_tearing = rgb_cfg->flags.avoid_tearing, + }; + lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, &priv_cfg); + + if (disp != NULL) { + lvgl_port_display_ctx_t *disp_ctx = lvgl_port_get_display_ctx(disp); + /* Set display type */ + disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_RGB; + +#if (CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) + /* Register done callback */ + const esp_lcd_rgb_panel_event_callbacks_t vsync_cbs = { + .on_vsync = lvgl_port_flush_rgb_vsync_ready_callback, + }; + + const esp_lcd_rgb_panel_event_callbacks_t bb_cbs = { +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + .on_frame_buf_complete = lvgl_port_flush_rgb_vsync_ready_callback, +#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2) + .on_bounce_frame_finish = lvgl_port_flush_rgb_vsync_ready_callback, +#endif + }; + + if (rgb_cfg->flags.bb_mode && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2))) { + ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &bb_cbs, &disp_ctx->disp_drv)); + } else { + ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &vsync_cbs, &disp_ctx->disp_drv)); + } +#else + ESP_RETURN_ON_FALSE(false, NULL, TAG, "RGB is supported only on ESP32S3 and from IDF 5.0!"); +#endif + } + + return disp; +} + +esp_err_t lvgl_port_remove_disp(lv_disp_t *disp) +{ + assert(disp); + lv_disp_drv_t *disp_drv = disp->driver; + assert(disp_drv); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)disp_drv->user_data; + if (disp_ctx->trans_sem) { + vSemaphoreDelete(disp_ctx->trans_sem); + } + + lv_disp_remove(disp); + + if (disp_drv) { + if (disp_drv->draw_ctx) { + disp_drv->draw_ctx_deinit(disp_drv, disp_drv->draw_ctx); + lv_mem_free(disp_drv->draw_ctx); + disp_drv->draw_ctx = NULL; + } + if (disp_drv->draw_buf && disp_drv->draw_buf->buf1) { + free(disp_drv->draw_buf->buf1); + disp_drv->draw_buf->buf1 = NULL; + } + if (disp_drv->draw_buf && disp_drv->draw_buf->buf2) { + free(disp_drv->draw_buf->buf2); + disp_drv->draw_buf->buf2 = NULL; + } + if (disp_drv->draw_buf) { + free(disp_drv->draw_buf); + disp_drv->draw_buf = NULL; + } + } + + free(disp_ctx); + + return ESP_OK; +} + +void lvgl_port_flush_ready(lv_disp_t *disp) +{ + assert(disp); + assert(disp->driver); + lv_disp_flush_ready(disp->driver); +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static lvgl_port_display_ctx_t *lvgl_port_get_display_ctx(lv_disp_t *disp) +{ + assert(disp); + lv_disp_drv_t *disp_drv = disp->driver; + assert(disp_drv); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)disp_drv->user_data; + return disp_ctx; +} + +static lv_disp_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp_cfg, + const lvgl_port_disp_priv_cfg_t *priv_cfg) +{ + esp_err_t ret = ESP_OK; + lv_disp_t *disp = NULL; + lv_color_t *buf1 = NULL; + lv_color_t *buf2 = NULL; + lv_color_t *buf3 = NULL; + uint32_t buffer_size = 0; + SemaphoreHandle_t trans_sem = NULL; + assert(disp_cfg != NULL); + assert(disp_cfg->panel_handle != NULL); + assert(disp_cfg->buffer_size > 0); + assert(disp_cfg->hres > 0); + assert(disp_cfg->vres > 0); + + /* Display context */ + lvgl_port_display_ctx_t *disp_ctx = malloc(sizeof(lvgl_port_display_ctx_t)); + ESP_GOTO_ON_FALSE(disp_ctx, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for display context allocation!"); + memset(disp_ctx, 0, sizeof(lvgl_port_display_ctx_t)); + disp_ctx->io_handle = disp_cfg->io_handle; + disp_ctx->panel_handle = disp_cfg->panel_handle; + disp_ctx->control_handle = disp_cfg->control_handle; + disp_ctx->rotation.swap_xy = disp_cfg->rotation.swap_xy; + disp_ctx->rotation.mirror_x = disp_cfg->rotation.mirror_x; + disp_ctx->rotation.mirror_y = disp_cfg->rotation.mirror_y; + disp_ctx->trans_size = disp_cfg->trans_size; + + buffer_size = disp_cfg->buffer_size; + + /* Use RGB internal buffers for avoid tearing effect */ + if (priv_cfg && priv_cfg->avoid_tearing) { +#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + buffer_size = disp_cfg->hres * disp_cfg->vres; + ESP_GOTO_ON_ERROR(esp_lcd_rgb_panel_get_frame_buffer(disp_cfg->panel_handle, 2, (void *)&buf1, (void *)&buf2), err, TAG, + "Get RGB buffers failed"); +#elif CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + buffer_size = disp_cfg->hres * disp_cfg->vres; + ESP_GOTO_ON_ERROR(esp_lcd_dpi_panel_get_frame_buffer(disp_cfg->panel_handle, 2, (void *)&buf1, (void *)&buf2), err, TAG, + "Get RGB buffers failed"); +#endif + + trans_sem = xSemaphoreCreateCounting(1, 0); + ESP_GOTO_ON_FALSE(trans_sem, ESP_ERR_NO_MEM, err, TAG, "Failed to create transport counting Semaphore"); + disp_ctx->trans_sem = trans_sem; + } else { + uint32_t buff_caps = MALLOC_CAP_DEFAULT; + if (disp_cfg->flags.buff_dma && disp_cfg->flags.buff_spiram && (0 == disp_cfg->trans_size)) { + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "Alloc DMA capable buffer in SPIRAM is not supported!"); + } else if (disp_cfg->flags.buff_dma) { + buff_caps = MALLOC_CAP_DMA; + } else if (disp_cfg->flags.buff_spiram) { + buff_caps = MALLOC_CAP_SPIRAM; + } + + if (disp_cfg->trans_size) { + buf3 = heap_caps_malloc(disp_cfg->trans_size * sizeof(lv_color_t), MALLOC_CAP_DMA); + ESP_GOTO_ON_FALSE(buf3, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for buffer(transport) allocation!"); + disp_ctx->trans_buf = buf3; + + trans_sem = xSemaphoreCreateCounting(1, 0); + ESP_GOTO_ON_FALSE(trans_sem, ESP_ERR_NO_MEM, err, TAG, "Failed to create transport counting Semaphore"); + disp_ctx->trans_sem = trans_sem; + } + + /* alloc draw buffers used by LVGL */ + /* it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized */ + buf1 = heap_caps_malloc(buffer_size * sizeof(lv_color_t), buff_caps); + ESP_GOTO_ON_FALSE(buf1, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf1) allocation!"); + if (disp_cfg->double_buffer) { + buf2 = heap_caps_malloc(buffer_size * sizeof(lv_color_t), buff_caps); + ESP_GOTO_ON_FALSE(buf2, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf2) allocation!"); + } + } + + lv_disp_draw_buf_t *disp_buf = malloc(sizeof(lv_disp_draw_buf_t)); + ESP_GOTO_ON_FALSE(disp_buf, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL display buffer allocation!"); + + /* initialize LVGL draw buffers */ + lv_disp_draw_buf_init(disp_buf, buf1, buf2, buffer_size); + + ESP_LOGD(TAG, "Register display driver to LVGL"); + lv_disp_drv_init(&disp_ctx->disp_drv); + disp_ctx->disp_drv.hor_res = disp_cfg->hres; + disp_ctx->disp_drv.ver_res = disp_cfg->vres; + disp_ctx->disp_drv.flush_cb = lvgl_port_flush_callback; + disp_ctx->disp_drv.draw_buf = disp_buf; + disp_ctx->disp_drv.user_data = disp_ctx; + + /* Add rounder_cb */ + if (disp_cfg->rounder_cb) { + disp_ctx->rounder_cb = disp_cfg->rounder_cb; + disp_ctx->disp_drv.rounder_cb = lvgl_port_rounder_callback; + } + + disp_ctx->disp_drv.sw_rotate = disp_cfg->flags.sw_rotate; + if (disp_ctx->disp_drv.sw_rotate == false) { + disp_ctx->disp_drv.drv_update_cb = lvgl_port_update_callback; + } + + /* Monochrome display settings */ + if (disp_cfg->monochrome) { + /* When using monochromatic display, there must be used full bufer! */ + ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, + "Monochromatic display must using full buffer!"); + + disp_ctx->disp_drv.full_refresh = 1; + disp_ctx->disp_drv.set_px_cb = lvgl_port_pix_monochrome_callback; + } else if (disp_cfg->flags.direct_mode) { + /* When using direct_mode, there must be used full bufer! */ + ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, + "Direct mode must using full buffer!"); + + disp_ctx->disp_drv.direct_mode = 1; + } else if (disp_cfg->flags.full_refresh) { + /* When using full_refresh, there must be used full bufer! */ + ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, + "Full refresh must using full buffer!"); + + disp_ctx->disp_drv.full_refresh = 1; + } + + disp = lv_disp_drv_register(&disp_ctx->disp_drv); + + /* Apply rotation from initial display configuration */ + lvgl_port_update_callback(&disp_ctx->disp_drv); + +err: + if (ret != ESP_OK) { + if (buf1) { + free(buf1); + } + if (buf2) { + free(buf2); + } + if (buf3) { + free(buf3); + } + if (trans_sem) { + vSemaphoreDelete(trans_sem); + } + if (disp_ctx) { + free(disp_ctx); + } + } + + return disp; +} + +#if LVGL_PORT_HANDLE_FLUSH_READY +static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, + void *user_ctx) +{ + BaseType_t taskAwake = pdFALSE; + + lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)user_ctx; + assert(disp_drv != NULL); + lvgl_port_display_ctx_t *disp_ctx = disp_drv->user_data; + assert(disp_ctx != NULL); + lv_disp_flush_ready(disp_drv); + + if (disp_ctx->trans_size && disp_ctx->trans_sem) { + xSemaphoreGiveFromISR(disp_ctx->trans_sem, &taskAwake); + } + + return false; +} + +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) +static bool lvgl_port_flush_dpi_panel_ready_callback(esp_lcd_panel_handle_t panel_io, + esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) +{ + BaseType_t taskAwake = pdFALSE; + + lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)user_ctx; + assert(disp_drv != NULL); + lvgl_port_display_ctx_t *disp_ctx = disp_drv->user_data; + assert(disp_ctx != NULL); + lv_disp_flush_ready(disp_drv); + + if (disp_ctx->trans_size && disp_ctx->trans_sem) { + xSemaphoreGiveFromISR(disp_ctx->trans_sem, &taskAwake); + } + + return false; +} + +static bool lvgl_port_flush_dpi_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, + esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) +{ + BaseType_t need_yield = pdFALSE; + + lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)user_ctx; + assert(disp_drv != NULL); + lvgl_port_display_ctx_t *disp_ctx = disp_drv->user_data; + assert(disp_ctx != NULL); + + if (disp_ctx->trans_sem) { + xSemaphoreGiveFromISR(disp_ctx->trans_sem, &need_yield); + } + + return (need_yield == pdTRUE); +} +#endif + +#if (CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) +static bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, + const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx) +{ + BaseType_t need_yield = pdFALSE; + + lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)user_ctx; + assert(disp_drv != NULL); + lvgl_port_display_ctx_t *disp_ctx = disp_drv->user_data; + assert(disp_ctx != NULL); + + if (disp_ctx->trans_sem) { + xSemaphoreGiveFromISR(disp_ctx->trans_sem, &need_yield); + } + + return (need_yield == pdTRUE); +} +#endif +#endif + +static void lvgl_port_flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) +{ + assert(drv != NULL); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)drv->user_data; + assert(disp_ctx != NULL); + + int x_draw_start; + int x_draw_end; + int y_draw_start; + int y_draw_end; + + int y_start_tmp; + int y_end_tmp; + + int trans_count; + int trans_line; + int max_line; + + const int x_start = area->x1; + const int x_end = area->x2; + const int y_start = area->y1; + const int y_end = area->y2; + const int width = x_end - x_start + 1; + const int height = y_end - y_start + 1; + + lv_color_t *from = color_map; + lv_color_t *to = NULL; + + if (disp_ctx->trans_size == 0) { + if ((disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_RGB || disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_DSI) + && (drv->direct_mode || drv->full_refresh)) { + if (lv_disp_flush_is_last(drv)) { + /* If the interface is I80 or SPI, this step cannot be used for drawing. */ + esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, x_start, y_start, x_end + 1, y_end + 1, color_map); + /* Waiting for the last frame buffer to complete transmission */ + xSemaphoreTake(disp_ctx->trans_sem, 0); + xSemaphoreTake(disp_ctx->trans_sem, portMAX_DELAY); + } + } else { + esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, x_start, y_start, x_end + 1, y_end + 1, color_map); + } + + if (disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_RGB || (disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_DSI + && (drv->direct_mode || drv->full_refresh))) { + lv_disp_flush_ready(drv); + } + } else { + y_start_tmp = y_start; + max_line = ((disp_ctx->trans_size / width) > height) ? (height) : (disp_ctx->trans_size / width); + trans_count = height / max_line + (height % max_line ? (1) : (0)); + + for (int i = 0; i < trans_count; i++) { + trans_line = (y_end - y_start_tmp + 1) > max_line ? max_line : (y_end - y_start_tmp + 1); + y_end_tmp = (y_end - y_start_tmp + 1) > max_line ? (y_start_tmp + max_line - 1) : y_end; + + to = disp_ctx->trans_buf; + for (int y = 0; y < trans_line; y++) { + for (int x = 0; x < width; x++) { + *(to + y * (width) + x) = *(from + y * (width) + x); + } + } + x_draw_start = x_start; + x_draw_end = x_end; + y_draw_start = y_start_tmp; + y_draw_end = y_end_tmp; + esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, x_draw_start, y_draw_start, x_draw_end + 1, y_draw_end + 1, to); + + from += max_line * width; + y_start_tmp += max_line; + xSemaphoreTake(disp_ctx->trans_sem, portMAX_DELAY); + } + } +} + +static void lvgl_port_rounder_callback(lv_disp_drv_t *drv, lv_area_t *area) +{ + assert(drv != NULL); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)drv->user_data; + assert(disp_ctx != NULL); + + if (disp_ctx->rounder_cb) { + disp_ctx->rounder_cb(area); + } +} + +static void lvgl_port_update_callback(lv_disp_drv_t *drv) +{ + assert(drv); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)drv->user_data; + assert(disp_ctx != NULL); + esp_lcd_panel_handle_t control_handle = (disp_ctx->control_handle ? disp_ctx->control_handle : disp_ctx->panel_handle); + + /* Solve rotation screen and touch */ + switch (drv->rotated) { + case LV_DISP_ROT_NONE: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, disp_ctx->rotation.swap_xy); + esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); + break; + case LV_DISP_ROT_90: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, !disp_ctx->rotation.swap_xy); + if (disp_ctx->rotation.swap_xy) { + esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); + } else { + esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); + } + break; + case LV_DISP_ROT_180: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, disp_ctx->rotation.swap_xy); + esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); + break; + case LV_DISP_ROT_270: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, !disp_ctx->rotation.swap_xy); + if (disp_ctx->rotation.swap_xy) { + esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); + } else { + esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); + } + break; + } +} + +static void lvgl_port_pix_monochrome_callback(lv_disp_drv_t *drv, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, + lv_coord_t y, lv_color_t color, lv_opa_t opa) +{ + if (drv->rotated == LV_DISP_ROT_90 || drv->rotated == LV_DISP_ROT_270) { + lv_coord_t tmp_x = x; + x = y; + y = tmp_x; + } + + /* Write to the buffer as required for the display. + * It writes only 1-bit for monochrome displays mapped vertically.*/ + buf += drv->hor_res * (y >> 3) + x; + if (lv_color_to1(color)) { + (*buf) &= ~(1 << (y % 8)); + } else { + (*buf) |= (1 << (y % 8)); + } +} diff --git a/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_knob.c b/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_knob.c new file mode 100644 index 00000000..453fb642 --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_knob.c @@ -0,0 +1,218 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lvgl_port.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + knob_handle_t knob_handle; /* Encoder knob handlers */ + button_handle_t btn_handle; /* Encoder button handlers */ + lv_indev_drv_t indev_drv; /* LVGL input device driver */ + bool btn_enter; /* Encoder button enter state */ + int32_t diff; /* Encoder diff */ +} lvgl_port_encoder_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static void lvgl_port_encoder_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_encoder_btn_down_handler(void *arg, void *arg2); +static void lvgl_port_encoder_btn_up_handler(void *arg, void *arg2); +static void lvgl_port_encoder_left_handler(void *arg, void *arg2); +static void lvgl_port_encoder_right_handler(void *arg, void *arg2); +static int32_t lvgl_port_calculate_diff(knob_handle_t knob, knob_event_t event); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_encoder(const lvgl_port_encoder_cfg_t *encoder_cfg) +{ + esp_err_t ret = ESP_OK; + lv_indev_t *indev = NULL; + assert(encoder_cfg != NULL); + assert(encoder_cfg->disp != NULL); + + /* Encoder context */ + lvgl_port_encoder_ctx_t *encoder_ctx = malloc(sizeof(lvgl_port_encoder_ctx_t)); + if (encoder_ctx == NULL) { + ESP_LOGE(TAG, "Not enough memory for encoder context allocation!"); + return NULL; + } + + /* Encoder_a/b */ + if (encoder_cfg->encoder_a_b != NULL) { + encoder_ctx->knob_handle = iot_knob_create(encoder_cfg->encoder_a_b); + ESP_GOTO_ON_FALSE(encoder_ctx->knob_handle, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for knob create!"); + } + + ESP_ERROR_CHECK(iot_knob_register_cb(encoder_ctx->knob_handle, KNOB_LEFT, lvgl_port_encoder_left_handler, encoder_ctx)); + ESP_ERROR_CHECK(iot_knob_register_cb(encoder_ctx->knob_handle, KNOB_RIGHT, lvgl_port_encoder_right_handler, + encoder_ctx)); + + /* Encoder Enter */ + if (encoder_cfg->encoder_enter != NULL) { +#if BUTTON_VER_MAJOR < 4 + encoder_ctx->btn_handle = iot_button_create(encoder_cfg->encoder_enter); + ESP_GOTO_ON_FALSE(encoder_ctx->btn_handle, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!"); +#else + ESP_GOTO_ON_FALSE(encoder_cfg->encoder_enter, ESP_ERR_INVALID_ARG, err, TAG, "Invalid button handler!"); + encoder_ctx->btn_handle = encoder_cfg->encoder_enter; +#endif +#if BUTTON_VER_MAJOR < 4 + ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_DOWN, lvgl_port_encoder_btn_down_handler, + encoder_ctx)); + ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_UP, lvgl_port_encoder_btn_up_handler, + encoder_ctx)); +#else + ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_DOWN, NULL, + lvgl_port_encoder_btn_down_handler, encoder_ctx)); + ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_UP, NULL, lvgl_port_encoder_btn_up_handler, + encoder_ctx)); +#endif + } + + encoder_ctx->btn_enter = false; + encoder_ctx->diff = 0; + + /* Register a encoder input device */ + lv_indev_drv_init(&encoder_ctx->indev_drv); + encoder_ctx->indev_drv.type = LV_INDEV_TYPE_ENCODER; + encoder_ctx->indev_drv.disp = encoder_cfg->disp; + encoder_ctx->indev_drv.read_cb = lvgl_port_encoder_read; + encoder_ctx->indev_drv.user_data = encoder_ctx; + indev = lv_indev_drv_register(&encoder_ctx->indev_drv); + +err: + if (ret != ESP_OK) { + if (encoder_ctx->knob_handle != NULL) { + iot_knob_delete(encoder_ctx->knob_handle); + } + + if (encoder_ctx->btn_handle != NULL) { + iot_button_delete(encoder_ctx->btn_handle); + } + + if (encoder_ctx != NULL) { + free(encoder_ctx); + } + } + return indev; +} + +esp_err_t lvgl_port_remove_encoder(lv_indev_t *encoder) +{ + assert(encoder); + lv_indev_drv_t *indev_drv = encoder->driver; + assert(indev_drv); + lvgl_port_encoder_ctx_t *encoder_ctx = (lvgl_port_encoder_ctx_t *)indev_drv->user_data; + + if (encoder_ctx->knob_handle != NULL) { + iot_knob_delete(encoder_ctx->knob_handle); + } + + if (encoder_ctx->btn_handle != NULL) { + iot_button_delete(encoder_ctx->btn_handle); + } + + if (encoder_ctx != NULL) { + free(encoder_ctx); + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static void lvgl_port_encoder_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) +{ + assert(indev_drv); + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *)indev_drv->user_data; + assert(ctx); + + data->enc_diff = ctx->diff; + data->state = (true == ctx->btn_enter) ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; + ctx->diff = 0; +} + +static void lvgl_port_encoder_btn_down_handler(void *arg, void *arg2) +{ + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2; + button_handle_t button = (button_handle_t)arg; + if (ctx && button) { + /* ENTER */ + if (button == ctx->btn_handle) { + ctx->btn_enter = true; + } + } +} + +static void lvgl_port_encoder_btn_up_handler(void *arg, void *arg2) +{ + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2; + button_handle_t button = (button_handle_t)arg; + if (ctx && button) { + /* ENTER */ + if (button == ctx->btn_handle) { + ctx->btn_enter = false; + } + } +} + +static void lvgl_port_encoder_left_handler(void *arg, void *arg2) +{ + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2; + knob_handle_t knob = (knob_handle_t)arg; + if (ctx && knob) { + /* LEFT */ + if (knob == ctx->knob_handle) { + int32_t diff = lvgl_port_calculate_diff(knob, KNOB_LEFT); + ctx->diff = (ctx->diff > 0) ? diff : ctx->diff + diff; + } + } +} + +static void lvgl_port_encoder_right_handler(void *arg, void *arg2) +{ + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2; + knob_handle_t knob = (knob_handle_t)arg; + if (ctx && knob) { + /* RIGHT */ + if (knob == ctx->knob_handle) { + int32_t diff = lvgl_port_calculate_diff(knob, KNOB_RIGHT); + ctx->diff = (ctx->diff < 0) ? diff : ctx->diff + diff; + } + } +} + +static int32_t lvgl_port_calculate_diff(knob_handle_t knob, knob_event_t event) +{ + static int32_t last_v = 0; + + int32_t diff = 0; + int32_t invd = iot_knob_get_count_value(knob); + + if (last_v ^ invd) { + + diff = (int32_t)((uint32_t)invd - (uint32_t)last_v); + diff += (event == KNOB_RIGHT && invd < last_v) ? CONFIG_KNOB_HIGH_LIMIT : + (event == KNOB_LEFT && invd > last_v) ? CONFIG_KNOB_LOW_LIMIT : 0; + last_v = invd; + } + + return diff; +} diff --git a/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_touch.c b/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_touch.c new file mode 100644 index 00000000..a127bd3d --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_touch.c @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lcd_touch.h" +#include "esp_lvgl_port.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + esp_lcd_touch_handle_t handle; /* LCD touch IO handle */ + lv_indev_drv_t indev_drv; /* LVGL input device driver */ + struct { + float x; + float y; + } scale; /* Touch scale */ +} lvgl_port_touch_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static void lvgl_port_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg) +{ + assert(touch_cfg != NULL); + assert(touch_cfg->disp != NULL); + assert(touch_cfg->handle != NULL); + + /* Touch context */ + lvgl_port_touch_ctx_t *touch_ctx = malloc(sizeof(lvgl_port_touch_ctx_t)); + if (touch_ctx == NULL) { + ESP_LOGE(TAG, "Not enough memory for touch context allocation!"); + return NULL; + } + touch_ctx->handle = touch_cfg->handle; + touch_ctx->scale.x = (touch_cfg->scale.x ? touch_cfg->scale.x : 1); + touch_ctx->scale.y = (touch_cfg->scale.y ? touch_cfg->scale.y : 1); + + /* Register a touchpad input device */ + lv_indev_drv_init(&touch_ctx->indev_drv); + touch_ctx->indev_drv.type = LV_INDEV_TYPE_POINTER; + touch_ctx->indev_drv.disp = touch_cfg->disp; + touch_ctx->indev_drv.read_cb = lvgl_port_touchpad_read; + touch_ctx->indev_drv.user_data = touch_ctx; + return lv_indev_drv_register(&touch_ctx->indev_drv); +} + +esp_err_t lvgl_port_remove_touch(lv_indev_t *touch) +{ + assert(touch); + lv_indev_drv_t *indev_drv = touch->driver; + assert(indev_drv); + lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *)indev_drv->user_data; + + /* Remove input device driver */ + lv_indev_delete(touch); + + if (touch_ctx) { + free(touch_ctx); + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static void lvgl_port_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) +{ + assert(indev_drv); + lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *)indev_drv->user_data; + assert(touch_ctx->handle); + + uint16_t touchpad_x[1] = {0}; + uint16_t touchpad_y[1] = {0}; + uint8_t touchpad_cnt = 0; + + /* Read data from touch controller into memory */ + esp_lcd_touch_read_data(touch_ctx->handle); + + /* Read data from touch controller */ + bool touchpad_pressed = esp_lcd_touch_get_coordinates(touch_ctx->handle, touchpad_x, touchpad_y, NULL, &touchpad_cnt, + 1); + + if (touchpad_pressed && touchpad_cnt > 0) { + data->point.x = touch_ctx->scale.x * touchpad_x[0]; + data->point.y = touch_ctx->scale.y * touchpad_y[0]; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } +} diff --git a/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_usbhid.c b/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_usbhid.c new file mode 100644 index 00000000..14f77dc2 --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl8/esp_lvgl_port_usbhid.c @@ -0,0 +1,472 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lvgl_port.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#include "usb/hid_host.h" +#include "usb/hid_usage_keyboard.h" +#include "usb/hid_usage_mouse.h" + +/* LVGL image of cursor */ +LV_IMG_DECLARE(img_cursor) + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + QueueHandle_t queue; /* USB HID queue */ + TaskHandle_t task; /* USB HID task */ + bool running; /* USB HID task running */ + struct { + lv_indev_drv_t drv; /* LVGL mouse input device driver */ + uint8_t sensitivity; /* Mouse sensitivity (cannot be zero) */ + int16_t x; /* Mouse X coordinate */ + int16_t y; /* Mouse Y coordinate */ + bool left_button; /* Mouse left button state */ + } mouse; + struct { + lv_indev_drv_t drv; /* LVGL keyboard input device driver */ + uint32_t last_key; + bool pressed; + } kb; +} lvgl_port_usb_hid_ctx_t; + +typedef struct { + hid_host_device_handle_t hid_device_handle; + hid_host_driver_event_t event; + void *arg; +} lvgl_port_usb_hid_event_t; + +/******************************************************************************* +* Local variables +*******************************************************************************/ + +static lvgl_port_usb_hid_ctx_t lvgl_hid_ctx; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static lvgl_port_usb_hid_ctx_t *lvgl_port_hid_init(void); +static void lvgl_port_usb_hid_task(void *arg); +static void lvgl_port_usb_hid_read_mouse(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_usb_hid_read_kb(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_usb_hid_callback(hid_host_device_handle_t hid_device_handle, const hid_host_driver_event_t event, + void *arg); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_usb_hid_mouse_input(const lvgl_port_hid_mouse_cfg_t *mouse_cfg) +{ + lv_indev_t *indev; + assert(mouse_cfg); + assert(mouse_cfg->disp); + assert(mouse_cfg->disp->driver); + + /* Initialize USB HID */ + lvgl_port_usb_hid_ctx_t *hid_ctx = lvgl_port_hid_init(); + if (hid_ctx == NULL) { + return NULL; + } + + /* Mouse sensitivity cannot be zero */ + hid_ctx->mouse.sensitivity = (mouse_cfg->sensitivity == 0 ? 1 : mouse_cfg->sensitivity); + /* Default coordinates to screen center */ + hid_ctx->mouse.x = (mouse_cfg->disp->driver->hor_res * hid_ctx->mouse.sensitivity) / 2; + hid_ctx->mouse.y = (mouse_cfg->disp->driver->ver_res * hid_ctx->mouse.sensitivity) / 2; + + /* Register a mouse input device */ + lv_indev_drv_init(&hid_ctx->mouse.drv); + hid_ctx->mouse.drv.type = LV_INDEV_TYPE_POINTER; + hid_ctx->mouse.drv.disp = mouse_cfg->disp; + hid_ctx->mouse.drv.read_cb = lvgl_port_usb_hid_read_mouse; + hid_ctx->mouse.drv.user_data = hid_ctx; + indev = lv_indev_drv_register(&hid_ctx->mouse.drv); + + /* Set image of cursor */ + lv_obj_t *cursor = mouse_cfg->cursor_img; + if (cursor == NULL) { + cursor = lv_img_create(lv_scr_act()); + lv_img_set_src(cursor, &img_cursor); + } + lv_indev_set_cursor(indev, cursor); + + return indev; +} + +lv_indev_t *lvgl_port_add_usb_hid_keyboard_input(const lvgl_port_hid_keyboard_cfg_t *keyboard_cfg) +{ + lv_indev_t *indev; + assert(keyboard_cfg); + assert(keyboard_cfg->disp); + + /* Initialize USB HID */ + lvgl_port_usb_hid_ctx_t *hid_ctx = lvgl_port_hid_init(); + if (hid_ctx == NULL) { + return NULL; + } + + /* Register a keyboard input device */ + lv_indev_drv_init(&hid_ctx->kb.drv); + hid_ctx->kb.drv.type = LV_INDEV_TYPE_KEYPAD; + hid_ctx->kb.drv.disp = keyboard_cfg->disp; + hid_ctx->kb.drv.read_cb = lvgl_port_usb_hid_read_kb; + hid_ctx->kb.drv.user_data = hid_ctx; + indev = lv_indev_drv_register(&hid_ctx->kb.drv); + + return indev; +} + +esp_err_t lvgl_port_remove_usb_hid_input(lv_indev_t *hid) +{ + assert(hid); + lv_indev_drv_t *indev_drv = hid->driver; + assert(indev_drv); + lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)indev_drv->user_data; + + /* Remove input device driver */ + lv_indev_delete(hid); + + /* If all hid input devices are removed, stop task and clean all */ + if (lvgl_hid_ctx.mouse.drv.disp == NULL && lvgl_hid_ctx.kb.drv.disp) { + hid_ctx->running = false; + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static lvgl_port_usb_hid_ctx_t *lvgl_port_hid_init(void) +{ + esp_err_t ret; + + /* USB HID is already initialized */ + if (lvgl_hid_ctx.task) { + return &lvgl_hid_ctx; + } + + /* USB HID host driver config */ + const hid_host_driver_config_t hid_host_driver_config = { + .create_background_task = true, + .task_priority = 5, + .stack_size = 4096, + .core_id = 0, + .callback = lvgl_port_usb_hid_callback, + .callback_arg = &lvgl_hid_ctx, + }; + + ret = hid_host_install(&hid_host_driver_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "USB HID install failed!"); + return NULL; + } + + lvgl_hid_ctx.queue = xQueueCreate(10, sizeof(lvgl_port_usb_hid_event_t)); + xTaskCreate(&lvgl_port_usb_hid_task, "hid_task", 4 * 1024, &lvgl_hid_ctx, 2, &lvgl_hid_ctx.task); + + return &lvgl_hid_ctx; +} + +static char usb_hid_get_keyboard_char(uint8_t key, uint8_t shift) +{ + char ret_key = 0; + + const uint8_t keycode2ascii [57][2] = { + {0, 0}, /* HID_KEY_NO_PRESS */ + {0, 0}, /* HID_KEY_ROLLOVER */ + {0, 0}, /* HID_KEY_POST_FAIL */ + {0, 0}, /* HID_KEY_ERROR_UNDEFINED */ + {'a', 'A'}, /* HID_KEY_A */ + {'b', 'B'}, /* HID_KEY_B */ + {'c', 'C'}, /* HID_KEY_C */ + {'d', 'D'}, /* HID_KEY_D */ + {'e', 'E'}, /* HID_KEY_E */ + {'f', 'F'}, /* HID_KEY_F */ + {'g', 'G'}, /* HID_KEY_G */ + {'h', 'H'}, /* HID_KEY_H */ + {'i', 'I'}, /* HID_KEY_I */ + {'j', 'J'}, /* HID_KEY_J */ + {'k', 'K'}, /* HID_KEY_K */ + {'l', 'L'}, /* HID_KEY_L */ + {'m', 'M'}, /* HID_KEY_M */ + {'n', 'N'}, /* HID_KEY_N */ + {'o', 'O'}, /* HID_KEY_O */ + {'p', 'P'}, /* HID_KEY_P */ + {'q', 'Q'}, /* HID_KEY_Q */ + {'r', 'R'}, /* HID_KEY_R */ + {'s', 'S'}, /* HID_KEY_S */ + {'t', 'T'}, /* HID_KEY_T */ + {'u', 'U'}, /* HID_KEY_U */ + {'v', 'V'}, /* HID_KEY_V */ + {'w', 'W'}, /* HID_KEY_W */ + {'x', 'X'}, /* HID_KEY_X */ + {'y', 'Y'}, /* HID_KEY_Y */ + {'z', 'Z'}, /* HID_KEY_Z */ + {'1', '!'}, /* HID_KEY_1 */ + {'2', '@'}, /* HID_KEY_2 */ + {'3', '#'}, /* HID_KEY_3 */ + {'4', '$'}, /* HID_KEY_4 */ + {'5', '%'}, /* HID_KEY_5 */ + {'6', '^'}, /* HID_KEY_6 */ + {'7', '&'}, /* HID_KEY_7 */ + {'8', '*'}, /* HID_KEY_8 */ + {'9', '('}, /* HID_KEY_9 */ + {'0', ')'}, /* HID_KEY_0 */ + {'\r', '\r'}, /* HID_KEY_ENTER */ + {0, 0}, /* HID_KEY_ESC */ + {'\b', 0}, /* HID_KEY_DEL */ + {0, 0}, /* HID_KEY_TAB */ + {' ', ' '}, /* HID_KEY_SPACE */ + {'-', '_'}, /* HID_KEY_MINUS */ + {'=', '+'}, /* HID_KEY_EQUAL */ + {'[', '{'}, /* HID_KEY_OPEN_BRACKET */ + {']', '}'}, /* HID_KEY_CLOSE_BRACKET */ + {'\\', '|'}, /* HID_KEY_BACK_SLASH */ + {'\\', '|'}, /* HID_KEY_SHARP */ // HOTFIX: for NonUS Keyboards repeat HID_KEY_BACK_SLASH + {';', ':'}, /* HID_KEY_COLON */ + {'\'', '"'}, /* HID_KEY_QUOTE */ + {'`', '~'}, /* HID_KEY_TILDE */ + {',', '<'}, /* HID_KEY_LESS */ + {'.', '>'}, /* HID_KEY_GREATER */ + {'/', '?'} /* HID_KEY_SLASH */ + }; + + if (shift > 1) { + shift = 1; + } + + if ((key >= HID_KEY_A) && (key <= HID_KEY_SLASH)) { + ret_key = keycode2ascii[key][shift]; + } + + return ret_key; +} + +static void lvgl_port_usb_hid_host_interface_callback(hid_host_device_handle_t hid_device_handle, + const hid_host_interface_event_t event, void *arg) +{ + hid_host_dev_params_t dev; + hid_host_device_get_params(hid_device_handle, &dev); + lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)arg; + uint8_t data[10]; + unsigned int data_length = 0; + + assert(hid_ctx != NULL); + + switch (event) { + case HID_HOST_INTERFACE_EVENT_INPUT_REPORT: + hid_host_device_get_raw_input_report_data(hid_device_handle, data, sizeof(data), &data_length); + if (dev.proto == HID_PROTOCOL_KEYBOARD) { + hid_keyboard_input_report_boot_t *keyboard = (hid_keyboard_input_report_boot_t *)data; + if (data_length < sizeof(hid_keyboard_input_report_boot_t)) { + return; + } + for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) { + if (keyboard->key[i] > HID_KEY_ERROR_UNDEFINED) { + char key = 0; + + /* LVGL special keys */ + if (keyboard->key[i] == HID_KEY_TAB) { + if ((keyboard->modifier.left_shift || keyboard->modifier.right_shift)) { + key = LV_KEY_PREV; + } else { + key = LV_KEY_NEXT; + } + } else if (keyboard->key[i] == HID_KEY_RIGHT) { + key = LV_KEY_RIGHT; + } else if (keyboard->key[i] == HID_KEY_LEFT) { + key = LV_KEY_LEFT; + } else if (keyboard->key[i] == HID_KEY_DOWN) { + key = LV_KEY_DOWN; + } else if (keyboard->key[i] == HID_KEY_UP) { + key = LV_KEY_UP; + } else if (keyboard->key[i] == HID_KEY_ENTER || keyboard->key[i] == HID_KEY_KEYPAD_ENTER) { + key = LV_KEY_ENTER; + } else if (keyboard->key[i] == HID_KEY_DELETE) { + key = LV_KEY_DEL; + } else if (keyboard->key[i] == HID_KEY_HOME) { + key = LV_KEY_HOME; + } else if (keyboard->key[i] == HID_KEY_END) { + key = LV_KEY_END; + } else { + /* Get ASCII char */ + key = usb_hid_get_keyboard_char(keyboard->key[i], (keyboard->modifier.left_shift || keyboard->modifier.right_shift)); + } + + if (key == 0) { + ESP_LOGI(TAG, "Not recognized key: %c (%d)", keyboard->key[i], keyboard->key[i]); + } + hid_ctx->kb.last_key = key; + hid_ctx->kb.pressed = true; + } + } + + } else if (dev.proto == HID_PROTOCOL_MOUSE) { + hid_mouse_input_report_boot_t *mouse = (hid_mouse_input_report_boot_t *)data; + if (data_length < sizeof(hid_mouse_input_report_boot_t)) { + break; + } + hid_ctx->mouse.left_button = mouse->buttons.button1; + hid_ctx->mouse.x += mouse->x_displacement; + hid_ctx->mouse.y += mouse->y_displacement; + } + break; + case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR: + break; + case HID_HOST_INTERFACE_EVENT_DISCONNECTED: + hid_host_device_close(hid_device_handle); + break; + default: + break; + } +} + +static void lvgl_port_usb_hid_task(void *arg) +{ + hid_host_dev_params_t dev; + lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)arg; + hid_host_device_handle_t hid_device_handle = NULL; + lvgl_port_usb_hid_event_t msg; + + assert(ctx); + + ctx->running = true; + + while (ctx->running) { + if (xQueueReceive(ctx->queue, &msg, pdMS_TO_TICKS(50))) { + hid_device_handle = msg.hid_device_handle; + hid_host_device_get_params(hid_device_handle, &dev); + + switch (msg.event) { + case HID_HOST_DRIVER_EVENT_CONNECTED: + /* Handle mouse or keyboard */ + if (dev.proto == HID_PROTOCOL_KEYBOARD || dev.proto == HID_PROTOCOL_MOUSE) { + const hid_host_device_config_t dev_config = { + .callback = lvgl_port_usb_hid_host_interface_callback, + .callback_arg = ctx + }; + + ESP_ERROR_CHECK( hid_host_device_open(hid_device_handle, &dev_config) ); + ESP_ERROR_CHECK( hid_class_request_set_idle(hid_device_handle, 0, 0) ); + ESP_ERROR_CHECK( hid_class_request_set_protocol(hid_device_handle, HID_REPORT_PROTOCOL_BOOT) ); + ESP_ERROR_CHECK( hid_host_device_start(hid_device_handle) ); + } + break; + default: + break; + } + } + } + + xQueueReset(ctx->queue); + vQueueDelete(ctx->queue); + + hid_host_uninstall(); + + memset(&lvgl_hid_ctx, 0, sizeof(lvgl_port_usb_hid_ctx_t)); + + vTaskDelete(NULL); +} + +static void lvgl_port_usb_hid_read_mouse(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) +{ + int16_t width = 0; + int16_t height = 0; + assert(indev_drv); + lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)indev_drv->user_data; + assert(ctx); + + if (indev_drv->disp->driver->rotated == LV_DISP_ROT_NONE || indev_drv->disp->driver->rotated == LV_DISP_ROT_180) { + width = indev_drv->disp->driver->hor_res; + height = indev_drv->disp->driver->ver_res; + } else { + width = indev_drv->disp->driver->ver_res; + height = indev_drv->disp->driver->hor_res; + } + + /* Screen borders */ + if (ctx->mouse.x < 0) { + ctx->mouse.x = 0; + } else if (ctx->mouse.x > width * ctx->mouse.sensitivity) { + ctx->mouse.x = width * ctx->mouse.sensitivity; + } + if (ctx->mouse.y < 0) { + ctx->mouse.y = 0; + } else if (ctx->mouse.y > height * ctx->mouse.sensitivity) { + ctx->mouse.y = height * ctx->mouse.sensitivity; + } + + /* Get coordinates by rotation with sensitivity */ + switch (indev_drv->disp->driver->rotated) { + case LV_DISP_ROT_NONE: + data->point.x = ctx->mouse.x / ctx->mouse.sensitivity; + data->point.y = ctx->mouse.y / ctx->mouse.sensitivity; + break; + case LV_DISP_ROT_90: + data->point.y = width - ctx->mouse.x / ctx->mouse.sensitivity; + data->point.x = ctx->mouse.y / ctx->mouse.sensitivity; + break; + case LV_DISP_ROT_180: + data->point.x = width - ctx->mouse.x / ctx->mouse.sensitivity; + data->point.y = height - ctx->mouse.y / ctx->mouse.sensitivity; + break; + case LV_DISP_ROT_270: + data->point.y = ctx->mouse.x / ctx->mouse.sensitivity; + data->point.x = height - ctx->mouse.y / ctx->mouse.sensitivity; + break; + } + + if (ctx->mouse.left_button) { + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } +} + +static void lvgl_port_usb_hid_read_kb(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) +{ + assert(indev_drv); + lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)indev_drv->user_data; + assert(ctx); + + data->key = ctx->kb.last_key; + if (ctx->kb.pressed) { + data->state = LV_INDEV_STATE_PRESSED; + ctx->kb.pressed = false; + } else { + data->state = LV_INDEV_STATE_RELEASED; + ctx->kb.last_key = 0; + } +} + +static void lvgl_port_usb_hid_callback(hid_host_device_handle_t hid_device_handle, const hid_host_driver_event_t event, + void *arg) +{ + lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)arg; + + const lvgl_port_usb_hid_event_t msg = { + .hid_device_handle = hid_device_handle, + .event = event, + .arg = arg + }; + + xQueueSend(hid_ctx->queue, &msg, 0); +} diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port.c b/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port.c new file mode 100644 index 00000000..bdcf1a5f --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port.c @@ -0,0 +1,327 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/event_groups.h" +#include "freertos/idf_additions.h" +#include "esp_lvgl_port.h" +#include "esp_lvgl_port_priv.h" +#include "lvgl.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct lvgl_port_ctx_s { + TaskHandle_t lvgl_task; + SemaphoreHandle_t lvgl_mux; + SemaphoreHandle_t timer_mux; + EventGroupHandle_t lvgl_events; + esp_timer_handle_t tick_timer; + bool running; + int task_max_sleep_ms; + int timer_period_ms; +} lvgl_port_ctx_t; + +/******************************************************************************* +* Local variables +*******************************************************************************/ +static lvgl_port_ctx_t lvgl_port_ctx; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ +static void lvgl_port_task(void *arg); +static esp_err_t lvgl_port_tick_init(void); +static void lvgl_port_task_deinit(void); +static inline bool indev_matches_event(const lv_indev_t *indev, EventBits_t event); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(cfg->task_affinity < (configNUM_CORES), ESP_ERR_INVALID_ARG, err, TAG, + "Bad core number for task! Maximum core number is %d", (configNUM_CORES - 1)); + + memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx)); + + /* Tick init */ + lvgl_port_ctx.timer_period_ms = cfg->timer_period_ms; + /* Create task */ + lvgl_port_ctx.task_max_sleep_ms = cfg->task_max_sleep_ms; + if (lvgl_port_ctx.task_max_sleep_ms == 0) { + lvgl_port_ctx.task_max_sleep_ms = 500; + } + /* Timer semaphore */ + lvgl_port_ctx.timer_mux = xSemaphoreCreateMutex(); + ESP_GOTO_ON_FALSE(lvgl_port_ctx.timer_mux, ESP_ERR_NO_MEM, err, TAG, "Create timer mutex fail!"); + /* LVGL semaphore */ + lvgl_port_ctx.lvgl_mux = xSemaphoreCreateRecursiveMutex(); + ESP_GOTO_ON_FALSE(lvgl_port_ctx.lvgl_mux, ESP_ERR_NO_MEM, err, TAG, "Create LVGL mutex fail!"); + /* Task queue */ + lvgl_port_ctx.lvgl_events = xEventGroupCreate(); + ESP_GOTO_ON_FALSE(lvgl_port_ctx.lvgl_events, ESP_ERR_NO_MEM, err, TAG, "Create LVGL Event Group fail!"); + + BaseType_t res; + const uint32_t caps = cfg->task_stack_caps ? cfg->task_stack_caps : MALLOC_CAP_INTERNAL | + MALLOC_CAP_DEFAULT; // caps cannot be zero + if (cfg->task_affinity < 0) { + res = xTaskCreateWithCaps(lvgl_port_task, "taskLVGL", cfg->task_stack, xTaskGetCurrentTaskHandle(), cfg->task_priority, + &lvgl_port_ctx.lvgl_task, caps); + } else { + res = xTaskCreatePinnedToCoreWithCaps(lvgl_port_task, "taskLVGL", cfg->task_stack, xTaskGetCurrentTaskHandle(), + cfg->task_priority, &lvgl_port_ctx.lvgl_task, cfg->task_affinity, caps); + } + ESP_GOTO_ON_FALSE(res == pdPASS, ESP_FAIL, err, TAG, "Create LVGL task fail!"); + + // Wait until taskLVGL starts + if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(5000)) == 0) { + ret = ESP_ERR_TIMEOUT; + } + +err: + if (ret != ESP_OK) { + lvgl_port_deinit(); + } + + return ret; +} + +esp_err_t lvgl_port_resume(void) +{ + esp_err_t ret = ESP_ERR_INVALID_STATE; + + if (lvgl_port_ctx.tick_timer != NULL) { + lv_timer_enable(true); + ret = esp_timer_start_periodic(lvgl_port_ctx.tick_timer, lvgl_port_ctx.timer_period_ms * 1000); + } + + return ret; +} + +esp_err_t lvgl_port_stop(void) +{ + esp_err_t ret = ESP_ERR_INVALID_STATE; + + if (lvgl_port_ctx.tick_timer != NULL) { + lv_timer_enable(false); + ret = esp_timer_stop(lvgl_port_ctx.tick_timer); + } + + return ret; +} + +esp_err_t lvgl_port_deinit(void) +{ + /* Stop running task */ + if (lvgl_port_ctx.running) { + lvgl_port_ctx.running = false; + } + + return ESP_OK; +} + +bool lvgl_port_lock(uint32_t timeout_ms) +{ + assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first"); + + const TickType_t timeout_ticks = (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + return xSemaphoreTakeRecursive(lvgl_port_ctx.lvgl_mux, timeout_ticks) == pdTRUE; +} + +void lvgl_port_unlock(void) +{ + assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first"); + xSemaphoreGiveRecursive(lvgl_port_ctx.lvgl_mux); +} + +esp_err_t lvgl_port_task_wake(lvgl_port_event_type_t event, void *param) +{ + EventBits_t bits = 0; + if (!lvgl_port_ctx.lvgl_events) { + return ESP_ERR_INVALID_STATE; + } + + /* Get unprocessed bits */ + if (xPortInIsrContext() == pdTRUE) { + bits = xEventGroupGetBitsFromISR(lvgl_port_ctx.lvgl_events); + } else { + bits = xEventGroupGetBits(lvgl_port_ctx.lvgl_events); + } + + /* Set event */ + bits |= event; + + /* Save */ + if (xPortInIsrContext() == pdTRUE) { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xEventGroupSetBitsFromISR(lvgl_port_ctx.lvgl_events, bits, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken) { + portYIELD_FROM_ISR( ); + } + } else { + xEventGroupSetBits(lvgl_port_ctx.lvgl_events, bits); + } + + return ESP_OK; +} + +IRAM_ATTR bool lvgl_port_task_notify(uint32_t value) +{ + BaseType_t need_yield = pdFALSE; + + // Notify LVGL task + if (xPortInIsrContext() == pdTRUE) { + xTaskNotifyFromISR(lvgl_port_ctx.lvgl_task, value, eNoAction, &need_yield); + } else { + xTaskNotify(lvgl_port_ctx.lvgl_task, value, eNoAction); + } + + return (need_yield == pdTRUE); +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static void lvgl_port_task(void *arg) +{ + TaskHandle_t task_to_notify = (TaskHandle_t)arg; + EventBits_t events = 0; + uint32_t task_delay_ms = 0; + lv_indev_t *indev = NULL; + + /* LVGL init */ + lv_init(); + /* LVGL is initialized, notify lvgl_port_init() function about it */ + xTaskNotifyGive(task_to_notify); + /* Tick init */ + lvgl_port_tick_init(); + + ESP_LOGI(TAG, "Starting LVGL task"); + lvgl_port_ctx.running = true; + while (lvgl_port_ctx.running) { + /* Wait for queue or timeout (sleep task) */ + TickType_t wait = (pdMS_TO_TICKS(task_delay_ms) >= 1 ? pdMS_TO_TICKS(task_delay_ms) : 1); + events = xEventGroupWaitBits(lvgl_port_ctx.lvgl_events, 0xFF, pdTRUE, pdFALSE, wait); + + if (lv_display_get_default() && lvgl_port_lock(0)) { + + /* Call read input devices */ + if (events & LVGL_PORT_EVENT_TOUCH || events & LVGL_PORT_EVENT_ENCODER) { + xSemaphoreTake(lvgl_port_ctx.timer_mux, portMAX_DELAY); + indev = lv_indev_get_next(NULL); + while (indev != NULL) { + if (indev_matches_event(indev, events)) { + lv_indev_read(indev); + } + indev = lv_indev_get_next(indev); + } + xSemaphoreGive(lvgl_port_ctx.timer_mux); + } + + /* Handle LVGL */ + task_delay_ms = lv_timer_handler(); + lvgl_port_unlock(); + } else { + task_delay_ms = 1; /*Keep trying*/ + } + + if (task_delay_ms == LV_NO_TIMER_READY) { + task_delay_ms = lvgl_port_ctx.task_max_sleep_ms; + } + + /* Minimal dealy for the task. When there is too much events, it takes time for other tasks and interrupts. */ + vTaskDelay(1); + } + + ESP_LOGI(TAG, "Stopped LVGL task"); + + /* Deinit LVGL */ + lvgl_port_task_deinit(); + + /* Close task */ + vTaskDeleteWithCaps( NULL ); +} + +static void lvgl_port_task_deinit(void) +{ + /* Stop and delete timer */ + if (lvgl_port_ctx.tick_timer != NULL) { + esp_timer_stop(lvgl_port_ctx.tick_timer); + esp_timer_delete(lvgl_port_ctx.tick_timer); + lvgl_port_ctx.tick_timer = NULL; + } + + if (lvgl_port_ctx.timer_mux) { + vSemaphoreDelete(lvgl_port_ctx.timer_mux); + } + if (lvgl_port_ctx.lvgl_mux) { + vSemaphoreDelete(lvgl_port_ctx.lvgl_mux); + } + if (lvgl_port_ctx.lvgl_events) { + vEventGroupDelete(lvgl_port_ctx.lvgl_events); + } + memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx)); + + /* Deinitialize LVGL */ + lv_deinit(); +} + +static void lvgl_port_tick_increment(void *arg) +{ + xSemaphoreTake(lvgl_port_ctx.timer_mux, portMAX_DELAY); + /* Tell LVGL how many milliseconds have elapsed */ + lv_tick_inc(lvgl_port_ctx.timer_period_ms); + xSemaphoreGive(lvgl_port_ctx.timer_mux); +} + +static esp_err_t lvgl_port_tick_init(void) +{ + // Tick interface for LVGL (using esp_timer to generate 2ms periodic event) + const esp_timer_create_args_t lvgl_tick_timer_args = { + .callback = &lvgl_port_tick_increment, + .name = "LVGL tick", + }; + ESP_RETURN_ON_ERROR(esp_timer_create(&lvgl_tick_timer_args, &lvgl_port_ctx.tick_timer), TAG, + "Creating LVGL timer filed!"); + return esp_timer_start_periodic(lvgl_port_ctx.tick_timer, lvgl_port_ctx.timer_period_ms * 1000); +} + +static inline bool indev_matches_event(const lv_indev_t *indev, EventBits_t event) +{ + assert(indev != NULL); + + bool does_match = false; + + switch (lv_indev_get_type(indev)) { + case LV_INDEV_TYPE_KEYPAD: + case LV_INDEV_TYPE_POINTER: + does_match = event & LVGL_PORT_EVENT_TOUCH; + break; + case LV_INDEV_TYPE_ENCODER: + does_match = event & LVGL_PORT_EVENT_ENCODER; + break; + default: + does_match = false; + } + return does_match; +} diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_button.c b/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_button.c new file mode 100644 index 00000000..6eb111a2 --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_button.c @@ -0,0 +1,230 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lvgl_port.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef enum { + LVGL_PORT_NAV_BTN_PREV, + LVGL_PORT_NAV_BTN_NEXT, + LVGL_PORT_NAV_BTN_ENTER, + LVGL_PORT_NAV_BTN_CNT, +} lvgl_port_nav_btns_t; + +typedef struct { + button_handle_t btn[LVGL_PORT_NAV_BTN_CNT]; /* Button handlers */ + lv_indev_t *indev; /* LVGL input device driver */ + bool btn_prev; /* Button prev state */ + bool btn_next; /* Button next state */ + bool btn_enter; /* Button enter state */ +} lvgl_port_nav_btns_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static void lvgl_port_navigation_buttons_read(lv_indev_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_btn_down_handler(void *arg, void *arg2); +static void lvgl_port_btn_up_handler(void *arg, void *arg2); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_navigation_buttons(const lvgl_port_nav_btns_cfg_t *buttons_cfg) +{ + lv_indev_t *indev; + esp_err_t ret = ESP_OK; + assert(buttons_cfg != NULL); + assert(buttons_cfg->disp != NULL); + + /* Touch context */ + lvgl_port_nav_btns_ctx_t *buttons_ctx = malloc(sizeof(lvgl_port_nav_btns_ctx_t)); + if (buttons_ctx == NULL) { + ESP_LOGE(TAG, "Not enough memory for buttons context allocation!"); + return NULL; + } + +#if BUTTON_VER_MAJOR < 4 + /* Previous button */ + if (buttons_cfg->button_prev != NULL) { + buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV] = iot_button_create(buttons_cfg->button_prev); + ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV], ESP_ERR_NO_MEM, err, TAG, + "Not enough memory for button create!"); + } + + /* Next button */ + if (buttons_cfg->button_next != NULL) { + buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT] = iot_button_create(buttons_cfg->button_next); + ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT], ESP_ERR_NO_MEM, err, TAG, + "Not enough memory for button create!"); + } + + /* Enter button */ + if (buttons_cfg->button_enter != NULL) { + buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER] = iot_button_create(buttons_cfg->button_enter); + ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER], ESP_ERR_NO_MEM, err, TAG, + "Not enough memory for button create!"); + } +#else + ESP_GOTO_ON_FALSE(buttons_cfg->button_prev && buttons_cfg->button_next + && buttons_cfg->button_enter, ESP_ERR_INVALID_ARG, err, TAG, "Invalid some button handler!"); + + buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV] = buttons_cfg->button_prev; + buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT] = buttons_cfg->button_next; + buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER] = buttons_cfg->button_enter; +#endif + + /* Button handlers */ + for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) { +#if BUTTON_VER_MAJOR < 4 + ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_DOWN, lvgl_port_btn_down_handler, + buttons_ctx)); + ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_UP, lvgl_port_btn_up_handler, buttons_ctx)); +#else + ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_DOWN, NULL, lvgl_port_btn_down_handler, + buttons_ctx)); + ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_UP, NULL, lvgl_port_btn_up_handler, + buttons_ctx)); +#endif + } + + buttons_ctx->btn_prev = false; + buttons_ctx->btn_next = false; + buttons_ctx->btn_enter = false; + + lvgl_port_lock(0); + /* Register a touchpad input device */ + indev = lv_indev_create(); + lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER); + lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT); + lv_indev_set_read_cb(indev, lvgl_port_navigation_buttons_read); + lv_indev_set_disp(indev, buttons_cfg->disp); + lv_indev_set_driver_data(indev, buttons_ctx); + //buttons_ctx->indev->long_press_repeat_time = 300; + buttons_ctx->indev = indev; + lvgl_port_unlock(); + + return indev; + +err: + if (ret != ESP_OK) { + for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) { + if (buttons_ctx->btn[i] != NULL) { + iot_button_delete(buttons_ctx->btn[i]); + } + } + + if (buttons_ctx != NULL) { + free(buttons_ctx); + } + } + + return NULL; +} + +esp_err_t lvgl_port_remove_navigation_buttons(lv_indev_t *buttons) +{ + assert(buttons); + lvgl_port_nav_btns_ctx_t *buttons_ctx = (lvgl_port_nav_btns_ctx_t *)lv_indev_get_driver_data(buttons); + + lvgl_port_lock(0); + /* Remove input device driver */ + lv_indev_delete(buttons); + lvgl_port_unlock(); + + if (buttons_ctx) { + free(buttons_ctx); + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + + +static void lvgl_port_navigation_buttons_read(lv_indev_t *indev_drv, lv_indev_data_t *data) +{ + static uint32_t last_key = 0; + + assert(indev_drv); + lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *)lv_indev_get_driver_data(indev_drv); + assert(ctx); + + /* Buttons */ + if (ctx->btn_prev) { + data->key = LV_KEY_LEFT; + last_key = LV_KEY_LEFT; + data->state = LV_INDEV_STATE_PRESSED; + } else if (ctx->btn_next) { + data->key = LV_KEY_RIGHT; + last_key = LV_KEY_RIGHT; + data->state = LV_INDEV_STATE_PRESSED; + } else if (ctx->btn_enter) { + data->key = LV_KEY_ENTER; + last_key = LV_KEY_ENTER; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->key = last_key; + data->state = LV_INDEV_STATE_RELEASED; + } +} + +static void lvgl_port_btn_down_handler(void *arg, void *arg2) +{ + lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2; + button_handle_t button = (button_handle_t)arg; + if (ctx && button) { + /* PREV */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) { + ctx->btn_prev = true; + } + /* NEXT */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) { + ctx->btn_next = true; + } + /* ENTER */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) { + ctx->btn_enter = true; + } + } + + /* Wake LVGL task, if needed */ + lvgl_port_task_wake(LVGL_PORT_EVENT_ENCODER, ctx->indev); +} + +static void lvgl_port_btn_up_handler(void *arg, void *arg2) +{ + lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2; + button_handle_t button = (button_handle_t)arg; + if (ctx && button) { + /* PREV */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) { + ctx->btn_prev = false; + } + /* NEXT */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) { + ctx->btn_next = false; + } + /* ENTER */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) { + ctx->btn_enter = false; + } + } + + /* Wake LVGL task, if needed */ + lvgl_port_task_wake(LVGL_PORT_EVENT_ENCODER, ctx->indev); +} diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c b/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c new file mode 100644 index 00000000..6cb5d8ba --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c @@ -0,0 +1,812 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_heap_caps.h" +#include "esp_idf_version.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lvgl_port.h" +#include "esp_lvgl_port_priv.h" + +#define LVGL_PORT_PPA (CONFIG_LVGL_PORT_ENABLE_PPA) + +#if LVGL_PORT_PPA +#include "../common/ppa/lcd_ppa.h" +#endif + +#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#include "esp_lcd_panel_rgb.h" +#endif + +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) +#include "esp_lcd_mipi_dsi.h" +#endif + +#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 4)) || (ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(5, 0, 0)) +#define LVGL_PORT_HANDLE_FLUSH_READY 0 +#else +#define LVGL_PORT_HANDLE_FLUSH_READY 1 +#endif + +#ifndef CONFIG_LV_DRAW_BUF_ALIGN +#define CONFIG_LV_DRAW_BUF_ALIGN 1 +#endif + +static const char *TAG = "LVGL"; + +// Global mono threshold for ST7305 reflective - adjustable via settings UI +// Default 60 per user testing - bold but not filled. 1..255 range. +uint8_t g_mono_threshold = 60; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + lvgl_port_disp_type_t disp_type; /* Display type */ + esp_lcd_panel_io_handle_t io_handle; /* LCD panel IO handle */ + esp_lcd_panel_handle_t panel_handle; /* LCD panel handle */ + esp_lcd_panel_handle_t control_handle; /* LCD panel control handle */ + lvgl_port_rotation_cfg_t rotation; /* Default values of the screen rotation */ + lv_color_t *draw_buffs[3]; /* Display draw buffers */ + uint8_t *oled_buffer; + lv_display_t *disp_drv; /* LVGL display driver */ + lv_display_rotation_t current_rotation; + SemaphoreHandle_t trans_sem; /* Idle transfer mutex */ + lvgl_port_rounder_cb_t rounder_cb; /* Rounder callback for display area */ +#if LVGL_PORT_PPA + lvgl_port_ppa_handle_t ppa_handle; +#endif //LVGL_PORT_PPA + struct { + unsigned int monochrome: 1; /* True, if display is monochrome and using 1bit for 1px */ + unsigned int swap_bytes: 1; /* Swap bytes in RGB656 (16-bit) before send to LCD driver */ + unsigned int full_refresh: 1; /* Always make the whole screen redrawn */ + unsigned int direct_mode: 1; /* Use screen-sized buffers and draw to absolute coordinates */ + unsigned int sw_rotate: 1; /* Use software rotation (slower) or PPA if available */ + } flags; +} lvgl_port_display_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ +static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp_cfg, + const lvgl_port_disp_priv_cfg_t *priv_cfg); +#if LVGL_PORT_HANDLE_FLUSH_READY +static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, + void *user_ctx); +#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +static bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, + const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx); +#endif +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) +static bool lvgl_port_flush_dpi_panel_ready_callback(esp_lcd_panel_handle_t panel_io, + esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx); +static bool lvgl_port_flush_dpi_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, + esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx); +#endif +#endif +static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map); +static void lvgl_port_disp_size_update_callback(lv_event_t *e); +static void lvgl_port_disp_rotation_update(lvgl_port_display_ctx_t *disp_ctx); +static void lvgl_port_display_invalidate_callback(lv_event_t *e); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_display_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg) +{ + lvgl_port_lock(0); + lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, NULL); + + if (disp != NULL) { + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(disp); + /* Set display type */ + disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_OTHER; + + assert(disp_cfg->io_handle != NULL); + +#if LVGL_PORT_HANDLE_FLUSH_READY + const esp_lcd_panel_io_callbacks_t cbs = { + .on_color_trans_done = lvgl_port_flush_io_ready_callback, + }; + /* Register done callback */ + esp_lcd_panel_io_register_event_callbacks(disp_ctx->io_handle, &cbs, disp); +#endif + + /* Apply rotation from initial display configuration */ + lvgl_port_disp_rotation_update(disp_ctx); + } + lvgl_port_unlock(); + + return disp; +} + +lv_display_t *lvgl_port_add_disp_dsi(const lvgl_port_display_cfg_t *disp_cfg, + const lvgl_port_display_dsi_cfg_t *dsi_cfg) +{ + assert(dsi_cfg != NULL); + const lvgl_port_disp_priv_cfg_t priv_cfg = { + .avoid_tearing = dsi_cfg->flags.avoid_tearing, + }; + lvgl_port_lock(0); + lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, &priv_cfg); + + if (disp != NULL) { + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(disp); + /* Set display type */ + disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_DSI; + +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) + esp_lcd_dpi_panel_event_callbacks_t cbs = {0}; + if (dsi_cfg->flags.avoid_tearing) { + cbs.on_refresh_done = lvgl_port_flush_dpi_vsync_ready_callback; + } else { + cbs.on_color_trans_done = lvgl_port_flush_dpi_panel_ready_callback; + } + /* Register done callback */ + esp_lcd_dpi_panel_register_event_callbacks(disp_ctx->panel_handle, &cbs, disp); + + /* Apply rotation from initial display configuration */ + lvgl_port_disp_rotation_update(disp_ctx); + +#else + ESP_RETURN_ON_FALSE(false, NULL, TAG, "MIPI-DSI is supported only on ESP32P4 and from IDF 5.3!"); +#endif + } + lvgl_port_unlock(); + + return disp; +} + +lv_display_t *lvgl_port_add_disp_rgb(const lvgl_port_display_cfg_t *disp_cfg, + const lvgl_port_display_rgb_cfg_t *rgb_cfg) +{ + lvgl_port_lock(0); + assert(rgb_cfg != NULL); + const lvgl_port_disp_priv_cfg_t priv_cfg = { + .avoid_tearing = rgb_cfg->flags.avoid_tearing, + }; + lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, &priv_cfg); + + if (disp != NULL) { + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(disp); + /* Set display type */ + disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_RGB; + +#if (CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) + /* Register done callback */ + const esp_lcd_rgb_panel_event_callbacks_t vsync_cbs = { + .on_vsync = lvgl_port_flush_rgb_vsync_ready_callback, + }; + + const esp_lcd_rgb_panel_event_callbacks_t bb_cbs = { +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + .on_frame_buf_complete = lvgl_port_flush_rgb_vsync_ready_callback, +#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2) + .on_bounce_frame_finish = lvgl_port_flush_rgb_vsync_ready_callback, +#endif + }; + + if (rgb_cfg->flags.bb_mode && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2))) { + ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &bb_cbs, disp_ctx->disp_drv)); + } else { + ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &vsync_cbs, disp_ctx->disp_drv)); + } +#else + ESP_RETURN_ON_FALSE(false, NULL, TAG, "RGB is supported only on ESP32S3 and from IDF 5.0!"); +#endif + + /* Apply rotation from initial display configuration */ + lvgl_port_disp_rotation_update(disp_ctx); + } + lvgl_port_unlock(); + + return disp; +} + +esp_err_t lvgl_port_remove_disp(lv_display_t *disp) +{ + assert(disp); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(disp); + + lvgl_port_lock(0); + lv_disp_remove(disp); + lvgl_port_unlock(); + + if (disp_ctx->draw_buffs[0]) { + free(disp_ctx->draw_buffs[0]); + } + + if (disp_ctx->draw_buffs[1]) { + free(disp_ctx->draw_buffs[1]); + } + + if (disp_ctx->draw_buffs[2]) { + free(disp_ctx->draw_buffs[2]); + } + + if (disp_ctx->oled_buffer) { + free(disp_ctx->oled_buffer); + } + + if (disp_ctx->trans_sem) { + vSemaphoreDelete(disp_ctx->trans_sem); + } +#if LVGL_PORT_PPA + if (disp_ctx->ppa_handle) { + lvgl_port_ppa_delete(disp_ctx->ppa_handle); + } +#endif //LVGL_PORT_PPA + + free(disp_ctx); + + return ESP_OK; +} + +void lvgl_port_flush_ready(lv_display_t *disp) +{ + assert(disp); + lv_disp_flush_ready(disp); +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp_cfg, + const lvgl_port_disp_priv_cfg_t *priv_cfg) +{ + esp_err_t ret = ESP_OK; + lv_display_t *disp = NULL; + lv_color_t *buf1 = NULL; + lv_color_t *buf2 = NULL; + uint32_t buffer_size = 0; + SemaphoreHandle_t trans_sem = NULL; + assert(disp_cfg != NULL); + assert(disp_cfg->panel_handle != NULL); + assert(disp_cfg->buffer_size > 0); + assert(disp_cfg->hres > 0); + assert(disp_cfg->vres > 0); + + buffer_size = disp_cfg->buffer_size; + + /* Check supported display color formats */ + ESP_RETURN_ON_FALSE(disp_cfg->color_format == 0 || disp_cfg->color_format == LV_COLOR_FORMAT_RGB565 + || disp_cfg->color_format == LV_COLOR_FORMAT_RGB888 || disp_cfg->color_format == LV_COLOR_FORMAT_XRGB8888 + || disp_cfg->color_format == LV_COLOR_FORMAT_ARGB8888 + || disp_cfg->color_format == LV_COLOR_FORMAT_I1, NULL, TAG, "Not supported display color format!"); + + lv_color_format_t display_color_format = (disp_cfg->color_format != 0 ? disp_cfg->color_format : + LV_COLOR_FORMAT_RGB565); + uint8_t color_bytes = lv_color_format_get_size(display_color_format); + if (disp_cfg->flags.swap_bytes) { + /* Swap bytes can be used only in RGB565 color format */ + ESP_RETURN_ON_FALSE(display_color_format == LV_COLOR_FORMAT_RGB565, NULL, TAG, + "Swap bytes can be used only in display color format RGB565!"); + } + + if (disp_cfg->flags.buff_dma) { + /* DMA buffer can be used only in RGB565 color format */ + ESP_RETURN_ON_FALSE(display_color_format == LV_COLOR_FORMAT_RGB565, NULL, TAG, + "DMA buffer can be used only in display color format RGB565 (not aligned copy)!"); + } + + /* Display context */ + lvgl_port_display_ctx_t *disp_ctx = malloc(sizeof(lvgl_port_display_ctx_t)); + ESP_GOTO_ON_FALSE(disp_ctx, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for display context allocation!"); + memset(disp_ctx, 0, sizeof(lvgl_port_display_ctx_t)); + disp_ctx->io_handle = disp_cfg->io_handle; + disp_ctx->panel_handle = disp_cfg->panel_handle; + disp_ctx->control_handle = disp_cfg->control_handle; + disp_ctx->rotation.swap_xy = disp_cfg->rotation.swap_xy; + disp_ctx->rotation.mirror_x = disp_cfg->rotation.mirror_x; + disp_ctx->rotation.mirror_y = disp_cfg->rotation.mirror_y; + disp_ctx->flags.swap_bytes = disp_cfg->flags.swap_bytes; + disp_ctx->flags.sw_rotate = disp_cfg->flags.sw_rotate; + disp_ctx->current_rotation = LV_DISPLAY_ROTATION_0; + disp_ctx->rounder_cb = disp_cfg->rounder_cb; + + uint32_t buff_caps = 0; +#if SOC_PSRAM_DMA_CAPABLE == 0 + if (disp_cfg->flags.buff_dma && disp_cfg->flags.buff_spiram) { + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "Alloc DMA capable buffer in SPIRAM is not supported!"); + } +#endif + if (disp_cfg->flags.buff_dma) { + buff_caps |= MALLOC_CAP_DMA; + } + if (disp_cfg->flags.buff_spiram) { + buff_caps |= MALLOC_CAP_SPIRAM; + } + if (buff_caps == 0) { + buff_caps |= MALLOC_CAP_DEFAULT; + } + + /* Use RGB internal buffers for avoid tearing effect */ + if (priv_cfg && priv_cfg->avoid_tearing) { +#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + buffer_size = disp_cfg->hres * disp_cfg->vres; + ESP_GOTO_ON_ERROR(esp_lcd_rgb_panel_get_frame_buffer(disp_cfg->panel_handle, 2, (void *)&buf1, (void *)&buf2), err, TAG, + "Get RGB buffers failed"); +#elif CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + buffer_size = disp_cfg->hres * disp_cfg->vres; + ESP_GOTO_ON_ERROR(esp_lcd_dpi_panel_get_frame_buffer(disp_cfg->panel_handle, 2, (void *)&buf1, (void *)&buf2), err, TAG, + "Get RGB buffers failed"); +#endif + + trans_sem = xSemaphoreCreateCounting(1, 0); + ESP_GOTO_ON_FALSE(trans_sem, ESP_ERR_NO_MEM, err, TAG, "Failed to create transport counting Semaphore"); + disp_ctx->trans_sem = trans_sem; + } else { + /* alloc draw buffers used by LVGL */ + /* it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized */ + buf1 = heap_caps_aligned_alloc(CONFIG_LV_DRAW_BUF_ALIGN, buffer_size * color_bytes, buff_caps); + ESP_GOTO_ON_FALSE(buf1, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf1) allocation!"); + if (disp_cfg->double_buffer) { + buf2 = heap_caps_aligned_alloc(CONFIG_LV_DRAW_BUF_ALIGN, buffer_size * color_bytes, buff_caps); + ESP_GOTO_ON_FALSE(buf2, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf2) allocation!"); + } + + disp_ctx->draw_buffs[0] = buf1; + disp_ctx->draw_buffs[1] = buf2; + } + + disp = lv_display_create(disp_cfg->hres, disp_cfg->vres); + + /* Set display color format */ + lv_display_set_color_format(disp, display_color_format); + + /* Monochrome display settings */ + if (disp_cfg->monochrome) { +#if CONFIG_LV_COLOR_DEPTH_1 +#error please disable LV_COLOR_DEPTH_1 for using monochromatic screen +#endif + + /* Monochrome can be used only in RGB565 color format */ + ESP_RETURN_ON_FALSE(display_color_format == LV_COLOR_FORMAT_RGB565 + || display_color_format == LV_COLOR_FORMAT_I1, NULL, TAG, + "Monochrome can be used only in display color format RGB565 or I1!"); + + /* When using monochromatic display, there must be used full bufer! */ + ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, + "Monochromatic display must using full buffer!"); + + disp_ctx->flags.monochrome = 1; + lv_display_set_buffers(disp, buf1, buf2, buffer_size * color_bytes, LV_DISPLAY_RENDER_MODE_FULL); + + if (display_color_format == LV_COLOR_FORMAT_I1) { + /* OLED monochrome buffer */ + // To use LV_COLOR_FORMAT_I1, we need an extra buffer to hold the converted data + disp_ctx->oled_buffer = heap_caps_malloc(buffer_size, buff_caps); + ESP_GOTO_ON_FALSE(disp_ctx->oled_buffer, ESP_ERR_NO_MEM, err, TAG, + "Not enough memory for LVGL buffer (OLED buffer) allocation!"); + } + + } else if (disp_cfg->flags.direct_mode) { + /* When using direct_mode, there must be used full bufer! */ + ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, + "Direct mode must using full buffer!"); + + disp_ctx->flags.direct_mode = 1; + lv_display_set_buffers(disp, buf1, buf2, buffer_size * color_bytes, LV_DISPLAY_RENDER_MODE_DIRECT); + } else if (disp_cfg->flags.full_refresh) { + /* When using full_refresh, there must be used full bufer! */ + ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, + "Full refresh must using full buffer!"); + + disp_ctx->flags.full_refresh = 1; + lv_display_set_buffers(disp, buf1, buf2, buffer_size * color_bytes, LV_DISPLAY_RENDER_MODE_FULL); + } else { + lv_display_set_buffers(disp, buf1, buf2, buffer_size * color_bytes, LV_DISPLAY_RENDER_MODE_PARTIAL); + } + + lv_display_set_flush_cb(disp, lvgl_port_flush_callback); + lv_display_add_event_cb(disp, lvgl_port_disp_size_update_callback, LV_EVENT_RESOLUTION_CHANGED, disp_ctx); + lv_display_add_event_cb(disp, lvgl_port_display_invalidate_callback, LV_EVENT_INVALIDATE_AREA, disp_ctx); + lv_display_add_event_cb(disp, lvgl_port_display_invalidate_callback, LV_EVENT_REFR_REQUEST, disp_ctx); + + lv_display_set_driver_data(disp, disp_ctx); + disp_ctx->disp_drv = disp; + + /* Use SW rotation */ + if (disp_cfg->flags.sw_rotate) { +#if LVGL_PORT_PPA + ESP_LOGI(TAG, "Setting PPA context for SW rotation"); + ppa_srm_color_mode_t color_mode = PPA_SRM_COLOR_MODE_RGB565; + if (disp_cfg->color_format == LV_COLOR_FORMAT_RGB888) { + color_mode = PPA_SRM_COLOR_MODE_RGB888; + } + + /* Create LCD PPA for rotation */ + lvgl_port_ppa_cfg_t ppa_cfg = { + .buffer_size = disp_cfg->buffer_size * color_bytes, + .color_mode = color_mode, + .flags = { + .buff_dma = disp_cfg->flags.buff_dma, + .buff_spiram = disp_cfg->flags.buff_spiram, + } + }; + disp_ctx->ppa_handle = lvgl_port_ppa_create(&ppa_cfg); + assert(disp_ctx->ppa_handle != NULL); +#else + disp_ctx->draw_buffs[2] = heap_caps_malloc(buffer_size * color_bytes, buff_caps); + ESP_GOTO_ON_FALSE(disp_ctx->draw_buffs[2], ESP_ERR_NO_MEM, err, TAG, + "Not enough memory for LVGL buffer (rotation buffer) allocation!"); + +#endif //LVGL_PORT_PPA + } + + +err: + if (ret != ESP_OK) { + if (disp_ctx->draw_buffs[0]) { + free(disp_ctx->draw_buffs[0]); + } + if (disp_ctx->draw_buffs[1]) { + free(disp_ctx->draw_buffs[1]); + } + if (disp_ctx->draw_buffs[2]) { + free(disp_ctx->draw_buffs[2]); + } + if (disp_ctx->oled_buffer) { + free(disp_ctx->oled_buffer); + } + if (disp_ctx) { + free(disp_ctx); + } + if (trans_sem) { + vSemaphoreDelete(trans_sem); + } + } + + return disp; +} + +#if LVGL_PORT_HANDLE_FLUSH_READY +static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, + void *user_ctx) +{ + lv_display_t *disp_drv = (lv_display_t *)user_ctx; + assert(disp_drv != NULL); + lv_disp_flush_ready(disp_drv); + return false; +} + +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) +static bool lvgl_port_flush_dpi_panel_ready_callback(esp_lcd_panel_handle_t panel_io, + esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) +{ + lv_display_t *disp_drv = (lv_display_t *)user_ctx; + assert(disp_drv != NULL); + lv_disp_flush_ready(disp_drv); + return false; +} + +static bool lvgl_port_flush_dpi_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, + esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) +{ + BaseType_t need_yield = pdFALSE; + + lv_display_t *disp_drv = (lv_display_t *)user_ctx; + assert(disp_drv != NULL); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(disp_drv); + assert(disp_ctx != NULL); + + if (disp_ctx->trans_sem) { + xSemaphoreGiveFromISR(disp_ctx->trans_sem, &need_yield); + } + + return (need_yield == pdTRUE); +} +#endif + +#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +static bool lvgl_port_flush_rgb_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, + const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx) +{ + BaseType_t need_yield = pdFALSE; + + lv_display_t *disp_drv = (lv_display_t *)user_ctx; + assert(disp_drv != NULL); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(disp_drv); + assert(disp_ctx != NULL); + + if (disp_ctx->trans_sem) { + xSemaphoreGiveFromISR(disp_ctx->trans_sem, &need_yield); + } + + return (need_yield == pdTRUE); +} +#endif +#endif + +static void _lvgl_port_transform_monochrome(lv_display_t *display, const lv_area_t *area, uint8_t **color_map) +{ + assert(color_map); + assert(*color_map); + uint8_t *src = *color_map; + lv_color16_t *color = (lv_color16_t *)*color_map; + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(display); + extern uint8_t g_mono_threshold; + uint8_t threshold = g_mono_threshold ? g_mono_threshold : 60; + uint16_t hor_res = lv_display_get_physical_horizontal_resolution(display); + uint16_t ver_res = lv_display_get_physical_vertical_resolution(display); + uint16_t res = hor_res; + bool swap_xy = (lv_display_get_rotation(display) == LV_DISPLAY_ROTATION_90 + || lv_display_get_rotation(display) == LV_DISPLAY_ROTATION_270); + + int x1 = area->x1; + int x2 = area->x2; + int y1 = area->y1; + int y2 = area->y2; + + lv_color_format_t color_format = lv_display_get_color_format(display); + if (color_format == LV_COLOR_FORMAT_I1) { + // This is necessary because LVGL reserves 2 x 4 bytes in the buffer, as these are assumed to be used as a palette. Skip the palette here + // More information about the monochrome, please refer to https://docs.lvgl.io/9.2/porting/display.html#monochrome-displays + src += 8; + /*Use oled_buffer as output */ + *color_map = disp_ctx->oled_buffer; + } + + int out_x, out_y; + for (int y = y1; y <= y2; y++) { + for (int x = x1; x <= x2; x++) { + bool chroma_color = 0; + if (color_format == LV_COLOR_FORMAT_I1) { + chroma_color = (src[(hor_res >> 3) * y + (x >> 3)] & 1 << (7 - x % 8)); + } else { + // Proper luminance from RGB565 instead of blue-only. + // Red 5-bit, Green 6-bit, Blue 5-bit from lv_color16_t struct (b:5,g:6,r:5) + lv_color16_t c = color[hor_res * y + x]; + uint8_t r5 = c.red; + uint8_t g6 = c.green; + uint8_t b5 = c.blue; + // Expand to 8-bit: r8 = (r5*255)/31 ≈ (r5*527+23)>>6, etc. + uint16_t r8 = (r5 * 527 + 23) >> 6; + uint16_t g8 = (g6 * 259 + 33) >> 6; + uint16_t b8 = (b5 * 527 + 23) >> 6; + // Y = 0.299R + 0.587G + 0.114B → (77R+150G+29B)>>8 + uint16_t luma = (r8 * 77 + g8 * 150 + b8 * 29) >> 8; + // Adjustable threshold - no dithering to avoid dots, crisp B/W + chroma_color = (luma > threshold); + } + + if (swap_xy) { + out_x = y; + out_y = x; + res = ver_res; + } else { + out_x = x; + out_y = y; + res = hor_res; + } + + /* Write to the buffer as required for the display. + * It writes only 1-bit for monochrome displays mapped vertically.*/ + uint8_t *outbuf = NULL; + outbuf = *color_map + res * (out_y >> 3) + (out_x); + if (chroma_color) { + (*outbuf) &= ~(1 << (out_y % 8)); + } else { + (*outbuf) |= (1 << (out_y % 8)); + } + } + } + +} + +void lvgl_port_rotate_area(lv_display_t *disp, lv_area_t *area) +{ + lv_display_rotation_t rotation = lv_display_get_rotation(disp); + + int32_t w = lv_area_get_width(area); + int32_t h = lv_area_get_height(area); + + int32_t hres = lv_display_get_horizontal_resolution(disp); + int32_t vres = lv_display_get_vertical_resolution(disp); + if (rotation == LV_DISPLAY_ROTATION_90 || rotation == LV_DISPLAY_ROTATION_270) { + vres = lv_display_get_horizontal_resolution(disp); + hres = lv_display_get_vertical_resolution(disp); + } + + switch (rotation) { + case LV_DISPLAY_ROTATION_0: + return; + case LV_DISPLAY_ROTATION_90: + area->y2 = vres - area->x1 - 1; + area->x1 = area->y1; + area->x2 = area->x1 + h - 1; + area->y1 = area->y2 - w + 1; + break; + case LV_DISPLAY_ROTATION_180: + area->y2 = vres - area->y1 - 1; + area->y1 = area->y2 - h + 1; + area->x2 = hres - area->x1 - 1; + area->x1 = area->x2 - w + 1; + break; + case LV_DISPLAY_ROTATION_270: + area->x1 = hres - area->y2 - 1; + area->y2 = area->x2; + area->x2 = area->x1 + h - 1; + area->y1 = area->y2 - w + 1; + break; + } +} + +static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map) +{ + assert(drv != NULL); + assert(area != NULL); + assert(color_map != NULL); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_driver_data(drv); + assert(disp_ctx != NULL); + + int offsetx1 = area->x1; + int offsetx2 = area->x2; + int offsety1 = area->y1; + int offsety2 = area->y2; + + /* SW rotation enabled */ + if (disp_ctx->flags.sw_rotate && (disp_ctx->current_rotation > LV_DISPLAY_ROTATION_0)) { +#if LVGL_PORT_PPA + if (disp_ctx->ppa_handle) { + /* Screen vertical size */ + int32_t hres = lv_display_get_horizontal_resolution(drv); + int32_t vres = lv_display_get_vertical_resolution(drv); + lvgl_port_ppa_disp_rotate_t rotate_cfg = { + .in_buff = color_map, + .area = { + .x1 = area->x1, + .x2 = area->x2, + .y1 = area->y1, + .y2 = area->y2, + }, + .disp_size = { + .hres = hres, + .vres = vres, + }, + .rotation = disp_ctx->current_rotation, + .ppa_mode = PPA_TRANS_MODE_BLOCKING, + .swap_bytes = (disp_ctx->flags.swap_bytes ? true : false), + .user_data = disp_ctx + }; + /* Do operation */ + esp_err_t err = lvgl_port_ppa_rotate(disp_ctx->ppa_handle, &rotate_cfg); + if (err == ESP_OK) { + color_map = lvgl_port_ppa_get_output_buffer(disp_ctx->ppa_handle); + offsetx1 = rotate_cfg.area.x1; + offsetx2 = rotate_cfg.area.x2; + offsety1 = rotate_cfg.area.y1; + offsety2 = rotate_cfg.area.y2; + } + } +#else + /* SW rotation */ + if (disp_ctx->draw_buffs[2]) { + int32_t ww = lv_area_get_width(area); + int32_t hh = lv_area_get_height(area); + lv_color_format_t cf = lv_display_get_color_format(drv); + uint32_t w_stride = lv_draw_buf_width_to_stride(ww, cf); + uint32_t h_stride = lv_draw_buf_width_to_stride(hh, cf); + if (disp_ctx->current_rotation == LV_DISPLAY_ROTATION_180) { + lv_draw_sw_rotate(color_map, disp_ctx->draw_buffs[2], hh, ww, h_stride, h_stride, LV_DISPLAY_ROTATION_180, cf); + } else if (disp_ctx->current_rotation == LV_DISPLAY_ROTATION_90) { + lv_draw_sw_rotate(color_map, disp_ctx->draw_buffs[2], ww, hh, w_stride, h_stride, LV_DISPLAY_ROTATION_90, cf); + } else if (disp_ctx->current_rotation == LV_DISPLAY_ROTATION_270) { + lv_draw_sw_rotate(color_map, disp_ctx->draw_buffs[2], ww, hh, w_stride, h_stride, LV_DISPLAY_ROTATION_270, cf); + } + color_map = (uint8_t *)disp_ctx->draw_buffs[2]; + lvgl_port_rotate_area(drv, (lv_area_t *)area); + offsetx1 = area->x1; + offsetx2 = area->x2; + offsety1 = area->y1; + offsety2 = area->y2; + } +#endif //LVGL_PORT_PPA + } + + if (disp_ctx->flags.swap_bytes) { + size_t len = lv_area_get_size(area); + lv_draw_sw_rgb565_swap(color_map, len); + } + /* Transfer data in buffer for monochromatic screen */ + if (disp_ctx->flags.monochrome) { + _lvgl_port_transform_monochrome(drv, area, &color_map); + } + + if ((disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_RGB || disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_DSI) + && (disp_ctx->flags.direct_mode || disp_ctx->flags.full_refresh)) { + if (lv_disp_flush_is_last(drv)) { + /* If the interface is I80 or SPI, this step cannot be used for drawing. */ + esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, 0, 0, lv_disp_get_hor_res(drv), lv_disp_get_ver_res(drv), color_map); + /* Waiting for the last frame buffer to complete transmission */ + xSemaphoreTake(disp_ctx->trans_sem, 0); + xSemaphoreTake(disp_ctx->trans_sem, portMAX_DELAY); + } + } else { + esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map); + } + + if (disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_RGB || (disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_DSI + && (disp_ctx->flags.direct_mode || disp_ctx->flags.full_refresh))) { + lv_disp_flush_ready(drv); + } +} + +static void lvgl_port_disp_rotation_update(lvgl_port_display_ctx_t *disp_ctx) +{ + assert(disp_ctx != NULL); + + disp_ctx->current_rotation = lv_display_get_rotation(disp_ctx->disp_drv); + if (disp_ctx->flags.sw_rotate) { + return; + } + + esp_lcd_panel_handle_t control_handle = (disp_ctx->control_handle ? disp_ctx->control_handle : disp_ctx->panel_handle); + /* Solve rotation screen and touch */ + switch (lv_display_get_rotation(disp_ctx->disp_drv)) { + case LV_DISPLAY_ROTATION_0: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, disp_ctx->rotation.swap_xy); + esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); + break; + case LV_DISPLAY_ROTATION_90: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, !disp_ctx->rotation.swap_xy); + if (disp_ctx->rotation.swap_xy) { + esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); + } else { + esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); + } + break; + case LV_DISPLAY_ROTATION_180: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, disp_ctx->rotation.swap_xy); + esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); + break; + case LV_DISPLAY_ROTATION_270: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, !disp_ctx->rotation.swap_xy); + if (disp_ctx->rotation.swap_xy) { + esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); + } else { + esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); + } + break; + } + + /* Wake LVGL task, if needed */ + lvgl_port_task_wake(LVGL_PORT_EVENT_DISPLAY, disp_ctx->disp_drv); +} + +static void lvgl_port_disp_size_update_callback(lv_event_t *e) +{ + assert(e); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_event_get_user_data(e); + lvgl_port_disp_rotation_update(disp_ctx); +} + +static void lvgl_port_display_invalidate_callback(lv_event_t *e) +{ + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_event_get_user_data(e); + lv_area_t *area = (lv_area_t *)lv_event_get_param(e); + if (area != NULL && disp_ctx != NULL && disp_ctx->rounder_cb != NULL) { + disp_ctx->rounder_cb(area); + } + + /* Wake LVGL task, if needed */ + lvgl_port_task_wake(LVGL_PORT_EVENT_DISPLAY, NULL); +} diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_knob.c b/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_knob.c new file mode 100644 index 00000000..a12ff764 --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_knob.c @@ -0,0 +1,237 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lvgl_port.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + knob_handle_t knob_handle; /* Encoder knob handlers */ + button_handle_t btn_handle; /* Encoder button handlers */ + lv_indev_t *indev; /* LVGL input device driver */ + bool btn_enter; /* Encoder button enter state */ + int32_t diff; /* Encoder diff */ +} lvgl_port_encoder_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static void lvgl_port_encoder_read(lv_indev_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_encoder_btn_down_handler(void *button_handle, void *usr_data); +static void lvgl_port_encoder_btn_up_handler(void *button_handle, void *usr_data); +static void lvgl_port_encoder_left_handler(void *arg, void *arg2); +static void lvgl_port_encoder_right_handler(void *arg, void *arg2); +static int32_t lvgl_port_calculate_diff(knob_handle_t knob, knob_event_t event); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_encoder(const lvgl_port_encoder_cfg_t *encoder_cfg) +{ + lv_indev_t *indev; + esp_err_t ret = ESP_OK; + assert(encoder_cfg != NULL); + assert(encoder_cfg->disp != NULL); + + /* Encoder context */ + lvgl_port_encoder_ctx_t *encoder_ctx = malloc(sizeof(lvgl_port_encoder_ctx_t)); + if (encoder_ctx == NULL) { + ESP_LOGE(TAG, "Not enough memory for encoder context allocation!"); + return NULL; + } + + /* Encoder_a/b */ + if (encoder_cfg->encoder_a_b != NULL) { + encoder_ctx->knob_handle = iot_knob_create(encoder_cfg->encoder_a_b); + ESP_GOTO_ON_FALSE(encoder_ctx->knob_handle, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for knob create!"); + + ESP_ERROR_CHECK(iot_knob_register_cb(encoder_ctx->knob_handle, KNOB_LEFT, lvgl_port_encoder_left_handler, encoder_ctx)); + ESP_ERROR_CHECK(iot_knob_register_cb(encoder_ctx->knob_handle, KNOB_RIGHT, lvgl_port_encoder_right_handler, + encoder_ctx)); + } + + /* Encoder Enter */ + if (encoder_cfg->encoder_enter != NULL) { +#if BUTTON_VER_MAJOR < 4 + encoder_ctx->btn_handle = iot_button_create(encoder_cfg->encoder_enter); + ESP_GOTO_ON_FALSE(encoder_ctx->btn_handle, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!"); +#else + ESP_GOTO_ON_FALSE(encoder_cfg->encoder_enter, ESP_ERR_INVALID_ARG, err, TAG, "Invalid button handler!"); + encoder_ctx->btn_handle = encoder_cfg->encoder_enter; +#endif +#if BUTTON_VER_MAJOR < 4 + ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_DOWN, lvgl_port_encoder_btn_down_handler, + encoder_ctx)); + ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_UP, lvgl_port_encoder_btn_up_handler, + encoder_ctx)); +#else + ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_DOWN, NULL, + lvgl_port_encoder_btn_down_handler, encoder_ctx)); + ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_UP, NULL, lvgl_port_encoder_btn_up_handler, + encoder_ctx)); +#endif + } + + encoder_ctx->btn_enter = false; + encoder_ctx->diff = 0; + + lvgl_port_lock(0); + /* Register a encoder input device */ + indev = lv_indev_create(); + lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER); + lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT); + lv_indev_set_read_cb(indev, lvgl_port_encoder_read); + lv_indev_set_disp(indev, encoder_cfg->disp); + lv_indev_set_driver_data(indev, encoder_ctx); + encoder_ctx->indev = indev; + lvgl_port_unlock(); + + return indev; + +err: + if (ret != ESP_OK) { + if (encoder_ctx->knob_handle != NULL) { + iot_knob_delete(encoder_ctx->knob_handle); + } + + if (encoder_ctx->btn_handle != NULL) { + iot_button_delete(encoder_ctx->btn_handle); + } + + if (encoder_ctx != NULL) { + free(encoder_ctx); + } + } + return NULL; +} + +esp_err_t lvgl_port_remove_encoder(lv_indev_t *encoder) +{ + assert(encoder); + lvgl_port_encoder_ctx_t *encoder_ctx = (lvgl_port_encoder_ctx_t *)lv_indev_get_driver_data(encoder); + + if (encoder_ctx->knob_handle != NULL) { + iot_knob_delete(encoder_ctx->knob_handle); + } + + if (encoder_ctx->btn_handle != NULL) { + iot_button_delete(encoder_ctx->btn_handle); + } + + lvgl_port_lock(0); + /* Remove input device driver */ + lv_indev_delete(encoder); + lvgl_port_unlock(); + + if (encoder_ctx != NULL) { + free(encoder_ctx); + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static void lvgl_port_encoder_read(lv_indev_t *indev_drv, lv_indev_data_t *data) +{ + assert(indev_drv); + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *)lv_indev_get_driver_data(indev_drv); + assert(ctx); + + data->enc_diff = ctx->diff; + data->state = (true == ctx->btn_enter) ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; + ctx->diff = 0; +} + +static void lvgl_port_encoder_btn_down_handler(void *button_handle, void *usr_data) +{ + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) usr_data; + button_handle_t button = (button_handle_t)button_handle; + if (ctx && button) { + /* ENTER */ + if (button == ctx->btn_handle) { + ctx->btn_enter = true; + } + } + + /* Wake LVGL task, if needed */ + lvgl_port_task_wake(LVGL_PORT_EVENT_ENCODER, ctx->indev); +} + +static void lvgl_port_encoder_btn_up_handler(void *button_handle, void *usr_data) +{ + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) usr_data; + button_handle_t button = (button_handle_t)button_handle; + if (ctx && button) { + /* ENTER */ + if (button == ctx->btn_handle) { + ctx->btn_enter = false; + } + } + + /* Wake LVGL task, if needed */ + lvgl_port_task_wake(LVGL_PORT_EVENT_ENCODER, ctx->indev); +} + +static void lvgl_port_encoder_left_handler(void *arg, void *arg2) +{ + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2; + knob_handle_t knob = (knob_handle_t)arg; + if (ctx && knob) { + /* LEFT */ + if (knob == ctx->knob_handle) { + int32_t diff = lvgl_port_calculate_diff(knob, KNOB_LEFT); + ctx->diff = (ctx->diff > 0) ? diff : ctx->diff + diff; + } + /* Wake LVGL task, if needed */ + lvgl_port_task_wake(LVGL_PORT_EVENT_ENCODER, ctx->indev); + } +} + +static void lvgl_port_encoder_right_handler(void *arg, void *arg2) +{ + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2; + knob_handle_t knob = (knob_handle_t)arg; + if (ctx && knob) { + /* RIGHT */ + if (knob == ctx->knob_handle) { + int32_t diff = lvgl_port_calculate_diff(knob, KNOB_RIGHT); + ctx->diff = (ctx->diff < 0) ? diff : ctx->diff + diff; + } + /* Wake LVGL task, if needed */ + lvgl_port_task_wake(LVGL_PORT_EVENT_ENCODER, ctx->indev); + } +} + + +static int32_t lvgl_port_calculate_diff(knob_handle_t knob, knob_event_t event) +{ + static int32_t last_v = 0; + + int32_t diff = 0; + int32_t invd = iot_knob_get_count_value(knob); + + if (last_v ^ invd) { + + diff = (int32_t)((uint32_t)invd - (uint32_t)last_v); + diff += (event == KNOB_RIGHT && invd < last_v) ? CONFIG_KNOB_HIGH_LIMIT : + (event == KNOB_LEFT && invd > last_v) ? CONFIG_KNOB_LOW_LIMIT : 0; + last_v = invd; + } + + return diff; +} diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_touch.c b/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_touch.c new file mode 100644 index 00000000..96c6f3ca --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_touch.c @@ -0,0 +1,171 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lcd_touch.h" +#include "esp_lvgl_port.h" +#include "esp_timer.h" +#include "sdkconfig.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + esp_lcd_touch_handle_t handle; /* LCD touch IO handle */ + lv_indev_t *indev; /* LVGL input device driver */ + struct { + float x; + float y; + } scale; /* Touch scale */ +} lvgl_port_touch_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static void lvgl_port_touchpad_read(lv_indev_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_touch_interrupt_callback(esp_lcd_touch_handle_t tp); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg) +{ + esp_err_t ret = ESP_OK; + lv_indev_t *indev = NULL; + assert(touch_cfg != NULL); + assert(touch_cfg->disp != NULL); + assert(touch_cfg->handle != NULL); + + /* Touch context */ + lvgl_port_touch_ctx_t *touch_ctx = malloc(sizeof(lvgl_port_touch_ctx_t)); + if (touch_ctx == NULL) { + ESP_LOGE(TAG, "Not enough memory for touch context allocation!"); + return NULL; + } + touch_ctx->handle = touch_cfg->handle; + touch_ctx->scale.x = (touch_cfg->scale.x ? touch_cfg->scale.x : 1); + touch_ctx->scale.y = (touch_cfg->scale.y ? touch_cfg->scale.y : 1); + + if (touch_ctx->handle->config.int_gpio_num != GPIO_NUM_NC) { + /* Register touch interrupt callback */ + ret = esp_lcd_touch_register_interrupt_callback_with_data(touch_ctx->handle, lvgl_port_touch_interrupt_callback, + touch_ctx); + ESP_GOTO_ON_ERROR(ret, err, TAG, "Error in register touch interrupt."); + } + + lvgl_port_lock(0); + /* Register a touchpad input device */ + indev = lv_indev_create(); + lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); + /* Event mode can be set only, when touch interrupt enabled */ + if (touch_ctx->handle->config.int_gpio_num != GPIO_NUM_NC) { + lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT); + } + lv_indev_set_read_cb(indev, lvgl_port_touchpad_read); + lv_indev_set_disp(indev, touch_cfg->disp); + lv_indev_set_driver_data(indev, touch_ctx); + touch_ctx->indev = indev; + lvgl_port_unlock(); + +err: + if (ret != ESP_OK) { + if (touch_ctx) { + free(touch_ctx); + } + } + + return indev; +} + +esp_err_t lvgl_port_remove_touch(lv_indev_t *touch) +{ + assert(touch); + lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *)lv_indev_get_driver_data(touch); + + lvgl_port_lock(0); + /* Remove input device driver */ + lv_indev_delete(touch); + lvgl_port_unlock(); + + if (touch_ctx->handle->config.int_gpio_num != GPIO_NUM_NC) { + /* Unregister touch interrupt callback */ + esp_lcd_touch_register_interrupt_callback(touch_ctx->handle, NULL); + } + + if (touch_ctx) { + free(touch_ctx); + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static void lvgl_port_touchpad_read(lv_indev_t *indev_drv, lv_indev_data_t *data) +{ + assert(indev_drv); + lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *)lv_indev_get_driver_data(indev_drv); + assert(touch_ctx); + assert(touch_ctx->handle); + + uint8_t touch_cnt = 0; + esp_lcd_touch_point_data_t touch_data[CONFIG_ESP_LCD_TOUCH_MAX_POINTS] = {0}; + + /* Read data from touch controller into memory */ + ESP_ERROR_CHECK(esp_lcd_touch_read_data(touch_ctx->handle)); + + /* Read data from touch controller */ + ESP_ERROR_CHECK(esp_lcd_touch_get_data(touch_ctx->handle, touch_data, &touch_cnt, CONFIG_ESP_LCD_TOUCH_MAX_POINTS)); + +#if (CONFIG_ESP_LCD_TOUCH_MAX_POINTS > 1 && CONFIG_LV_USE_GESTURE_RECOGNITION) + // Number of touch points which need to be constantly updated inside gesture recognizers +#define GESTURE_TOUCH_POINTS 2 +#if GESTURE_TOUCH_POINTS > CONFIG_ESP_LCD_TOUCH_MAX_POINTS +#error "Number of touch point for gesture exceeds maximum number of aquired touch points" +#endif + + /* Initialize LVGL touch data for each activated touch point */ + lv_indev_touch_data_t touches[GESTURE_TOUCH_POINTS] = {0}; + + for (int i = 0; i < touch_cnt && i < GESTURE_TOUCH_POINTS; i++) { + touches[i].state = LV_INDEV_STATE_PRESSED; + touches[i].point.x = touch_ctx->scale.x * touch_data[i].x; + touches[i].point.y = touch_ctx->scale.y * touch_data[i].y; + touches[i].id = touch_data[i].track_id; + touches[i].timestamp = esp_timer_get_time() / 1000; + } + + /* Pass touch data to LVGL gesture recognizers */ + lv_indev_gesture_recognizers_update(indev_drv, touches, GESTURE_TOUCH_POINTS); + lv_indev_gesture_recognizers_set_data(indev_drv, data); + +#endif + + if (touch_cnt > 0) { + data->point.x = touch_ctx->scale.x * touch_data[0].x; + data->point.y = touch_ctx->scale.y * touch_data[0].y; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } +} + +static void IRAM_ATTR lvgl_port_touch_interrupt_callback(esp_lcd_touch_handle_t tp) +{ + lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *) tp->config.user_data; + + /* Wake LVGL task, if needed */ + lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, touch_ctx->indev); +} diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_usbhid.c b/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_usbhid.c new file mode 100644 index 00000000..193275f3 --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/esp_lvgl_port_usbhid.c @@ -0,0 +1,494 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lvgl_port.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#include "usb/hid_host.h" +#include "usb/hid_usage_keyboard.h" +#include "usb/hid_usage_mouse.h" + +/* LVGL image of cursor */ +LV_IMG_DECLARE(img_cursor) + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + QueueHandle_t queue; /* USB HID queue */ + TaskHandle_t task; /* USB HID task */ + bool running; /* USB HID task running */ + struct { + lv_indev_t *indev; /* LVGL mouse input device driver */ + uint8_t sensitivity; /* Mouse sensitivity (cannot be zero) */ + int16_t x; /* Mouse X coordinate */ + int16_t y; /* Mouse Y coordinate */ + bool left_button; /* Mouse left button state */ + } mouse; + struct { + lv_indev_t *indev; /* LVGL keyboard input device driver */ + uint32_t last_key; + bool pressed; + } kb; +} lvgl_port_usb_hid_ctx_t; + +typedef struct { + hid_host_device_handle_t hid_device_handle; + hid_host_driver_event_t event; + void *arg; +} lvgl_port_usb_hid_event_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static lvgl_port_usb_hid_ctx_t *lvgl_port_hid_init(void); +static void lvgl_port_usb_hid_task(void *arg); +static void lvgl_port_usb_hid_read_mouse(lv_indev_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_usb_hid_read_kb(lv_indev_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_usb_hid_callback(hid_host_device_handle_t hid_device_handle, const hid_host_driver_event_t event, + void *arg); + +/******************************************************************************* +* Local variables +*******************************************************************************/ +static lvgl_port_usb_hid_ctx_t lvgl_hid_ctx; + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_usb_hid_mouse_input(const lvgl_port_hid_mouse_cfg_t *mouse_cfg) +{ + lv_indev_t *indev; + assert(mouse_cfg); + assert(mouse_cfg->disp); + + /* Initialize USB HID */ + lvgl_port_usb_hid_ctx_t *hid_ctx = lvgl_port_hid_init(); + if (hid_ctx == NULL) { + return NULL; + } + + /* Mouse sensitivity cannot be zero */ + hid_ctx->mouse.sensitivity = (mouse_cfg->sensitivity == 0 ? 1 : mouse_cfg->sensitivity); + + int32_t ver_res = lv_display_get_vertical_resolution(mouse_cfg->disp); + int32_t hor_res = lv_display_get_physical_horizontal_resolution(mouse_cfg->disp); + + /* Default coordinates to screen center */ + hid_ctx->mouse.x = (hor_res * hid_ctx->mouse.sensitivity) / 2; + hid_ctx->mouse.y = (ver_res * hid_ctx->mouse.sensitivity) / 2; + + lvgl_port_lock(0); + /* Register a mouse input device */ + indev = lv_indev_create(); + lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); + lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT); + lv_indev_set_read_cb(indev, lvgl_port_usb_hid_read_mouse); + lv_indev_set_disp(indev, mouse_cfg->disp); + lv_indev_set_driver_data(indev, hid_ctx); + hid_ctx->mouse.indev = indev; + lvgl_port_unlock(); + + /* Set image of cursor */ + lv_obj_t *cursor = mouse_cfg->cursor_img; + if (cursor == NULL) { + cursor = lv_img_create(lv_scr_act()); + lv_img_set_src(cursor, &img_cursor); + } + lv_indev_set_cursor(indev, cursor); + + return indev; +} + +lv_indev_t *lvgl_port_add_usb_hid_keyboard_input(const lvgl_port_hid_keyboard_cfg_t *keyboard_cfg) +{ + lv_indev_t *indev; + assert(keyboard_cfg); + assert(keyboard_cfg->disp); + + /* Initialize USB HID */ + lvgl_port_usb_hid_ctx_t *hid_ctx = lvgl_port_hid_init(); + if (hid_ctx == NULL) { + return NULL; + } + + lvgl_port_lock(0); + /* Register a mouse input device */ + indev = lv_indev_create(); + lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD); + lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT); + lv_indev_set_read_cb(indev, lvgl_port_usb_hid_read_kb); + lv_indev_set_disp(indev, keyboard_cfg->disp); + lv_indev_set_driver_data(indev, hid_ctx); + hid_ctx->kb.indev = indev; + lvgl_port_unlock(); + + return indev; +} + +esp_err_t lvgl_port_remove_usb_hid_input(lv_indev_t *hid) +{ + assert(hid); + lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)lv_indev_get_driver_data(hid); + + lvgl_port_lock(0); + /* Remove input device driver */ + lv_indev_delete(hid); + lvgl_port_unlock(); + + if (lvgl_hid_ctx.mouse.indev == hid) { + lvgl_hid_ctx.mouse.indev = NULL; + } else if (lvgl_hid_ctx.kb.indev == hid) { + lvgl_hid_ctx.kb.indev = NULL; + } + + /* If all hid input devices are removed, stop task and clean all */ + if (lvgl_hid_ctx.mouse.indev == NULL && lvgl_hid_ctx.kb.indev) { + hid_ctx->running = false; + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static lvgl_port_usb_hid_ctx_t *lvgl_port_hid_init(void) +{ + esp_err_t ret; + + /* USB HID is already initialized */ + if (lvgl_hid_ctx.task) { + return &lvgl_hid_ctx; + } + + /* USB HID host driver config */ + const hid_host_driver_config_t hid_host_driver_config = { + .create_background_task = true, + .task_priority = 5, + .stack_size = 4096, + .core_id = 0, + .callback = lvgl_port_usb_hid_callback, + .callback_arg = &lvgl_hid_ctx, + }; + + ret = hid_host_install(&hid_host_driver_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "USB HID install failed!"); + return NULL; + } + + lvgl_hid_ctx.queue = xQueueCreate(10, sizeof(lvgl_port_usb_hid_event_t)); + xTaskCreate(&lvgl_port_usb_hid_task, "hid_task", 4 * 1024, &lvgl_hid_ctx, 2, &lvgl_hid_ctx.task); + + return &lvgl_hid_ctx; +} + +static char usb_hid_get_keyboard_char(uint8_t key, uint8_t shift) +{ + char ret_key = 0; + + const uint8_t keycode2ascii [57][2] = { + {0, 0}, /* HID_KEY_NO_PRESS */ + {0, 0}, /* HID_KEY_ROLLOVER */ + {0, 0}, /* HID_KEY_POST_FAIL */ + {0, 0}, /* HID_KEY_ERROR_UNDEFINED */ + {'a', 'A'}, /* HID_KEY_A */ + {'b', 'B'}, /* HID_KEY_B */ + {'c', 'C'}, /* HID_KEY_C */ + {'d', 'D'}, /* HID_KEY_D */ + {'e', 'E'}, /* HID_KEY_E */ + {'f', 'F'}, /* HID_KEY_F */ + {'g', 'G'}, /* HID_KEY_G */ + {'h', 'H'}, /* HID_KEY_H */ + {'i', 'I'}, /* HID_KEY_I */ + {'j', 'J'}, /* HID_KEY_J */ + {'k', 'K'}, /* HID_KEY_K */ + {'l', 'L'}, /* HID_KEY_L */ + {'m', 'M'}, /* HID_KEY_M */ + {'n', 'N'}, /* HID_KEY_N */ + {'o', 'O'}, /* HID_KEY_O */ + {'p', 'P'}, /* HID_KEY_P */ + {'q', 'Q'}, /* HID_KEY_Q */ + {'r', 'R'}, /* HID_KEY_R */ + {'s', 'S'}, /* HID_KEY_S */ + {'t', 'T'}, /* HID_KEY_T */ + {'u', 'U'}, /* HID_KEY_U */ + {'v', 'V'}, /* HID_KEY_V */ + {'w', 'W'}, /* HID_KEY_W */ + {'x', 'X'}, /* HID_KEY_X */ + {'y', 'Y'}, /* HID_KEY_Y */ + {'z', 'Z'}, /* HID_KEY_Z */ + {'1', '!'}, /* HID_KEY_1 */ + {'2', '@'}, /* HID_KEY_2 */ + {'3', '#'}, /* HID_KEY_3 */ + {'4', '$'}, /* HID_KEY_4 */ + {'5', '%'}, /* HID_KEY_5 */ + {'6', '^'}, /* HID_KEY_6 */ + {'7', '&'}, /* HID_KEY_7 */ + {'8', '*'}, /* HID_KEY_8 */ + {'9', '('}, /* HID_KEY_9 */ + {'0', ')'}, /* HID_KEY_0 */ + {'\r', '\r'}, /* HID_KEY_ENTER */ + {0, 0}, /* HID_KEY_ESC */ + {'\b', 0}, /* HID_KEY_DEL */ + {0, 0}, /* HID_KEY_TAB */ + {' ', ' '}, /* HID_KEY_SPACE */ + {'-', '_'}, /* HID_KEY_MINUS */ + {'=', '+'}, /* HID_KEY_EQUAL */ + {'[', '{'}, /* HID_KEY_OPEN_BRACKET */ + {']', '}'}, /* HID_KEY_CLOSE_BRACKET */ + {'\\', '|'}, /* HID_KEY_BACK_SLASH */ + {'\\', '|'}, /* HID_KEY_SHARP */ // HOTFIX: for NonUS Keyboards repeat HID_KEY_BACK_SLASH + {';', ':'}, /* HID_KEY_COLON */ + {'\'', '"'}, /* HID_KEY_QUOTE */ + {'`', '~'}, /* HID_KEY_TILDE */ + {',', '<'}, /* HID_KEY_LESS */ + {'.', '>'}, /* HID_KEY_GREATER */ + {'/', '?'} /* HID_KEY_SLASH */ + }; + + if (shift > 1) { + shift = 1; + } + + if ((key >= HID_KEY_A) && (key <= HID_KEY_SLASH)) { + ret_key = keycode2ascii[key][shift]; + } + + return ret_key; +} + +static void lvgl_port_usb_hid_host_interface_callback(hid_host_device_handle_t hid_device_handle, + const hid_host_interface_event_t event, void *arg) +{ + hid_host_dev_params_t dev; + hid_host_device_get_params(hid_device_handle, &dev); + lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)arg; + uint8_t data[10]; + unsigned int data_length = 0; + + assert(hid_ctx != NULL); + + switch (event) { + case HID_HOST_INTERFACE_EVENT_INPUT_REPORT: + hid_host_device_get_raw_input_report_data(hid_device_handle, data, sizeof(data), &data_length); + if (dev.proto == HID_PROTOCOL_KEYBOARD) { + hid_keyboard_input_report_boot_t *keyboard = (hid_keyboard_input_report_boot_t *)data; + if (data_length < sizeof(hid_keyboard_input_report_boot_t)) { + return; + } + for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) { + if (keyboard->key[i] > HID_KEY_ERROR_UNDEFINED) { + char key = 0; + + /* LVGL special keys */ + if (keyboard->key[i] == HID_KEY_TAB) { + if ((keyboard->modifier.left_shift || keyboard->modifier.right_shift)) { + key = LV_KEY_PREV; + } else { + key = LV_KEY_NEXT; + } + } else if (keyboard->key[i] == HID_KEY_RIGHT) { + key = LV_KEY_RIGHT; + } else if (keyboard->key[i] == HID_KEY_LEFT) { + key = LV_KEY_LEFT; + } else if (keyboard->key[i] == HID_KEY_DOWN) { + key = LV_KEY_DOWN; + } else if (keyboard->key[i] == HID_KEY_UP) { + key = LV_KEY_UP; + } else if (keyboard->key[i] == HID_KEY_ENTER || keyboard->key[i] == HID_KEY_KEYPAD_ENTER) { + key = LV_KEY_ENTER; + } else if (keyboard->key[i] == HID_KEY_DELETE) { + key = LV_KEY_DEL; + } else if (keyboard->key[i] == HID_KEY_HOME) { + key = LV_KEY_HOME; + } else if (keyboard->key[i] == HID_KEY_END) { + key = LV_KEY_END; + } else { + /* Get ASCII char */ + key = usb_hid_get_keyboard_char(keyboard->key[i], (keyboard->modifier.left_shift || keyboard->modifier.right_shift)); + } + + if (key == 0) { + ESP_LOGI(TAG, "Not recognized key: %c (%d)", keyboard->key[i], keyboard->key[i]); + } + hid_ctx->kb.last_key = key; + hid_ctx->kb.pressed = true; + } + } + + /* Wake LVGL task, if needed */ + lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, hid_ctx->kb.indev); + } else if (dev.proto == HID_PROTOCOL_MOUSE) { + hid_mouse_input_report_boot_t *mouse = (hid_mouse_input_report_boot_t *)data; + if (data_length < sizeof(hid_mouse_input_report_boot_t)) { + break; + } + hid_ctx->mouse.left_button = mouse->buttons.button1; + hid_ctx->mouse.x += mouse->x_displacement; + hid_ctx->mouse.y += mouse->y_displacement; + + /* Wake LVGL task, if needed */ + lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, hid_ctx->mouse.indev); + } + break; + case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR: + break; + case HID_HOST_INTERFACE_EVENT_DISCONNECTED: + hid_host_device_close(hid_device_handle); + break; + default: + break; + } +} + +static void lvgl_port_usb_hid_task(void *arg) +{ + hid_host_dev_params_t dev; + lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)arg; + hid_host_device_handle_t hid_device_handle = NULL; + lvgl_port_usb_hid_event_t msg; + + assert(ctx); + + ctx->running = true; + + while (ctx->running) { + if (xQueueReceive(ctx->queue, &msg, pdMS_TO_TICKS(50))) { + hid_device_handle = msg.hid_device_handle; + hid_host_device_get_params(hid_device_handle, &dev); + + switch (msg.event) { + case HID_HOST_DRIVER_EVENT_CONNECTED: + /* Handle mouse or keyboard */ + if (dev.proto == HID_PROTOCOL_KEYBOARD || dev.proto == HID_PROTOCOL_MOUSE) { + const hid_host_device_config_t dev_config = { + .callback = lvgl_port_usb_hid_host_interface_callback, + .callback_arg = ctx + }; + + ESP_ERROR_CHECK( hid_host_device_open(hid_device_handle, &dev_config) ); + ESP_ERROR_CHECK( hid_class_request_set_idle(hid_device_handle, 0, 0) ); + ESP_ERROR_CHECK( hid_class_request_set_protocol(hid_device_handle, HID_REPORT_PROTOCOL_BOOT) ); + ESP_ERROR_CHECK( hid_host_device_start(hid_device_handle) ); + } + break; + default: + break; + } + } + } + + xQueueReset(ctx->queue); + vQueueDelete(ctx->queue); + + hid_host_uninstall(); + + memset(&lvgl_hid_ctx, 0, sizeof(lvgl_port_usb_hid_ctx_t)); + + vTaskDelete(NULL); +} + +static void lvgl_port_usb_hid_read_mouse(lv_indev_t *indev_drv, lv_indev_data_t *data) +{ + int16_t width = 0; + int16_t height = 0; + assert(indev_drv); + lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)lv_indev_get_driver_data(indev_drv); + assert(ctx); + + lv_display_t *disp = lv_indev_get_display(indev_drv); + assert(disp); + if (lv_display_get_rotation(disp) == LV_DISPLAY_ROTATION_0 + || lv_display_get_rotation(disp) == LV_DISPLAY_ROTATION_180) { + width = lv_display_get_physical_horizontal_resolution(disp); + height = lv_display_get_vertical_resolution(disp); + } else { + width = lv_display_get_vertical_resolution(disp); + height = lv_display_get_physical_horizontal_resolution(disp); + } + + /* Screen borders */ + if (ctx->mouse.x < 0) { + ctx->mouse.x = 0; + } else if (ctx->mouse.x > width * ctx->mouse.sensitivity) { + ctx->mouse.x = width * ctx->mouse.sensitivity; + } + if (ctx->mouse.y < 0) { + ctx->mouse.y = 0; + } else if (ctx->mouse.y > height * ctx->mouse.sensitivity) { + ctx->mouse.y = height * ctx->mouse.sensitivity; + } + + /* Get coordinates by rotation with sensitivity */ + switch (lv_display_get_rotation(disp)) { + case LV_DISPLAY_ROTATION_0: + data->point.x = ctx->mouse.x / ctx->mouse.sensitivity; + data->point.y = ctx->mouse.y / ctx->mouse.sensitivity; + break; + case LV_DISPLAY_ROTATION_90: + data->point.y = width - ctx->mouse.x / ctx->mouse.sensitivity; + data->point.x = ctx->mouse.y / ctx->mouse.sensitivity; + break; + case LV_DISPLAY_ROTATION_180: + data->point.x = width - ctx->mouse.x / ctx->mouse.sensitivity; + data->point.y = height - ctx->mouse.y / ctx->mouse.sensitivity; + break; + case LV_DISPLAY_ROTATION_270: + data->point.y = ctx->mouse.x / ctx->mouse.sensitivity; + data->point.x = height - ctx->mouse.y / ctx->mouse.sensitivity; + break; + } + + if (ctx->mouse.left_button) { + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } +} + +static void lvgl_port_usb_hid_read_kb(lv_indev_t *indev_drv, lv_indev_data_t *data) +{ + assert(indev_drv); + lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)lv_indev_get_driver_data(indev_drv); + assert(ctx); + + data->key = ctx->kb.last_key; + if (ctx->kb.pressed) { + data->state = LV_INDEV_STATE_PRESSED; + ctx->kb.pressed = false; + } else { + data->state = LV_INDEV_STATE_RELEASED; + ctx->kb.last_key = 0; + } +} + +static void lvgl_port_usb_hid_callback(hid_host_device_handle_t hid_device_handle, const hid_host_driver_event_t event, + void *arg) +{ + lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)arg; + + const lvgl_port_usb_hid_event_t msg = { + .hid_device_handle = hid_device_handle, + .event = event, + .arg = arg + }; + + xQueueSend(hid_ctx->queue, &msg, 0); +} diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_argb8888_esp32.S b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_argb8888_esp32.S new file mode 100644 index 00000000..7d060675 --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_argb8888_esp32.S @@ -0,0 +1,81 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// This is LVGL ARGB8888 simple fill for ESP32 processor + + .section .text + .align 4 + .global lv_color_blend_to_argb8888_esp + .type lv_color_blend_to_argb8888_esp,@function + +// The function implements the following C code: +// void lv_color_blend_to_argb8888(_lv_draw_sw_blend_fill_dsc_t * dsc); + +// Input params +// +// dsc - a2 + +// typedef struct { +// uint32_t opa; l32i 0 +// void * dst_buf; l32i 4 +// uint32_t dst_w; l32i 8 +// uint32_t dst_h; l32i 12 +// uint32_t dst_stride; l32i 16 +// const void * src_buf; l32i 20 +// uint32_t src_stride; l32i 24 +// const lv_opa_t * mask_buf; l32i 28 +// uint32_t mask_stride; l32i 32 +// } asm_dsc_t; + +lv_color_blend_to_argb8888_esp: + + entry a1, 32 + + l32i.n a3, a2, 4 // a3 - dest_buff + l32i.n a4, a2, 8 // a4 - dest_w in uint32_t + l32i.n a5, a2, 12 // a5 - dest_h in uint32_t + l32i.n a6, a2, 16 // a6 - dest_stride in bytes + l32i.n a7, a2, 20 // a7 - src_buff (color) + l32i.n a8, a7, 0 // a8 - color as value + slli a11, a4, 2 // a11 - dest_w_bytes = sizeof(uint32_t) * dest_w + + movi a7, 0xff000000 // oppactiy mask + or a10, a7, a8 // apply oppacity + + srli a9, a4, 2 // a9 - loop_len = dest_w / 4 + sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes + + .outer_loop: + + // Run main loop which sets 16 bytes in one loop run + loopnez a9, ._main_loop + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3 + s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3 + s32i.n a10, a3, 8 // save 32 bits from a10 to dest_buff a3 + s32i.n a10, a3, 12 // save 32 bits from a10 to dest_buff a3 + addi.n a3, a3, 16 // increment dest_buff pointer by 16 bytes + ._main_loop: + + // Finish the remaining bytes out of the loop + // Check modulo 8 of the dest_w_bytes, if - then set 8 bytes + bbci a11, 3, _mod_8_check // branch if 2-nd bit of dest_w_bytes is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes + _mod_8_check: + + // Check modulo 4 of the dest_w_bytes, if - then set 4 bytes + bbci a11, 2, _mod_4_check // branch if 2-nd bit of dest_w_bytes is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + _mod_4_check: + + add a3, a3, a6 // dest_buff + dest_stride + addi.n a5, a5, -1 // decrease the outer loop + bnez a5, .outer_loop + + movi.n a2, 1 // return LV_RESULT_OK = 1 + retw.n // return diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_argb8888_esp32s3.S b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_argb8888_esp32s3.S new file mode 100644 index 00000000..10276f4f --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_argb8888_esp32s3.S @@ -0,0 +1,325 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// This is LVGL ARGB8888 simple fill for ESP32S3 processor + + .section .text + .align 4 + .global lv_color_blend_to_argb8888_esp + .type lv_color_blend_to_argb8888_esp,@function +// The function implements the following C code: +// void lv_color_blend_to_argb8888(_lv_draw_sw_blend_fill_dsc_t * dsc); + +// Input params +// +// dsc - a2 + +// typedef struct { +// uint32_t opa; l32i 0 +// void * dst_buf; l32i 4 +// uint32_t dst_w; l32i 8 +// uint32_t dst_h; l32i 12 +// uint32_t dst_stride; l32i 16 +// const void * src_buf; l32i 20 +// uint32_t src_stride; l32i 24 +// const lv_opa_t * mask_buf; l32i 28 +// uint32_t mask_stride; l32i 32 +// } asm_dsc_t; + + +lv_color_blend_to_argb8888_esp: + + entry a1, 32 + + l32i.n a3, a2, 4 // a3 - dest_buff + l32i.n a4, a2, 8 // a4 - dest_w in uint32_t + l32i.n a5, a2, 12 // a5 - dest_h in uint32_t + l32i.n a6, a2, 16 // a6 - dest_stride in bytes + l32i.n a7, a2, 20 // a7 - src_buff (color) + l32i.n a8, a7, 0 // a8 - color as value + slli a11, a4, 2 // a11 - dest_w_bytes = sizeof(uint32_t) * dest_w + + movi a7, 0xff000000 // oppactiy mask + or a10, a7, a8 // apply oppacity + + // Check for short lengths + // dest_w should be at least 8, othewise it's not worth using esp32s3 TIE + bgei a4, 8, _esp32s3_implementation // Branch if dest_w is greater than or equal to 8 + j .lv_color_blend_to_argb8888_esp32_body // Jump to esp32 implementation + + _esp32s3_implementation: + + ee.movi.32.q q0, a10, 0 // fill q0 register from a10 by 32 bits + ee.movi.32.q q0, a10, 1 + ee.movi.32.q q0, a10, 2 + ee.movi.32.q q0, a10, 3 + + // Check dest_buff alignment + movi.n a7, 0xf // 0xf alignment mask (16-byte alignment) + and a15, a7, a3 // 16-byte alignment mask AND dest_buff pointer + bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero + + // Check dest_stride alignment + and a15, a7, a6 // 16-byte alignment mask AND dest_stride + bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero + + // Check dest_w_bytes alignment + and a15, a7, a11 // 16-byte alignment mask AND dest_w_bytes + bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero + +//********************************************************************************************************************** + + // all aligned, the most ideal case + + // dest_buff (a3) - 16-byte aligned + // dest_stride (a6) - 16-byte multiple + // dest_w (a4) - 16-byte multiple + + srli a9, a4, 2 // a9 - loop_len = dest_w / 4 + sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes + + .outer_loop_aligned: + + loopnez a9, ._main_loop_aligned // 16 bytes (4 argb8888) in one loop + ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3 + ._main_loop_aligned: + + add a3, a3, a6 // dest_buff + dest_stride + addi.n a5, a5, -1 // decrease the outer loop + bnez a5, .outer_loop_aligned + + movi.n a2, 1 // return LV_RESULT_OK = 1 + retw.n // return + + _unaligned_by_4byte: + + // Check dest_buff alignment + movi.n a7, 0x3 // 0x3 alignment mask (4-byte alignment) + and a15, a7, a3 // 4-byte alignment mask AND dest_buff pointer + bnez a15, _unaligned_by_1byte // branch if a15 not equals to zero + + // Check dest_stride alignment + and a15, a7, a6 // 4-byte alignment mask AND dest_stride pointer + bnez a15, _unaligned_by_1byte // branch if a15 not equals to zero + +//********************************************************************************************************************** + + // either dest_buff or dest_stride is not 16-byte aligned + // dest_w is always 4-byte multiple + // all of the following are 4-byte aligned + + // dest_buff (a3) - 16-byte, or 4-byte aligned + // dest_stride (a6) - 16-byte, or 4-byte multiple + // dest_w (a4) - 4-byte multiple + + sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes + movi.n a7, 0xf // 0xf alignment mask + + .outer_loop_aligned_by_4byte: + + // alignment check + and a15, a7, a3 // 0xf (alignment mask) AND dest_buff pointer + mov a12, a11 // a12 - local_dest_w_bytes = dest_w_bytes + beqz a15, _dest_buff_aligned_by_4byte // branch if a15 equals to zero + + + movi.n a14, 16 // a14 - 16 + sub a15, a14, a15 // a15 = 16 - unalignment (lower 4 bits of dest_buff address) + sub a12, a12, a15 // local_dest_w_bytes = len - (16 - unalignment) + + // keep setting until dest_buff is aligned + // Check modulo 8 of the unalignment, if - then set 8 bytes + bbci a15, 3, _aligning_mod_8_check_4byte // branch if 3-rd bit of unalignment a15 is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 4 bytes + addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes + _aligning_mod_8_check_4byte: + + // Check modulo 4 of the unalignment, if - then set 4 bytes + bbci a15, 2, _aligning_mod_4_check_4byte // branch if 2-nd bit unalignment a15 is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + _aligning_mod_4_check_4byte: + + _dest_buff_aligned_by_4byte: + // Calculate main loop_len + srli a9, a12, 4 // a9 - loop_len = local_dest_w_bytes / 16 + + // Main loop + loopnez a9, ._main_loop_unaligned_by_4byte // 16 bytes (4 argb8888) in one loop + ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3 + ._main_loop_unaligned_by_4byte: + + // Check modulo 8 of the dest_w, if - then set 8 bytes + bbci a12, 3, _aligned_mod_8_check_4byte // branch if 3-rd bit of local_dest_w_bytes a12 is clear + ee.vst.l.64.ip q0, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes + _aligned_mod_8_check_4byte: + + // Check modulo 4 of the dest_w, if - then set 4 bytes + bbci a12, 2, _aligned_mod_4_check_4byte // branch if 2-nd bit of local_dest_w_bytes a12 is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + _aligned_mod_4_check_4byte: + + add a3, a3, a6 // dest_buff + dest_stride + addi.n a5, a5, -1 // decrease the outer loop + bnez a5, .outer_loop_aligned_by_4byte + + movi.n a2, 1 // return LV_RESULT_OK = 1 + retw.n // return + + _unaligned_by_1byte: + +//********************************************************************************************************************** + + // either dest_buff or dest_stride is not 4-byte aligned + // dest_w is always 4-byte multiple + + // dest_buff (a3) - 4-byte, or 1-byte aligned + // dest_stride (a6) - 4-byte, or 1-byte multiple + // dest_w (a4) - 4-byte multiple + + + ee.zero.q q1 // clear q1 + ee.orq q1, q1, q0 // copy q0 to q1 + sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes + movi.n a7, 0xf // 0xf alignment mask + + .outer_loop_aligned_by_1byte: + + // alignment check + and a15, a7, a3 // 0xf (alignment mask) AND dest_buff pointer + mov a12, a11 // a12 - local_dest_w_bytes = dest_w_bytes + beqz a15, _dest_buff_aligned_by_1byte // branch if a15 equals to zero + + + movi.n a14, 16 // a14 - 16 + sub a15, a14, a15 // a15 = 16 - unalignment (lower 4 bits of dest_buff address) + sub a12, a12, a15 // local_dest_w_bytes = len - (16 - unalignment) + + // keep setting until dest_buff is aligned + // Check modulo 8 of the unalignment, if - then set 8 bytes + bbci a15, 3, _aligning_mod_8_check_1byte// branch if 3-rd bit of unalignment a15 is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 4 bytes + addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes + _aligning_mod_8_check_1byte: + + // Check modulo 4 of the unalignment, if - then set 4 bytes + bbci a15, 2, _aligning_mod_4_check_1byte // branch if 2-nd bit unalignment a15 is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + _aligning_mod_4_check_1byte: + + // Check modulo 2 and 1 (the following 2 ifs do the same correction) + // modulo 2 and modulo 1 requires the same action, just once + bbci a15, 1, _aligning_mod_2_check_1byte + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + j _dest_buff_aligned_by_1byte + _aligning_mod_2_check_1byte: + + bbci a15, 0, _dest_buff_aligned_by_1byte + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + _dest_buff_aligned_by_1byte: + + // Shift q reg, allowing to set 16-byte unaligned adata + wur.sar_byte a15 // apply unalignment to the SAR_BYTE + ee.src.q q2, q0, q1 // shift concat. of q0 and q1 to q2 by SAR_BYTE amount + + // Calculate main loop_len + srli a9, a12, 4 // a9 - loop_len = local_dest_w_bytes / 16 + + // Main loop + loopnez a9, ._main_loop_unaligned_by_1byte // 16 bytes (4 argb8888) in one loop + ee.vst.128.ip q2, a3, 16 // store 16 bytes from q0 to dest_buff a3 + ._main_loop_unaligned_by_1byte: + + // Firstly check mod 1 and mod 2 - correcting the aligned memory access + // Go back in one Byte, allow to correct after ee.vst.128.ip aligned access + addi a3, a3, -4 + + // Check modulo 2 of the dest_w, if - then set 2 bytes + // set SSSS in 0xSSSS0000 + bbci a12, 1, _aligned_mod_2_check_1byte // branch if 1-st bit of dest_w a12 is clear + srli a14, a10, 16 // shift a10 in 16, allowing s16i (saving of lower 16 bits) + s16i a14, a3, 2 // save 16 bits from a10 to dest_buff a3, offset 2 bytes + + // Check modulo 1 of the dest_w, if - then set 1 byte + // additionally set SS in 0x0000SS00 + bbci a12, 0, _aligned_end // branch if 0-th bit of dest_w a12 is clear + srli a14, a10, 8 // shift a10 in 8, allowing s8i + s8i a14, a3, 1 // save 8 bits from a10 to dest_buff a3, offset 1 byte + j _aligned_end + _aligned_mod_2_check_1byte: + + // Check modulo 1 of the dest_w, if - then set 1 byte + // set SS in 0xSS000000 + bbci a12, 0, _aligned_end // branch if 0-th bit of dest_w a12 is clear + srli a14, a10, 24 // shift a10 in 24, allowing s8i (saving of lower 8 bits) + s8i a14, a3, 3 // save 8 bits from a10 to dest_buff a3, offset 3 bytes + _aligned_end: + + addi a3, a3, 4 // Increase the pointer back, correction for addi a3, a3, -4 + + // Check modulo 8 of the dest_w, if - then set 8 bytes + bbci a12, 3, _aligned_mod_8_check_1byte // branch if 3-rd bit of local_dest_w_bytes a12 is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 8 // increment dest_buff pointer by 4 bytes + //ee.vst.l.64.ip q2, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes + _aligned_mod_8_check_1byte: + + // Check modulo 4 of the dest_w, if - then set 4 bytes + bbci a12, 2, _aligned_mod_4_check_1byte // branch if 2-nd bit of local_dest_w_bytes a12 is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + _aligned_mod_4_check_1byte: + + add a3, a3, a6 // dest_buff + dest_stride + addi.n a5, a5, -1 // decrease the outer loop + bnez a5, .outer_loop_aligned_by_1byte + + movi.n a2, 1 // return LV_RESULT_OK = 1 + retw.n // return + + .lv_color_blend_to_argb8888_esp32_body: + + srli a9, a4, 2 // a9 - loop_len = dest_w / 4 + sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes + + .outer_loop: + + // Run main loop which sets 16 bytes in one loop run + loopnez a9, ._main_loop + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3 + s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3 + s32i.n a10, a3, 8 // save 32 bits from a10 to dest_buff a3 + s32i.n a10, a3, 12 // save 32 bits from a10 to dest_buff a3 + addi.n a3, a3, 16 // increment dest_buff pointer by 16 bytes + ._main_loop: + + // Finish the remaining bytes out of the loop + // Check modulo 8 of the dest_w_bytes, if - then set 8 bytes + bbci a11, 3, _mod_8_check // branch if 2-nd bit of dest_w_bytes is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes + _mod_8_check: + + // Check modulo 4 of the dest_w_bytes, if - then set 4 bytes + bbci a11, 2, _mod_4_check // branch if 2-nd bit of dest_w_bytes is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + _mod_4_check: + + add a3, a3, a6 // dest_buff + dest_stride + addi.n a5, a5, -1 // decrease the outer loop + bnez a5, .outer_loop + + movi.n a2, 1 // return LV_RESULT_OK = 1 + retw.n // return diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb565_esp32.S b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb565_esp32.S new file mode 100644 index 00000000..07b5aa11 --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb565_esp32.S @@ -0,0 +1,149 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// This is LVGL RGB565 simple fill for ESP32 processor + + .section .text + .align 4 + .global lv_color_blend_to_rgb565_esp + .type lv_color_blend_to_rgb565_esp,@function +// The function implements the following C code: +// void lv_color_blend_to_rgb565(_lv_draw_sw_blend_fill_dsc_t * dsc); + +// Input params +// +// dsc - a2 + +// typedef struct { +// uint32_t opa; l32i 0 +// void * dst_buf; l32i 4 +// uint32_t dst_w; l32i 8 +// uint32_t dst_h; l32i 12 +// uint32_t dst_stride; l32i 16 +// const void * src_buf; l32i 20 +// uint32_t src_stride; l32i 24 +// const lv_opa_t * mask_buf; l32i 28 +// uint32_t mask_stride; l32i 32 +// } asm_dsc_t; + +lv_color_blend_to_rgb565_esp: + + entry a1, 32 + + l32i.n a3, a2, 4 // a3 - dest_buff + l32i.n a4, a2, 8 // a4 - dest_w in uint16_t + l32i.n a5, a2, 12 // a5 - dest_h in uint16_t + l32i.n a6, a2, 16 // a6 - dest_stride in bytes + l32i.n a7, a2, 20 // a7 - src_buff (color) + l32i.n a8, a7, 0 // a8 - color as value + slli a11, a4, 1 // a11 - dest_w_bytes = sizeof(uint16_t) * dest_w + + // Convert color to rgb656 + l8ui a15, a7, 2 // red + movi.n a14, 0xf8 + and a13, a15, a14 + slli a10, a13, 8 + + l8ui a15, a7, 0 // blue + and a13, a15, a14 + srli a12, a13, 3 + add a10, a10, a12 + + l8ui a15, a7, 1 // green + movi.n a14, 0xfc + and a13, a15, a14 + slli a12, a13, 3 + add a12, a10, a12 // a12 = 16-bit color + + slli a10, a12, 16 + movi.n a13, 0xFFFF0000 + and a10, a10, a13 + or a10, a10, a12 // a10 = 32-bit color (16bit + (16bit << 16)) + + movi.n a8, 0x3 // a8 = 0x3, dest_buff align mask + sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes + + // cache init + // Prepare main loop length and dest_w_bytes + srli a9, a4, 4 // a9 = loop_len = dest_w / 8, calculate main loop_len for original dest_w + slli a11, a4, 1 // a11 = dest_w_bytes = sizeof(uint16_t) * dest_w + addi a4, a4, -1 // a4-- (decrement a4) + s32i.n a9, a1, 0 // cache.orig.loop_len + s32i.n a11, a1, 4 // cache.orig.dest_w_bytes + + // Prepare decreased main loop length and dest_w_bytes + srli a9, a4, 4 // a9 = loop_len = dest_w / 8, calculate main loop_len for dest_w - 1 + slli a11, a4, 1 // a11 = dest_w_bytes = sizeof(uint16_t) * (dest_w - 1) + s32i.n a9, a1, 8 // cache.decr.loop_len + s32i.n a11, a1, 12 // cache.decr.dest_w_bytes + and a7, a8, a3 // a7 = dest_buff AND 0x3 (chck if the address is 4-byte aligned) + + .outer_loop: + + // Check if the des_buff is 2-byte aligned + beqz a7, _dest_buff_2_byte_aligned // branch if a7 is equal to zero + s16i a12, a3, 0 // save 16 bits from 16-bit color a12 to dest_buff a3, offset 0 + l32i.n a9, a1, 8 // a9 = load cache.decr.loop_len + l32i.n a11, a1, 12 // a11 = load cache.decr.dest_w_bytes + addi.n a3, a3, 2 // increment dest_buff pointer by 2 + j _dest_buff_unaligned + _dest_buff_2_byte_aligned: + + l32i.n a9, a1, 0 // a11 = load cache.orig.loop_len + l32i.n a11, a1, 4 // a11 = load cache.orig.dest_w_bytes + + _dest_buff_unaligned: + + // Run main loop which sets 16 bytes in one loop run + loopnez a9, ._main_loop + s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0 + s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4 + s32i.n a10, a3, 8 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 8 + s32i.n a10, a3, 12 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 12 + s32i.n a10, a3, 16 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 16 + s32i.n a10, a3, 20 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 20 + s32i.n a10, a3, 24 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 24 + s32i.n a10, a3, 28 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 28 + addi.n a3, a3, 32 // increment dest_buff pointer by 32 + ._main_loop: + + // Finish the remaining bytes out of the loop + // Check modulo 8 of the dest_w_bytes, if - then set 16 bytes + bbci a11, 4, _mod_16_check // branch if 2-nd bit of dest_w_bytes is clear + s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0 + s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4 + s32i.n a10, a3, 8 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 8 + s32i.n a10, a3, 12 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 12 + addi.n a3, a3, 16 // increment dest_buff pointer by 16 + _mod_16_check: + + // Finish the remaining bytes out of the loop + // Check modulo 8 of the dest_w_bytes, if - then set 8 bytes + bbci a11, 3, _mod_8_check // branch if 2-nd bit of dest_w_bytes is clear + s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0 + s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4 + addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes + _mod_8_check: + + // Check modulo 4 of the dest_w_bytes, if - then set 4 bytes + bbci a11, 2, _mod_4_check // branch if 2-nd bit of dest_w_bytes is clear + s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0 + addi.n a3, a3, 4 // increment dest_buff pointer by 4 + _mod_4_check: + + // Check modulo 2 of the dest_w_bytes, if - then set 2 bytes + bbci a11, 1, _mod_2_check // branch if 1-st bit of dest_w_bytes is clear + s16i a12, a3, 0 // save 16 bits from 16-bit color a12 to dest_buff a3, offset 0 + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + _mod_2_check: + + add a3, a3, a6 // dest_buff + dest_stride + addi.n a5, a5, -1 // decrease the outer loop + and a7, a8, a3 // a7 = dest_buff AND 0x3 (chck if the address is 4-byte aligned) + bnez a5, .outer_loop + + movi.n a2, 1 // return LV_RESULT_OK = 1 + retw.n // return diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb565_esp32s3.S b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb565_esp32s3.S new file mode 100644 index 00000000..3a9fe43c --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb565_esp32s3.S @@ -0,0 +1,404 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// This is LVGL RGB565 simple fill for ESP32S3 processor + + .section .text + .align 4 + .global lv_color_blend_to_rgb565_esp + .type lv_color_blend_to_rgb565_esp,@function +// The function implements the following C code: +// void lv_color_blend_to_rgb565(_lv_draw_sw_blend_fill_dsc_t * dsc); + +// Input params +// +// dsc - a2 + +// typedef struct { +// uint32_t opa; l32i 0 +// void * dst_buf; l32i 4 +// uint32_t dst_w; l32i 8 +// uint32_t dst_h; l32i 12 +// uint32_t dst_stride; l32i 16 +// const void * src_buf; l32i 20 +// uint32_t src_stride; l32i 24 +// const lv_opa_t * mask_buf; l32i 28 +// uint32_t mask_stride; l32i 32 +// } asm_dsc_t; + +lv_color_blend_to_rgb565_esp: + + entry a1, 32 + + l32i.n a3, a2, 4 // a3 - dest_buff + l32i.n a4, a2, 8 // a4 - dest_w in uint16_t + l32i.n a5, a2, 12 // a5 - dest_h in uint16_t + l32i.n a6, a2, 16 // a6 - dest_stride in bytes + l32i.n a7, a2, 20 // a7 - src_buff (color) + l32i.n a8, a7, 0 // a8 - color as value + slli a11, a4, 1 // a11 - dest_w_bytes = sizeof(uint16_t) * dest_w + + // Convert color to rgb656 + l8ui a15, a7, 2 // red + movi.n a14, 0xf8 + and a13, a15, a14 + slli a10, a13, 8 + + l8ui a15, a7, 0 // blue + and a13, a15, a14 + srli a12, a13, 3 + add a10, a10, a12 + + l8ui a15, a7, 1 // green + movi.n a14, 0xfc + and a13, a15, a14 + slli a12, a13, 3 + add a12, a10, a12 // a12 = 16-bit color + + slli a10, a12, 16 + movi.n a13, 0xFFFF0000 + and a10, a10, a13 + or a10, a10, a12 // a10 = 32-bit color (16bit + (16bit << 16)) + + // Check for short lengths + // dest_w should be at least 16, othewise it's not worth using esp32s3 TIE + bgei a4, 16, _esp32s3_implementation // Branch if dest_w is greater than or equal to 16 + j .lv_color_blend_to_rgb565_esp32_body // Jump to esp32 implementation + + _esp32s3_implementation: + + ee.movi.32.q q0, a10, 0 // fill q0 register from a10 by 32 bits + ee.movi.32.q q0, a10, 1 + ee.movi.32.q q0, a10, 2 + ee.movi.32.q q0, a10, 3 + + // Check dest_buff alignment + movi.n a7, 0xf // 0xf alignment mask (16-byte alignment) + and a15, a7, a3 // 16-byte alignment mask AND dest_buff pointer + bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero + + // Check dest_stride alignment + and a15, a7, a6 // 16-byte alignment mask AND dest_stride + bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero + + // Check dest_w_bytes alignment + and a15, a7, a11 // 16-byte alignment mask AND dest_w_bytes + bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero + +//********************************************************************************************************************** + + // all aligned, the most ideal case + + // dest_buff (a3) - 16-byte aligned + // dest_stride (a6) - 16-byte multiple + // dest_w (a4) - 16-byte multiple + + srli a9, a4, 3 // a9 - loop_len = dest_w / 8 + sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes + + .outer_loop_aligned: + + loopnez a9, ._main_loop_aligned // 16 bytes (8 rgb565) in one loop + ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3 + ._main_loop_aligned: + + add a3, a3, a6 // dest_buff + dest_stride + addi.n a5, a5, -1 // decrease the outer loop + bnez a5, .outer_loop_aligned + + movi.n a2, 1 // return LV_RESULT_OK = 1 + retw.n // return + + _unaligned_by_4byte: + + // Check dest_buff alignment + movi.n a7, 0x3 // 0x3 alignment mask (4-byte alignment) + and a15, a7, a3 // 4-byte alignment mask AND dest_buff pointer + bnez a15, _unaligned_by_1byte // branch if a15 not equals to zero + + // Check dest_stride alignment + and a15, a7, a6 // 4-byte alignment mask AND dest_stride pointer + bnez a15, _unaligned_by_1byte // branch if a15 not equals to zero + +//********************************************************************************************************************** + + // either dest_buff or dest_stride is not 16-byte aligned + // dest_w is always 4-byte multiple + // all of the following are 4-byte aligned + + // dest_buff (a3) - 16-byte, or 4-byte aligned + // dest_stride (a6) - 16-byte, or 4-byte multiple + // dest_w (a4) - 4-byte multiple + + sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes + movi.n a7, 0xf // 0xf alignment mask + + .outer_loop_aligned_by_4byte: + + // alignment check + and a15, a7, a3 // 0xf (alignment mask) AND dest_buff pointer + mov a12, a11 // a12 - local_dest_w_bytes = dest_w_bytes + beqz a15, _dest_buff_aligned_by_4byte // branch if a15 equals to zero + + + movi.n a14, 16 // a14 - 16 + sub a15, a14, a15 // a15 = 16 - unalignment (lower 4 bits of dest_buff address) + sub a12, a12, a15 // local_dest_w_bytes = len - (16 - unalignment) + + // keep setting until dest_buff is aligned + // Check modulo 8 of the unalignment, if - then set 8 bytes + bbci a15, 3, _aligning_mod_8_check_4byte // branch if 3-rd bit of unalignment a15 is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 4 bytes + addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes + _aligning_mod_8_check_4byte: + + // Check modulo 4 of the unalignment, if - then set 4 bytes + bbci a15, 2, _aligning_mod_4_check_4byte // branch if 2-nd bit unalignment a15 is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + _aligning_mod_4_check_4byte: + + _dest_buff_aligned_by_4byte: + // Calculate main loop_len + srli a9, a12, 4 // a9 - loop_len = local_dest_w_bytes / 16 + + // Main loop + loopnez a9, ._main_loop_unaligned_by_4byte // 16 bytes (8 rgb565) in one loop + ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3 + ._main_loop_unaligned_by_4byte: + + // Check modulo 8 of the dest_w, if - then set 8 bytes + bbci a12, 3, _aligned_mod_8_check_4byte // branch if 3-rd bit of local_dest_w_bytes a12 is clear + ee.vst.l.64.ip q0, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes + _aligned_mod_8_check_4byte: + + // Check modulo 4 of the dest_w, if - then set 4 bytes + bbci a12, 2, _aligned_mod_4_check_4byte // branch if 2-nd bit of local_dest_w_bytes a12 is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + _aligned_mod_4_check_4byte: + + // Check modulo 2 of the dest_w, if - then set 2 bytes + bbci a12, 1, _aligned_mod_2_check_4byte // branch if 1-st bit of local_dest_w_bytes a12 is clear + s16i a10, a3, 0 // save 16 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + _aligned_mod_2_check_4byte: + + + add a3, a3, a6 // dest_buff + dest_stride + addi.n a5, a5, -1 // decrease the outer loop + bnez a5, .outer_loop_aligned_by_4byte + + movi.n a2, 1 // return LV_RESULT_OK = 1 + retw.n // return + + _unaligned_by_1byte: + +//********************************************************************************************************************** + + // either dest_buff or dest_stride is not 4-byte aligned + // dest_w is always 4-byte multiple + + // dest_buff (a3) - 4-byte, or 1-byte aligned + // dest_stride (a6) - 4-byte, or 1-byte multiple + // dest_w (a4) - 4-byte multiple + + + ee.zero.q q1 // clear q1 + ee.orq q1, q1, q0 // copy q0 to q1 + sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes + movi.n a7, 0xf // 0xf alignment mask + + .outer_loop_aligned_by_1byte: + + // alignment check + and a15, a7, a3 // 0xf (alignment mask) AND dest_buff pointer + mov a12, a11 // a12 - local_dest_w_bytes = dest_w_bytes + beqz a15, _dest_buff_aligned_by_1byte // branch if a15 equals to zero + + + movi.n a14, 16 // a14 - 16 + sub a15, a14, a15 // a15 = 16 - unalignment (lower 4 bits of dest_buff address) + sub a12, a12, a15 // local_dest_w_bytes = len - (16 - unalignment) + + // keep setting until dest_buff is aligned + // Check modulo 8 of the unalignment, if - then set 8 bytes + bbci a15, 3, _aligning_mod_8_check_1byte// branch if 3-rd bit of unalignment a15 is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 4 bytes + addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes + _aligning_mod_8_check_1byte: + + // Check modulo 4 of the unalignment, if - then set 4 bytes + bbci a15, 2, _aligning_mod_4_check_1byte // branch if 2-nd bit unalignment a15 is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + _aligning_mod_4_check_1byte: + + // Check modulo 2 and 1 + // modulo 2 and modulo 1 requires the same action + bbci a15, 1, _aligning_mod_2_check_1byte // branch if 1-st bit unalignment a15 is clear + s16i a10, a3, 0 // save 16 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + _aligning_mod_2_check_1byte: + + bbci a15, 0, _dest_buff_aligned_by_1byte // branch if 0-st bit unalignment a15 is clear + s16i a10, a3, 0 // save 16 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + _dest_buff_aligned_by_1byte: + + // Shift q reg, allowing to set 16-byte unaligned adata + wur.sar_byte a15 // apply unalignment to the SAR_BYTE + ee.src.q q2, q0, q1 // shift concat. of q0 and q1 to q2 by SAR_BYTE amount + + // Calculate main loop_len + srli a9, a12, 4 // a9 - loop_len = local_dest_w_bytes / 16 + + // Main loop + loopnez a9, ._main_loop_unaligned_by_1byte // 16 bytes (8 rgb565) in one loop + ee.vst.128.ip q2, a3, 16 // store 16 bytes from q0 to dest_buff a3 + ._main_loop_unaligned_by_1byte: + + // Firstly check mod 1 and mod 2 - correcting the aligned memory access + // Go back in one Byte, allow to correct after ee.vst.128.ip aligned access + addi a3, a3, -4 + + // Check modulo 2 of the dest_w, if - then set 2 bytes + // set SSSS in 0xSSSS0000 + bbci a12, 1, _aligned_mod_2_check_1byte_corr // branch if 1-st bit of dest_w a12 is clear + srli a14, a10, 16 // shift a10 in 16, allowing s16i (saving of lower 16 bits) + s16i a14, a3, 2 // save 16 bits from a10 to dest_buff a3, offset 2 bytes + + // Check modulo 1 of the dest_w, if - then set 1 byte + // additionally set SS in 0x0000SS00 + bbci a12, 0, _aligned_end // branch if 0-th bit of dest_w a12 is clear + srli a14, a10, 8 // shift a10 in 8, allowing s8i + s8i a14, a3, 1 // save 8 bits from a10 to dest_buff a3, offset 1 byte + j _aligned_end + _aligned_mod_2_check_1byte_corr: + + // Check modulo 1 of the dest_w, if - then set 1 byte + // set SS in 0xSS000000 + bbci a12, 0, _aligned_end // branch if 0-th bit of dest_w a12 is clear + srli a14, a10, 24 // shift a10 in 24, allowing s8i (saving of lower 8 bits) + s8i a14, a3, 3 // save 8 bits from a10 to dest_buff a3, offset 3 bytes + _aligned_end: + + addi a3, a3, 4 // Increase the pointer back, correction for addi a3, a3, -4 + + // Check modulo 8 of the dest_w, if - then set 8 bytes + bbci a12, 3, _aligned_mod_8_check_1byte // branch if 3-rd bit of local_dest_w_bytes a12 is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 8 // increment dest_buff pointer by 4 bytes + _aligned_mod_8_check_1byte: + + // Check modulo 4 of the dest_w, if - then set 4 bytes + bbci a12, 2, _aligned_mod_4_check_1byte // branch if 2-nd bit of local_dest_w_bytes a12 is clear + s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + _aligned_mod_4_check_1byte: + + // Check modulo 2 of the dest_w, if - then set 2 bytes + bbci a12, 1, _aligned_mod_2_check_1byte // branch if 1-st bit of local_dest_w_bytes a12 is clear + s16i a10, a3, 0 // save 16 bits from a10 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + _aligned_mod_2_check_1byte: + + add a3, a3, a6 // dest_buff + dest_stride + addi.n a5, a5, -1 // decrease the outer loop + bnez a5, .outer_loop_aligned_by_1byte + + movi.n a2, 1 // return LV_RESULT_OK = 1 + retw.n // return + + .lv_color_blend_to_rgb565_esp32_body: + + movi.n a8, 0x3 // a8 = 0x3, dest_buff align mask + sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes + + // cache init + // Prepare main loop length and dest_w_bytes + srli a9, a4, 4 // a9 = loop_len = dest_w / 8, calculate main loop_len for original dest_w + slli a11, a4, 1 // a11 = dest_w_bytes = sizeof(uint16_t) * dest_w + addi a4, a4, -1 // a4-- (decrement a4) + s32i.n a9, a1, 0 // cache.orig.loop_len + s32i.n a11, a1, 4 // cache.orig.dest_w_bytes + + // Prepare decreased main loop length and dest_w_bytes + srli a9, a4, 4 // a9 = loop_len = dest_w / 8, calculate main loop_len for dest_w - 1 + slli a11, a4, 1 // a11 = dest_w_bytes = sizeof(uint16_t) * (dest_w - 1) + s32i.n a9, a1, 8 // cache.decr.loop_len + s32i.n a11, a1, 12 // cache.decr.dest_w_bytes + and a7, a8, a3 // a7 = dest_buff AND 0x3 (chck if the address is 4-byte aligned) + + .outer_loop: + + // Check if the des_buff is 2-byte aligned + beqz a7, _dest_buff_2_byte_aligned // branch if a7 is equal to zero + s16i a12, a3, 0 // save 16 bits from 16-bit color a12 to dest_buff a3, offset 0 + l32i.n a9, a1, 8 // a9 = load cache.decr.loop_len + l32i.n a11, a1, 12 // a11 = load cache.decr.dest_w_bytes + addi.n a3, a3, 2 // increment dest_buff pointer by 2 + j _dest_buff_unaligned + _dest_buff_2_byte_aligned: + + l32i.n a9, a1, 0 // a11 = load cache.orig.loop_len + l32i.n a11, a1, 4 // a11 = load cache.orig.dest_w_bytes + + _dest_buff_unaligned: + + // Run main loop which sets 16 bytes in one loop run + loopnez a9, ._main_loop + s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0 + s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4 + s32i.n a10, a3, 8 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 8 + s32i.n a10, a3, 12 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 12 + s32i.n a10, a3, 16 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 16 + s32i.n a10, a3, 20 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 20 + s32i.n a10, a3, 24 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 24 + s32i.n a10, a3, 28 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 28 + addi.n a3, a3, 32 // increment dest_buff pointer by 32 + ._main_loop: + + // Finish the remaining bytes out of the loop + // Check modulo 8 of the dest_w_bytes, if - then set 16 bytes + bbci a11, 4, _mod_16_check // branch if 2-nd bit of dest_w_bytes is clear + s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0 + s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4 + s32i.n a10, a3, 8 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 8 + s32i.n a10, a3, 12 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 12 + addi.n a3, a3, 16 // increment dest_buff pointer by 16 + _mod_16_check: + + // Finish the remaining bytes out of the loop + // Check modulo 8 of the dest_w_bytes, if - then set 8 bytes + bbci a11, 3, _mod_8_check // branch if 2-nd bit of dest_w_bytes is clear + s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0 + s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4 + addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes + _mod_8_check: + + // Check modulo 4 of the dest_w_bytes, if - then set 4 bytes + bbci a11, 2, _mod_4_check // branch if 2-nd bit of dest_w_bytes is clear + s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0 + addi.n a3, a3, 4 // increment dest_buff pointer by 4 + _mod_4_check: + + // Check modulo 2 of the dest_w_bytes, if - then set 2 bytes + bbci a11, 1, _mod_2_check // branch if 1-st bit of dest_w_bytes is clear + s16i a12, a3, 0 // save 16 bits from 16-bit color a12 to dest_buff a3, offset 0 + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + _mod_2_check: + + add a3, a3, a6 // dest_buff + dest_stride + addi.n a5, a5, -1 // decrease the outer loop + and a7, a8, a3 // a7 = dest_buff AND 0x3 (chck if the address is 4-byte aligned) + bnez a5, .outer_loop + + movi.n a2, 1 // return LV_RESULT_OK = 1 + retw.n // return diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb888_esp32.S b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb888_esp32.S new file mode 100644 index 00000000..467b5348 --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb888_esp32.S @@ -0,0 +1,105 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// This is LVGL RGB888 simple fill for ESP32 processor + + .section .text + .align 4 + .global lv_color_blend_to_rgb888_esp + .type lv_color_blend_to_rgb888_esp,@function +// The function implements the following C code: +// void lv_color_blend_to_rgb888(_lv_draw_sw_blend_fill_dsc_t * dsc); + +// Input params +// +// dsc - a2 + +// typedef struct { +// uint32_t opa; l32i 0 +// void * dst_buf; l32i 4 +// uint32_t dst_w; l32i 8 +// uint32_t dst_h; l32i 12 +// uint32_t dst_stride; l32i 16 +// const void * src_buf; l32i 20 +// uint32_t src_stride; l32i 24 +// const lv_opa_t * mask_buf; l32i 28 +// uint32_t mask_stride; l32i 32 +// } asm_dsc_t; + +lv_color_blend_to_rgb888_esp: + + entry a1, 32 + + l32i.n a3, a2, 4 // a3 - dest_buff + l32i.n a4, a2, 8 // a4 - dest_w in uint24_t + l32i.n a5, a2, 12 // a5 - dest_h in uint16_t + l32i.n a6, a2, 16 // a6 - dest_stride in bytes + l32i.n a7, a2, 20 // a7 - src_buff (color) + l32i.n a8, a7, 0 // a8 - color as value + + // a11 - dest_w_bytes = sizeof(uint24_t) * dest_w = 3 * a4 + slli a11, a4, 1 // a11 - dest_w_bytes = sizeof(uint16_t) * dest_w + add a11, a11, a4 // a11 - dest_w_bytes = a11 + a4 + + // Prepare register combinations + // a13 - 0xBBRRGGBB a14 - 0xGGBBRRGG a15 - 0xRRGGBBRR + l8ui a13, a7, 0 // blue 000B + slli a13, a13, 24 // shift to B000 + or a13, a13, a8 // a13 BRGB + + srli a14, a8, 8 // a14 00RG + slli a10, a8, 16 // a10 GB00 + or a14, a14, a10 // a14 GBRG + + slli a15, a8, 8 // a15 RGB0 + l8ui a10, a7, 2 // a7 000R + or a15, a15, a10 // a15 RGBR + + sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes + + // Prepare main loop length and dest_w_bytes + srli a9, a4, 2 // a9 = loop_len = dest_w / 4, calculate main loop_len for original dest_w + movi.n a8, 0x3 // a8 = 0x3, remainder mask + and a10, a4, a8 // a10 - remainder after division by 4 = a4 and 0x3 + + .outer_loop: + + // Run main loop which sets 12 bytes (4 rgb888) in one loop run + loopnez a9, ._main_loop + s32i.n a13, a3, 0 // save 32 bits from 32-bit color a13 to dest_buff a3, offset 0 + s32i.n a14, a3, 4 // save 32 bits from 32-bit color a14 to dest_buff a3, offset 4 + s32i.n a15, a3, 8 // save 32 bits from 32-bit color a15 to dest_buff a3, offset 8 + addi.n a3, a3, 12 // increment dest_buff pointer by 12 + ._main_loop: + + bnei a10, 0x3, _less_than_3 // branch if less than 3 values left + s32i.n a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes + s32i.n a14, a3, 4 // save 32 bits from a14 to dest_buff a3, offset 4 bytes + s8i a15, a3, 8 // save 8 bits from a15 to dest_buff a3, offset 8 bytes + addi.n a3, a3, 9 // increment dest_buff pointer by 9 bytes + j _less_than_1 + _less_than_3: + + bnei a10, 0x2, _less_than_2 // branch if less than 2 values left + s32i.n a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes + s16i a14, a3, 4 // save 16 bits from a14 to dest_buff a3, offset 4 bytes + addi.n a3, a3, 6 // increment dest_buff pointer by 6 bytes + j _less_than_1 + _less_than_2: + + bnei a10, 0x1, _less_than_1 // branch if less than 1 value left + s16i a13, a3, 0 // save 16 bits from a13 to dest_buff a3, offset 0 bytes + s8i a15, a3, 2 // save 8 bits from a15 to dest_buff a3, offset 2 bytes + addi.n a3, a3, 3 // increment dest_buff pointer by 3 bytes + _less_than_1: + + add a3, a3, a6 // dest_buff + dest_stride + addi.n a5, a5, -1 // decrease the outer loop + and a7, a8, a3 // a7 = dest_buff AND 0x3 (check if the address is 4-byte aligned) + bnez a5, .outer_loop + + movi.n a2, 1 // return LV_RESULT_OK = 1 + retw.n // return diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb888_esp32s3.S b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb888_esp32s3.S new file mode 100644 index 00000000..955db4d0 --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_color_blend_to_rgb888_esp32s3.S @@ -0,0 +1,346 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// This is LVGL RGB888 simple fill for ESP32S3 processor + + .section .text + .align 4 + .global lv_color_blend_to_rgb888_esp + .type lv_color_blend_to_rgb888_esp,@function +// The function implements the following C code: +// void lv_color_blend_to_rgb888(_lv_draw_sw_blend_fill_dsc_t * dsc); + +// Input params +// +// dsc - a2 + +// typedef struct { +// uint32_t opa; l32i 0 +// void * dst_buf; l32i 4 +// uint32_t dst_w; l32i 8 +// uint32_t dst_h; l32i 12 +// uint32_t dst_stride; l32i 16 +// const void * src_buf; l32i 20 +// uint32_t src_stride; l32i 24 +// const lv_opa_t * mask_buf; l32i 28 +// uint32_t mask_stride; l32i 32 +// } asm_dsc_t; + +lv_color_blend_to_rgb888_esp: + + entry a1, 32 + + l32i.n a3, a2, 4 // a3 - dest_buff + l32i.n a4, a2, 8 // a4 - dest_w in uint24_t + l32i.n a5, a2, 12 // a5 - dest_h in uint16_t + l32i.n a6, a2, 16 // a6 - dest_stride in bytes + l32i.n a7, a2, 20 // a7 - src_buff (color) + l32i.n a8, a7, 0 // a8 - color as value + + // a11 - dest_w_bytes = sizeof(uint24_t) * dest_w = 3 * a4 + slli a11, a4, 1 // a11 - dest_w_bytes = 2 * dest_w + add a11, a11, a4 // a11 - dest_w_bytes = a11 + a4 + + // Prepare register combinations + // a13 - 0xBBRRGGBB a14 - 0xGGBBRRGG a15 - 0xRRGGBBRR + l8ui a13, a7, 0 // blue 000B + slli a13, a13, 24 // shift to B000 + or a13, a13, a8 // a13 BRGB + + srli a14, a8, 8 // a14 00RG + slli a10, a8, 16 // a10 GB00 + or a14, a14, a10 // a14 GBRG + + slli a15, a8, 8 // a15 RGB0 + l8ui a10, a7, 2 // a7 000R + or a15, a15, a10 // a15 RGBR + + sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes + + // Check for short lengths + // dest_w should be at least 12, othewise it's not worth using esp32s3 TIE + bgei a4, 12, _esp32s3_implementation // Branch if dest_w is greater than or equal to 12 + j .lv_color_blend_to_rgb888_esp32_body // Jump to esp32 implementation + + _esp32s3_implementation: + + // Prepare q registers for the main loop + ee.movi.32.q q3, a13, 0 // fill q3 register from a13 by 32 bits + ee.movi.32.q q3, a14, 1 // fill q3 register from a14 by 32 bits + ee.movi.32.q q3, a15, 2 // fill q3 register from a15 by 32 bits + ee.movi.32.q q3, a13, 3 // fill q3 register from a13 by 32 bits + + ee.movi.32.q q4, a14, 0 // fill q4 register from a14 by 32 bits + ee.movi.32.q q4, a15, 1 // fill q4 register from a15 by 32 bits + ee.movi.32.q q4, a13, 2 // fill q4 register from a13 by 32 bits + ee.movi.32.q q4, a14, 3 // fill q4 register from a14 by 32 bits + + ee.movi.32.q q5, a15, 0 // fill q5 register from a15 by 32 bits + ee.movi.32.q q5, a13, 1 // fill q5 register from a13 by 32 bits + ee.movi.32.q q5, a14, 2 // fill q5 register from a14 by 32 bits + ee.movi.32.q q5, a15, 3 // fill q5 register from a15 by 32 bits + + .outer_loop_aligned: + + // q registers will get shifted and clobbered, need to reinitialize them before using them again + // Clear q registers + ee.zero.q q0 // clear q0 + ee.zero.q q1 // clear q1 + ee.zero.q q2 // clear q2 + + // Reinitialize q registers + ee.orq q0, q0, q3 // copy q3 to q0 + ee.orq q1, q1, q4 // copy q4 to q1 + ee.orq q2, q2, q5 // copy q5 to q2 + + // alignment check + extui a8, a3, 0, 4 // address_alignment (a8) = dest_buff address (a3) AND 0xf + + movi.n a12, 16 // a12 = 16 + mov.n a2, a8 // unalignment (a2) = a8 + // following instruction is here to avoid branching + // need to adjust a8 == 0 to 16 to make the unalignment computation work + moveqz a2, a12, a8 // modified unalignment (a2) = 16 if unalignment (a8) == 0 + + sub a2, a12, a2 // a2 = 16 - unalignment (lower 4 bits of dest_buff address) + sub a10, a11, a2 // local_dest_w_bytes = len - (16 - unalignment) + + movi.n a12, 48 // a12 = 48 (main loop copies 48 bytes) + quou a9, a10, a12 // main_loop counter (a9) = local_dest_w_bytes (a10) DIV 48 (a12) + remu a10, a10, a12 // a10 = local_dest_w_bytes (a10) MOD 48 (a12) + + beqz a8, _dest_buff_aligned // If already aligned, skip aligning + + movi.n a7, unalignment_table // Load unalignment_table address + + addx4 a7, a8, a7 // jump_table handle (a7) = offset (a8) * 4 + jump_table address (a7) + l32i a7, a7, 0 // Load target address from jump table + jx a7 // Jump to the corresponding handler + + +// a13 - 0xBBRRGGBB a14 - 0xGGBBRRGG a15 - 0xRRGGBBRR +handle_0: +handle_1: + s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte + s16i a14, a3, 0 // save 16 bits from a14 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + s32i a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + ee.vst.l.64.ip q1, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes + j _shift_q_regs +handle_2: + s16i a13, a3, 0 // save 16 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + s32i a15, a3, 0 // save 32 bits from a15 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + ee.vst.l.64.ip q0, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes + j _shift_q_regs +handle_3: + s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte + s32i a14, a3, 0 // save 32 bits from a14 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + ee.vst.l.64.ip q2, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes + j _shift_q_regs +handle_4: + s32i a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + ee.vst.l.64.ip q1, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes + j _shift_q_regs +handle_5: + s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte + s16i a14, a3, 0 // save 16 bits from a14 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + ee.vst.l.64.ip q0, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes + j _shift_q_regs +handle_6: + s16i a13, a3, 0 // save 16 bits from a13 to dest_buff a3, offset 0 byte + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + ee.vst.l.64.ip q2, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes + j _shift_q_regs +handle_7: + s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte + ee.vst.l.64.ip q1, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes + j _shift_q_regs +handle_8: + ee.vst.l.64.ip q0, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes + j _shift_q_regs + +handle_9: + s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte + s16i a14, a3, 0 // save 16 bits from a14 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + s32i a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + j _shift_q_regs +handle_10: + s16i a13, a3, 0 // save 16 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + s32i a15, a3, 0 // save 32 bits from a15 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + j _shift_q_regs +handle_11: + s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte + s32i a14, a3, 0 // save 32 bits from a14 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + j _shift_q_regs +handle_12: + s32i a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + j _shift_q_regs +handle_13: + s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte + s16i a14, a3, 0 // save 16 bits from a14 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + j _shift_q_regs +handle_14: + s16i a13, a3, 0 // save 16 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + j _shift_q_regs +handle_15: + s8i a13, a3, 0 // save 8 bits from a13 to dest_buff a3, offset 0 bytes + addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte + j _shift_q_regs + +.align 4 + +unalignment_table: + .word handle_0 // Case 0: Dummy case for easier address computation + .word handle_1 // Case 1: Align 15 bytes + .word handle_2 // Case 2: Align 14 bytes + .word handle_3 // Case 3: Align 13 bytes + .word handle_4 // Case 4: Align 12 bytes + .word handle_5 // Case 5: Align 11 bytes + .word handle_6 // Case 6: Align 10 bytes + .word handle_7 // Case 7: Align 9 bytes + .word handle_8 // Case 8: Align 8 bytes + .word handle_9 // Case 9: Align 7 bytes + .word handle_10 // Case 10: Align 6 bytes + .word handle_11 // Case 11: Align 5 bytes + .word handle_12 // Case 12: Align 4 bytes + .word handle_13 // Case 13: Align 3 bytes + .word handle_14 // Case 14: Align 2 bytes + .word handle_15 // Case 15: Align 1 byte + + + _shift_q_regs: + wur.sar_byte a2 // apply unalignment to the SAR_BYTE + ee.src.q q0, q0, q1 // shift concat. of q0 and q1 to q0 by SAR_BYTE amount + ee.src.q q1, q1, q2 // shift concat. of q1 and q2 to q1 by SAR_BYTE amount + ee.src.q q2, q2, q3 // shift concat. of q2 and q3 to q2 by SAR_BYTE amount + + _dest_buff_aligned: + loopnez a9, ._main_loop_aligned // 48 bytes (16 rgb888) in one loop + ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3 + ee.vst.128.ip q1, a3, 16 // store 16 bytes from q1 to dest_buff a3 + ee.vst.128.ip q2, a3, 16 // store 16 bytes from q2 to dest_buff a3 + ._main_loop_aligned: + + // Check modulo 32 of the unalignment, if - then set 32 bytes + bbci a10, 5, .lt_32 // branch if 5-th bit of local_dest_w_bytes a10 is clear + ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3 + ee.vst.128.ip q1, a3, 16 // store 16 bytes from q1 to dest_buff a3 + + ee.srci.2q q0, q1, 1 // shift q0 register to have next bytes to store ready from LSB + .lt_32: + + // Check modulo 16 of the unalignment, if - then set 16 bytes + bbci a10, 4, .lt_16 // branch if 4-th bit of local_dest_w_bytes a10 is clear + ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3 + + ee.srci.2q q0, q1, 0 // shift q0 register to have next bytes to store ready from LSB + .lt_16: + + // Check modulo 8 of the unalignment, if - then set 8 bytes + bbci a10, 3, .lt_8 + ee.vst.l.64.ip q0, a3, 8 // store 8 bytes from q0 to dest_buff a3 + + ee.srci.2q q0, q1, 1 // shift q0 register to have next bytes to store ready from LSB + .lt_8: + + // Check modulo 4 of the unalignment, if - then set 4 bytes + bbci a10, 2, .lt_4 + ee.movi.32.a q0, a2, 0 // move lowest 32 bits of q0 to a2 + s32i.n a2, a3, 0 // save 32 bits from a2 to dest_buff a3, offset 0 + addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes + + ee.srci.2q q0, q1, 0 // shift q0 register to have next bytes to store ready from LSB + .lt_4: + + // Check modulo 2 of the unalignment, if - then set 2 bytes + bbci a10, 1, .lt_2 + ee.movi.32.a q0, a2, 0 // move lowest 32 bits of q0 to a2 + s16i a2, a3, 0 // save 16 bits from a2 to dest_buff a3, offset 0 + addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes + + ee.srci.2q q0, q1, 1 // shift q0 register to have next bytes to store ready from LSB + .lt_2: + + // Check modulo 1 of the unalignment, if - then set 1 byte + bbci a10, 0, .lt_1 + ee.movi.32.a q0, a2, 0 // move lowest 32 bits of q0 to a2 + s8i a2, a3, 0 // save 8 bits from a2 to dest_buff a3, offset 0 + addi.n a3, a3, 1 // increment dest_buff pointer by 1 byte + .lt_1: + + add a3, a3, a6 // dest_buff + dest_stride + addi.n a5, a5, -1 // decrease the outer loop + bnez a5, .outer_loop_aligned + + movi.n a2, 1 // return LV_RESULT_OK = 1 + retw.n // return + + .lv_color_blend_to_rgb888_esp32_body: + + // Prepare main loop length and dest_w_bytes + srli a9, a4, 2 // a9 = loop_len = dest_w / 4, calculate main loop_len for original dest_w + movi.n a8, 0x3 // a8 = 0x3, remainder mask + and a10, a4, a8 // a10 - remainder after division by 4 = a4 & 0x3 + + .outer_loop: + + // Run main loop which sets 12 bytes (4 rgb888) in one loop run + loopnez a9, ._main_loop + s32i.n a13, a3, 0 // save 32 bits from 32-bit color a13 to dest_buff a3, offset 0 + s32i.n a14, a3, 4 // save 32 bits from 32-bit color a14 to dest_buff a3, offset 4 + s32i.n a15, a3, 8 // save 32 bits from 32-bit color a15 to dest_buff a3, offset 8 + addi.n a3, a3, 12 // increment dest_buff pointer by 12 + ._main_loop: + + bnei a10, 0x3, _less_than_3 // branch if less than 3 values left + s32i.n a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes + s32i.n a14, a3, 4 // save 32 bits from a14 to dest_buff a3, offset 4 bytes + s8i a15, a3, 8 // save 8 bits from a15 to dest_buff a3, offset 8 bytes + addi.n a3, a3, 9 // increment dest_buff pointer by 9 bytes + j _less_than_1 + _less_than_3: + + bnei a10, 0x2, _less_than_2 // branch if less than 2 values left + s32i.n a13, a3, 0 // save 32 bits from a13 to dest_buff a3, offset 0 bytes + s16i a14, a3, 4 // save 16 bits from a14 to dest_buff a3, offset 4 bytes + addi.n a3, a3, 6 // increment dest_buff pointer by 6 bytes + j _less_than_1 + _less_than_2: + + bnei a10, 0x1, _less_than_1 // branch if less than 1 value left + s16i a13, a3, 0 // save 16 bits from a13 to dest_buff a3, offset 0 bytes + s8i a15, a3, 2 // save 8 bits from a15 to dest_buff a3, offset 2 bytes + addi.n a3, a3, 3 // increment dest_buff pointer by 3 bytes + _less_than_1: + + add a3, a3, a6 // dest_buff + dest_stride + addi.n a5, a5, -1 // decrease the outer loop + and a7, a8, a3 // a7 = dest_buff AND 0x3 (chck if the address is 4-byte aligned) + bnez a5, .outer_loop + + movi.n a2, 1 // return LV_RESULT_OK = 1 + retw.n // return diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_macro_memcpy.S b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_macro_memcpy.S new file mode 100644 index 00000000..6a5215d0 --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_macro_memcpy.S @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// Memcpy macros for modulo checking +// After running the main loop, there is need to check remaining bytes to be copied out of the main loop +// Macros work with both, aligned and unaligned (4-byte boundary) memories +// but performance is significantly lower when using unaligned memory, because of the unaligned memory access exception + + +// Macro for checking modulo 8 + .macro macro_memcpy_mod_8 src_buf, dest_buf, condition, x1, x2, JUMP_TAG + // Check modulo 8 of the \condition, if - then copy 8 bytes + bbci \condition, 3, ._mod_8_check_\JUMP_TAG // Branch if 3-rd bit of \condition is clear + l32i.n \x1, \src_buf, 0 // Load 32 bits from \src_buff to \x1, offset 0 + l32i.n \x2, \src_buf, 4 // Load 32 bits from \src_buff to \x2, offset 4 + s32i.n \x1, \dest_buf, 0 // Save 32 bits from \x1 to \dest_buff, offset 0 + s32i.n \x2, \dest_buf, 4 // Save 32 bits from \x2 to \dest_buff, offset 4 + addi.n \src_buf, \src_buf, 8 // Increment \src_buff pointer by 8 + addi.n \dest_buf, \dest_buf, 8 // Increment \dest_buff pointer 8 + ._mod_8_check_\JUMP_TAG: +.endm // macro_memcpy_mod_8 + + +// Macro for checking modulo 4 + .macro macro_memcpy_mod_4 src_buf, dest_buf, condition, x1, JUMP_TAG + // Check modulo 4 of the \condition, if - then copy 4 bytes + bbci \condition, 2, ._mod_4_check_\JUMP_TAG // Branch if 2-nd bit of \condition is clear + l32i.n \x1, \src_buf, 0 // Load 32 bits from \src_buff to \x1, offset 0 + addi.n \src_buf, \src_buf, 4 // Increment \src_buff pointer by 4 + s32i.n \x1, \dest_buf, 0 // Save 32 bits from \x1 to \dest_buff, offset 0 + addi.n \dest_buf, \dest_buf, 4 // Increment \dest_buff pointer 4 + ._mod_4_check_\JUMP_TAG: +.endm // macro_memcpy_mod_4 + + +// Macro for checking modulo 2 + .macro macro_memcpy_mod_2 src_buf, dest_buf, condition, x1, JUMP_TAG + // Check modulo 2 of the \condition, if - then copy 2 bytes + bbci \condition, 1, ._mod_2_check_\JUMP_TAG // Branch if 1-st bit of \condition is clear + l16ui \x1, \src_buf, 0 // Load 16 bits from \src_buff to \x1, offset 0 + addi.n \src_buf, \src_buf, 2 // Increment \src_buff pointer by 2 + s16i \x1, \dest_buf, 0 // Save 16 bits from \x1 to \dest_buff, offset 0 + addi.n \dest_buf, \dest_buf, 2 // Increment \dest_buff pointer 2 + ._mod_2_check_\JUMP_TAG: +.endm // macro_memcpy_mod_2 + + +// Macro for checking modulo 1 + .macro macro_memcpy_mod_1 src_buf, dest_buf, condition, x1, JUMP_TAG + // Check modulo 1 of the \condition, if - then copy 1 byte + bbci \condition, 0, ._mod_1_check_\JUMP_TAG // Branch if 0-th bit of \condition is clear + l8ui \x1, \src_buf, 0 // Load 8 bits from \src_buff to \x1, offset 0 + addi.n \src_buf, \src_buf, 1 // Increment \src_buff pointer by 1 + s8i \x1, \dest_buf, 0 // Save 8 bits from \x1 to \dest_buff, offset 0 + addi.n \dest_buf, \dest_buf, 1 // Increment \dest_buff pointer 1 + ._mod_1_check_\JUMP_TAG: +.endm // macro_memcpy_mod_1 diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb565_blend_normal_to_rgb565_esp32.S b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb565_blend_normal_to_rgb565_esp32.S new file mode 100644 index 00000000..eb08cd87 --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb565_blend_normal_to_rgb565_esp32.S @@ -0,0 +1,264 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "lv_macro_memcpy.S" // Memcpy macros + +// This is LVGL RGB565 image blend to RGB565 for ESP32 processor + + .section .text + .align 4 + .global lv_rgb565_blend_normal_to_rgb565_esp + .type lv_rgb565_blend_normal_to_rgb565_esp,@function +// The function implements the following C code: +// void rgb565_image_blend(_lv_draw_sw_blend_image_dsc_t * dsc); + +// Input params +// +// dsc - a2 + +// typedef struct { +// uint32_t opa; l32i 0 +// void * dst_buf; l32i 4 +// uint32_t dst_w; l32i 8 +// uint32_t dst_h; l32i 12 +// uint32_t dst_stride; l32i 16 +// const void * src_buf; l32i 20 +// uint32_t src_stride; l32i 24 +// const lv_opa_t * mask_buf; l32i 28 +// uint32_t mask_stride; l32i 32 +// } asm_dsc_t; + +lv_rgb565_blend_normal_to_rgb565_esp: + + entry a1, 32 + l32i.n a3, a2, 4 // a3 - dest_buff + l32i.n a4, a2, 8 // a4 - dest_w in uint16_t + l32i.n a5, a2, 12 // a5 - dest_h in uint16_t + l32i.n a6, a2, 16 // a6 - dest_stride in bytes + l32i.n a7, a2, 20 // a7 - src_buff + l32i.n a8, a2, 24 // a8 - src_stride in bytes + slli a11, a4, 1 // a11 - dest_w_bytes = sizeof(uint16_t) * dest_w + + // No need to convert any colors here, we are copying from rgb565 to rgb565 + + // Check dest_w length + bltui a4, 8, _matrix_width_check // Branch if dest_w (a4) is lower than 8 + + // Check memory alignment and input parameters lengths and decide which implementation to use + movi.n a10, 0x3 // a10 = 0x3 alignment mask (4-byte alignment) + or a15, a7, a3 // a15 = src_buff (a7) OR dest_buff (a3) + or a15, a15, a6 // a15 = a15 OR dest_stride (a6) + or a15, a15, a8 // a15 = a15 OR src_stride (a8) + or a15, a15, a11 // a15 = a15 OR dest_w_bytes (a11) + and a15, a15, a10 // a15 = a15 AND alignment mask (a10) + bnez a15, _alignment_check // Branch if a15 not equals to zero + +//********************************************************************************************************************** + + // The most ideal case - both arrays aligned, both strides and dest_w are multiples of 4 + + // dest_buff (a3) - 4-byte aligned + // src_buff (a7) - 4-byte aligned + // dest_stride (a6) - 4-byte multiple + // src_stride (a8) - 4-byte multiple + // dest_w (a4) - 4-byte multiple + + srli a9, a4, 3 // a9 - loop_len = dest_w / 8 + // Convert strides to matrix paddings + sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11) + sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11) + + .outer_loop_align: + + // Run main loop which copies 16 bytes (8 RGB565 pixels) in one loop run + loopnez a9, ._main_loop_aligned + l32i.n a15, a7, 0 // Load 32 bits from src_buff a7 to a15, offset 0 + l32i.n a14, a7, 4 // Load 32 bits from src_buff a7 to a14, offset 4 + l32i.n a13, a7, 8 // Load 32 bits from src_buff a7 to a13, offset 8 + l32i.n a12, a7, 12 // Load 32 bits from src_buff a7 to a12, offset 12 + s32i.n a15, a3, 0 // Save 32 bits from a15 to dest_buff a3, offset 0 + s32i.n a14, a3, 4 // Save 32 bits from a15 to dest_buff a3, offset 4 + s32i.n a13, a3, 8 // Save 32 bits from a15 to dest_buff a3, offset 8 + s32i.n a12, a3, 12 // Save 32 bits from a15 to dest_buff a3, offset 12 + addi.n a7, a7, 16 // Increment src_buff pointer a7 by 16 + addi.n a3, a3, 16 // Increment dest_buff pointer a3 by 16 + ._main_loop_aligned: + + // Finish the remaining bytes out of the main loop + + // Check modulo 8 of the dest_w_bytes (a11), if - then copy 8 bytes (4 RGB565 pixels) + // src_buff a7, dest_buff a3, dest_w_bytes a11, copy registers a14 a15 + macro_memcpy_mod_8 a7, a3, a11, a14, a15 __LINE__ + + // Check modulo 4 of the dest_w_bytes (a11), if - then copy 4 bytes (2 RGB565 pixels) + // src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15 + macro_memcpy_mod_4 a7, a3, a11, a15 __LINE__ + + // Check modulo 2 of the dest_w_bytes (a11), if - then copy 2 bytes (1 RGB565 pixel) + // src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15 + macro_memcpy_mod_2 a7, a3, a11, a15 __LINE__ + + // Check modulo 1 of the dest_w_bytes (a11), if - then copy 1 byte (1/2 RGB565 pixel) + // src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15 + macro_memcpy_mod_1 a7, a3, a11, a15 __LINE__ + + add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6) + add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8) + addi.n a5, a5, -1 // Decrease the outer loop + bnez a5, .outer_loop_align + + movi.n a2, 1 // Return LV_RESULT_OK = 1 + retw.n // Return + + +//********************************************************************************************************************** + + // The most general case - at leas one array is not aligned, or one parameter is not multiple of 4 + _alignment_check: + + // dest_buff (a3) - 4-byte aligned, or not + // src_buff (a7) - 4-byte aligned, or not + // dest_stride (a6) - 4-byte multiple, or not + // src_stride (a8) - 4-byte multiple, or not + // dest_w (a4) - 4-byte multiple, or not + + // Convert strides to matrix paddings + sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11) + sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11) + + .outer_loop_unalign: + + extui a13, a3, 0, 2 // Get last two bits of the dest_buff address a3, to a13 + movi.n a15, 4 // Move 4 to a15, for calculation of the destination alignment loop + sub a14, a15, a13 // Calculate destination alignment loop length (a14 = 4 - a13) + + // In case of the dest_buff a3 being already aligned (for example by matrix padding), correct a14 value, + // to prevent the destination aligning loop to run 4 times (to prevent aligning already aligned memory) + moveqz a14, a13, a13 // If a13 is zero, move a13 to a14, move 0 to a14 + + sub a10, a11, a14 // Get the dest_w_bytes after the aligning loop + srli a9, a10, 4 // Calculate main loop len (a9 = dest_w_bytes_local / 16) + + // Run dest_buff aligning loop byte by byte + loopnez a14, ._dest_aligning_loop + l8ui a15, a7, 0 // Load 8 bits from src_buff a7 to a15, offset 0 + addi.n a7, a7, 1 // Increment src_buff pointer a7 by 1 + s8i a15, a3, 0 // Save 8 bits from a15 to dest_buff a3, offset 0 + addi.n a3, a3, 1 // Increment dest_buff pointer a3 by 1 + ._dest_aligning_loop: + + // Destination is aligned, source is unaligned + + // For more information about this implementation, see chapter 3.3.2 Shifts and the Shift Amount Register (SAR) + // in Xtensa Instruction Set Architecture (ISA) Reference Manual + + ssa8l a7 // Set SAR_BYTE from src_buff a7 unalignment + extui a4, a7, 0, 2 // Get last 2 bits of the src_buff, a4 = src_buff_unalignment + sub a7, a7, a4 // "align" the src_buff a7, to 4-byte boundary by decreasing it's pointer to the nearest aligned boundary + + // First preload for the loopnez cycle + l32i.n a15, a7, 0 // Load 32 bits from 4-byte aligned src_buff a7 to a15, offset 0 + + // Run main loop which copies 16 bytes (8 RGB565 pixels) in one loop run + loopnez a9, ._main_loop_unalign + l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4 + l32i.n a13, a7, 8 // Load 32 bits from 4-byte aligned src_buff a7 to a13, offset 8 + src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15 + s32i.n a15, a3, 0 // Save 32 bits from shift-corrected a15 to dest_buff a3, offset 0 + l32i.n a12, a7, 12 // Load 32 bits from 4-byte aligned src_buff a7 to a12, offset 12 + src a14, a13, a14 // Concatenate a13 and a14 and shift by SAR_BYTE amount to a14 + s32i.n a14, a3, 4 // Save 32 bits from shift-corrected a14 to dest_buff a3, offset 4 + l32i.n a15, a7, 16 // Load 32 bits from 4-byte aligned src_buff a7 to a15, offset 16 + src a13, a12, a13 // Concatenate a12 and a13 and shift by SAR_BYTE amount to a13 + s32i.n a13, a3, 8 // Save 32 bits from shift-corrected a13 to dest_buff a3, offset 8 + addi.n a7, a7, 16 // Increment src_buff pointer a7 by 16 + src a12, a15, a12 // Concatenate a15 and a12 and shift by SAR_BYTE amount to a12 + s32i.n a12, a3, 12 // Save 32 bits from shift-corrected a12 to dest_buff a3, offset 12 + addi.n a3, a3, 16 // Increment dest_buff pointer a3 by 16 + ._main_loop_unalign: + + // Finish the remaining bytes out of the loop + // Check modulo 8 of the dest_w_bytes_local (a10), if - then copy 8 bytes + bbci a10, 3, _mod_8_check // Branch if 3-rd bit of dest_w_bytes_local is clear + l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4 + l32i.n a13, a7, 8 // Load 32 bits from 4-byte aligned src_buff a7 to a13, offset 8 + src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15 (value in a15 is already prepared from previous steps) + s32i.n a15, a3, 0 // Save 32 bits from shift-corrected a15 to dest_buff a3, offset 0 + addi.n a7, a7, 8 // Increment src_buff pointer a7 by 8 + src a14, a13, a14 // Concatenate a13 and a14 and shift by SAR_BYTE amount to a14 + s32i.n a14, a3, 4 // Save 32 bits from shift-corrected a14 to dest_buff a3, offset 4 + addi.n a3, a3, 8 // Increment dest_buff pointer a3 by 8 + mov a15, a13 // Prepare a15 for the next steps (copy a13 to a15) + _mod_8_check: + + // Check modulo 4 of the dest_w_bytes_local (a10), if - then copy 4 bytes + bbci a10, 2, _mod_4_check // Branch if 2-nd bit of dest_w_bytes_local is clear + l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4 + addi.n a7, a7, 4 // Increment src_buff pointer a7 by 4 + src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15 (value in a15 is already prepared from previous steps) + s32i.n a15, a3, 0 // Save 32 bits from shift-corrected a15 to dest_buff a3, offset 0 + addi.n a3, a3, 4 // Increment dest_buff pointer a3 by 4 + mov a15, a14 // Prepare a15 for the next steps (copy a14 to a15) + _mod_4_check: + + extui a13, a10, 0, 2 // Get the last 2 bytes of the dest_w_bytes_local (a10), a13 = a10[1:0], to find out how many bytes are needs copied and to increase src and dest pointer accordingly + beqz a13, _mod_1_2_check // Branch if a13 equal to zero, E.G. if there are no bytes to be copied + l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4 + l32i.n a12, a3, 0 // Get dest_buff value: Load 32 bits from 4-byte aligned dest_buff a3 to a12, offset 0 + src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15 (value in a15 is already prepared from previous steps) + ssa8l a10 // Set SAR_BYTE from dest_w_bytes_local a10 length + sll a15, a15 // Shift the dest word a15 by SAR_BYTE amount + srl a12, a12 // Shift the src word a12 by SAR_BYTE amount + ssa8b a10 // Set SAR_BYTE from dest_w_bytes_local a10 length + src a12, a12, a15 // Concatenate a12 and a15 and shift by SAR_BYTE amount to a12 + s32i.n a12, a3, 0 // Save 32 bits from shift-corrected a12 to dest_buff a3, offset 0 + add a7, a7, a13 // Increment src_buff pointer a7, by amount of copied bytes (a13) + add a3, a3, a13 // Increment dest_buff pointer a3, by amount of copied bytes (a13) + _mod_1_2_check: + + add a7, a7, a4 // Correct the src_buff back by src_buff_unalignment (a4), after we have force-aligned it to 4-byte boundary before the main loop + add a3, a3, a6 // dest_buff + dest_stride + add a7, a7, a8 // src_buff + src_stride + addi.n a5, a5, -1 // Decrease the outer loop + bnez a5, .outer_loop_unalign + + movi.n a2, 1 // Return LV_RESULT_OK = 1 + retw.n // Return + +//********************************************************************************************************************** + + // Small matrix width, keep it simple for lengths less than 8 pixels + _matrix_width_check: // Matrix width is greater or equal 8 pixels + + // Convert strides to matrix paddings + sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11) + sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11) + + .outer_loop_short_matrix_length: + + // Run main loop which copies 2 bytes (one RGB565 pixel) in one loop run + loopnez a4, ._main_loop_short_matrix_length + l8ui a15, a7, 0 // Load 8 bits from src_buff a7 to a15, offset 0 + l8ui a14, a7, 1 // Load 8 bits from src_buff a7 to a14, offset 1 + s8i a15, a3, 0 // Save 8 bits from a15 to dest_buff a3, offset 0 + s8i a14, a3, 1 // Save 8 bits from a14 to dest_buff a3, offset 1 + addi.n a7, a7, 2 // Increment src_buff pointer a7 by 1 + addi.n a3, a3, 2 // Increment dest_buff pointer a3 by 2 + ._main_loop_short_matrix_length: + + // Finish remaining byte out of the main loop + + // Check modulo 1 of the dest_w_bytes (a11), if - then copy 1 byte (1/2 RGB565 pixel) + // src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15 + macro_memcpy_mod_1 a7, a3, a11, a15, __LINE__ + + add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6) + add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8) + addi.n a5, a5, -1 // Decrease the outer loop + bnez a5, .outer_loop_short_matrix_length + + movi.n a2, 1 // Return LV_RESULT_OK = 1 + retw.n // Return diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb565_blend_normal_to_rgb565_esp32s3.S b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb565_blend_normal_to_rgb565_esp32s3.S new file mode 100644 index 00000000..66de392f --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb565_blend_normal_to_rgb565_esp32s3.S @@ -0,0 +1,372 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "lv_macro_memcpy.S" // Memcpy macros + +// This is LVGL RGB565 image blend to RGB565 for ESP32S3 processor + + .section .text + .align 4 + .global lv_rgb565_blend_normal_to_rgb565_esp + .type lv_rgb565_blend_normal_to_rgb565_esp,@function +// The function implements the following C code: +// void lv_color_blend_to_rgb565(_lv_draw_sw_blend_fill_dsc_t * dsc); + +// Input params +// +// dsc - a2 + +// typedef struct { +// uint32_t opa; l32i 0 +// void * dst_buf; l32i 4 +// uint32_t dst_w; l32i 8 +// uint32_t dst_h; l32i 12 +// uint32_t dst_stride; l32i 16 +// const void * src_buf; l32i 20 +// uint32_t src_stride; l32i 24 +// const lv_opa_t * mask_buf; l32i 28 +// uint32_t mask_stride; l32i 32 +// } asm_dsc_t; + +lv_rgb565_blend_normal_to_rgb565_esp: + + entry a1, 32 + l32i.n a3, a2, 4 // a3 - dest_buff + l32i.n a4, a2, 8 // a4 - dest_w in uint16_t + l32i.n a5, a2, 12 // a5 - dest_h in uint16_t + l32i.n a6, a2, 16 // a6 - dest_stride in bytes + l32i.n a7, a2, 20 // a7 - src_buff + l32i.n a8, a2, 24 // a8 - src_stride in bytes + movi.n a10, 0xf // 0xf alignment mask (16-byte alignment) + slli a11, a4, 1 // a11 - dest_w_bytes = sizeof(uint16_t) * dest_w + + // No need to convert any colors here, we are copying from rgb565 to rgb565 + + // Check dest_w length + bltui a4, 8, _matrix_width_check // Branch if dest_w (a4) is lower than 8 + + // Check dest_buff alignment fist + and a15, a10, a3 // 16-byte alignment mask AND dest_buff pointer a3 + bnez a15, _src_unalign_dest_unalign // Branch if a15 not equals to zero + // Jump straight to the last implementation, since this is the only one which deals with unaligned destination arrays + + // Check src_buff alignment + and a15, a10, a7 // 16-byte alignment mask AND src_buff pointer a7 + bnez a15, _src_align_dest_unalign // Branch if a15 not equals to zero + // Jump to check, if the second or third implementation can be used (depends on both strides and dest_w) + + // Check dest_stride alignment + and a15, a10, a6 // 16-byte alignment mask AND dest_stride a6 + bnez a15, _src_unalign_dest_unalign // Branch if a15 not equals to zero + // Jump straight to the last implementation, since this is the only one which deals with destination stride not aligned + + // Check src_stride alignment + and a15, a10, a8 // 16-byte alignment mask AND src_stride a8 + bnez a15, _src_align_dest_unalign // Branch if a15 not equals to zero + // Jump to check, if the second or third implementation can be used (depends on dest_w_bytes) + + // Check dest_w_bytes alignment + and a15, a10, a11 // 16-byte alignment mask AND dest_w_bytes + bnez a15, _src_unalign_dest_unalign // Branch if a15 not equals to zero + // Jump straight to the last implementation, since this is the only one which deals with dest_w_bytes not aligned + +//********************************************************************************************************************** + + // The most ideal case - both arrays aligned, both strides and dest_w are multiples of 16 + + // dest_buff (a3) - 16-byte aligned + // src_buff (a7) - 16-byte aligned + // dest_stride (a6) - 16-byte multiple + // src_stride (a8) - 16-byte multiple + // dest_w (a4) - 16-byte multiple + + srli a9, a4, 4 // a9 - loop_len = dest_w / 16 + // Convert strides to matrix paddings + sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11) + sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11) + + .outer_loop_align: + + // Run main loop which copies 32 bytes (16 RGB565 pixels) in one loop run + loopnez a9, ._main_loop_align // 32 bytes (16 RGB565 pixels) in one loop run + ee.vld.128.ip q0, a7, 16 // Load 16 bytes from src_buff a7 to q0, increase src_buf pointer a7 by 16 + ee.vld.128.ip q1, a7, 16 // Load 16 bytes from src_buff a7 to q1, increase src_buf pointer a7 by 16 + ee.vst.128.ip q0, a3, 16 // Store 16 bytes from q0 to dest_buff a3, increase dest_buff pointer a3 by 16 + ee.vst.128.ip q1, a3, 16 // Store 16 bytes from q1 to dest_buff a3, increase dest_buff pointer a3 by 16 + ._main_loop_align: + + // Finish remaining bytes out of the main loop + + // Check modulo 16 of the dest_w, if - then copy 16 bytes (8 RGB565 pixels) + bbci a11, 4, _align_mod_16_check // Branch if 4-th bit of dest_w_bytes a11 is clear + ee.vld.128.ip q0, a7, 16 // Load 16 bytes from src_buff a7 to q0, increase src_buf pointer a7 by 16 + ee.vst.128.ip q0, a3, 16 // Store 16 bytes from q0 to dest_buff a3, increase dest_buff pointer a3 by 16 + _align_mod_16_check: + + add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6) + add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8) + addi.n a5, a5, -1 // Decrease the outer loop + bnez a5, .outer_loop_align + + movi.n a2, 1 // Return LV_RESULT_OK = 1 + retw.n // Return + + + _src_align_dest_unalign: + + // Check dest_stride alignment + and a15, a10, a6 // 16-byte alignment mask AND dest_stride a6 + bnez a15, _src_unalign_dest_unalign // Branch if a15 not equals to zero + + // Check dest_w_bytes alignment + and a15, a10, a11 // 16-byte alignment mask AND dest_w_bytes a11 + bnez a15, _src_unalign_dest_unalign // Branch if a15 not equals to zero + + // We don't check src_stride alignment for this implementation, as it can be either align, or unalign + +//********************************************************************************************************************** + + // Less ideal case - Only destination array is aligned, src array is unaligned + // Source stride is either aligned or unaligned, destination stride must be aligned, dest_w_bytes must be aligned + + // dest_buff (a3) - 16-byte aligned + // src_buff (a7) - unaligned + // dest_stride (a6) - 16-byte multiple + // src_stride (a8) - does not matter if 16-byte multiple + // dest_w (a4) - 16-byte multiple + + // Convert strides to matrix paddings + sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11) + sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11) + + // Calculate modulo for non-aligned data + movi a15, 48 // a15 = 48 (main loop copies 48 bytes) + quou a9, a11, a15 // a9 = dest_w_bytes (a11) DIV 48 (15) + remu a12, a11, a15 // a12 = dest_w_bytes (a11) remainder after DIV 48 (15) + + .outer_loop_src_unalign_dest_align: + + ee.ld.128.usar.ip q2, a7, 16 // Preload 16 bytes from src_buff a7 to q2, get value of the SAR_BYTE, increase src_buf pointer a7 by 16 + ee.ld.128.usar.ip q3, a7, 16 // Preload 16 bytes from src_buff a7 to q3, get value of the SAR_BYTE, increase src_buf pointer a7 by 16 + + // Run main loop which copies 48 bytes (24 RGB565 pixels) in one loop run + loopnez a9, ._main_loop_src_unalign_dest_align // 48 bytes (24 RGB565 pixels) in one loop + ee.src.q.ld.ip q4, a7, 16, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, increase src_buf pointer a7 by 16 + ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q.ld.ip q2, a7, 16, q3, q4 // Load 16 bytes from src_buff a7 to q2, concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount, increase src_buf pointer a7 by 16 + ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q.ld.ip q3, a7, 16, q4, q2 // Load 16 bytes from src_buff a7 to q3, concatenate q4 and q2 and shift to q4 by the SAR_BYTE amount, increase src_buf pointer a7 by 16 + ee.vst.128.ip q4, a3, 16 // Store 16 bytes from q4 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ._main_loop_src_unalign_dest_align: + + // Finish the main loop outside of the loop from Q registers preloads + + // Check modulo 32 of the loop_len_remainder, if - then copy 32 bytes (16 RGB565 pixels) + bbci a12, 5, _unalign_mod_32_check // Branch if 5-th bit of loop_len_remainder a12 is clear + ee.src.q.ld.ip q4, a7, 0, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, don't increase src_buf pointer a7 + ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q q3, q3, q4 // Concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount + ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + j _end_of_row_src_unalign_dest_align + _unalign_mod_32_check: + + // Check modulo 16 of the loop_len_remainder, if - then copy 16 bytes (8 RGB565 pixels) + bbci a12, 4, _unalign_mod_16_check // Branch if 4-th bit of loop_len_remainder a12 is clear + ee.src.q q2, q2, q3 // Concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount + ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + addi a7, a7, -16 // Correct the src_buff pointer a7, caused by q reg preload + j _end_of_row_src_unalign_dest_align + _unalign_mod_16_check: + + // Nothing to copy outside of the main loop + addi a7, a7, -32 // Correct the src_buff pointer a7, caused by q reg preload + + _end_of_row_src_unalign_dest_align: + + add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6) + add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8) + addi.n a5, a5, -1 // Decrease the outer loop + bnez a5, .outer_loop_src_unalign_dest_align + + movi.n a2, 1 // Return LV_RESULT_OK = 1 + retw.n // Return + + + _src_unalign_dest_unalign: + +//********************************************************************************************************************** + + // The most general case, can handle all the possible combinations + + // dest_buff (a3) - unaligned + // src_buff (a7) - unaligned + // dest_stride (a6) - not 16-byte multiple + // src_stride (a8) - not 16-byte multiple + // dest_w (a4) - not 16-byte multiple + + // Convert strides to matrix paddings + sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11) + sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11) + + .outer_loop_all_unalign: + + // dest_buff alignment check + and a13, a10, a3 // Alignment mask 0xf (a10) AND dest_buff pointer + beqz a13, _dest_buff_aligned // Branch if a13 = 0 (if dest_buff is aligned) + + movi.n a14, 16 // a14 = 16 + sub a13, a14, a13 // a13 = 16 - unalignment + + // Check modulo 8 of the unalignment a13, if - then copy 8 bytes (4 RGB565 pixels) + // src_buff a7, dest_buff a3, unalignment a13, copy registers a14, a15 + macro_memcpy_mod_8 a7, a3, a13, a15, a14, __LINE__ + + // Check modulo 4 of the unalignment, if - then copy 4 bytes (2 RGB565 pixels) + // src_buff a7, dest_buff a3, unalignment a13, copy register a15 + macro_memcpy_mod_4 a7, a3, a13, a15, __LINE__ + + // Check modulo 2 of the unalignment, if - then copy 2 bytes (1 RGB565 pixel) + // src_buff a7, dest_buff a3, unalignment a13, copy register a15 + macro_memcpy_mod_2 a7, a3, a13, a15, __LINE__ + + // Check modulo 1 of the unalignment, if - then copy 1 byte (1/2 of RGB565 pixel) + // src_buff a7, dest_buff a3, unalignment a13, copy register a15 + macro_memcpy_mod_1 a7, a3, a13, a15, __LINE__ + + _dest_buff_aligned: + + // Calculate modulo for non-aligned data + sub a11, a11, a13 // a11 = local_dest_w_bytes (a11) = dest_w_bytes (a11) - (16 - unalignment) + movi a15, 48 // a15 = 48 + quou a9, a11, a15 // a9 = local_dest_w_bytes (a11) DIV 48 (a15) + remu a12, a11, a15 // a12 = local_dest_w_bytes (a11) remainder after div 48 (a15) + + ee.ld.128.usar.ip q2, a7, 16 // Preload 16 bytes from src_buff a7 to q2, get value of the SAR_BYTE, increase src_buf pointer a7 by 16 + ee.ld.128.usar.ip q3, a7, 16 // Preload 16 bytes from src_buff a7 to q3, get value of the SAR_BYTE, increase src_buf pointer a7 by 16 + + // Run main loop which copies 48 bytes (24 RGB565 pixels) in one loop run + loopnez a9, ._main_loop_all_unalign // 48 bytes (24 RGB565 pixels) in one loop + ee.src.q.ld.ip q4, a7, 16, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, increase src_buf pointer a7 by 16 + ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q.ld.ip q2, a7, 16, q3, q4 // Load 16 bytes from src_buff a7 to q2, concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount, increase src_buf pointer a7 by 16 + ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q.ld.ip q3, a7, 16, q4, q2 // Load 16 bytes from src_buff a7 to q3, concatenate q4 and q2 and shift to q4 by the SAR_BYTE amount, increase src_buf pointer a7 by 16 + ee.vst.128.ip q4, a3, 16 // Store 16 bytes from q4 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ._main_loop_all_unalign: + + // Finish the main loop outside of the loop from Q registers preloads + + // Check modulo 32 and modulo 8 of the loop_len_remainder a12 + bbci a12, 5, _all_unalign_mod_32_check // Branch if 5-th bit of loop_len_remainder a12 is clear + bbsi a12, 3, _all_unalign_mod_32_mod_8_check // Branch if 3-rd bif of loop_len_remainder a12 is set + + // Copy 32 bytes (16 RGB565 pixels) (47 - 40) + ee.src.q.ld.ip q4, a7, 0, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, don't increase src_buf pointer a7 + ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q q3, q3, q4 // Concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount + ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + j _skip_mod16 + + _all_unalign_mod_32_mod_8_check: + // Copy 40 bytes (20 RGB565 pixels) + ee.src.q.ld.ip q4, a7, 16, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, increase src_buf pointer a7 by 16 + ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q.ld.ip q2, a7, 0, q3, q4 // Load 16 bytes from src_buff a7 to q2, concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount, don't increase src_buf pointer a7 + ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q q4, q4, q2 // Concatenate q4 and q2 and shift to q4 by the SAR_BYTE amount + ee.vst.l.64.ip q4, a3, 8 // Store lower 8 bytes from q4 to aligned dest_buff a3, increase dest_buff pointer a3 by 8 + addi a7, a7, -8 // Correct the src_buff pointer a7, caused by q reg preload + j _skip_mod16 + + _all_unalign_mod_32_check: + + // Check modulo 16 and modulo 8 of the loop_len_remainder a12 + bbci a12, 4, _all_unalign_mod_16_check // branch if 4-th bit of loop_len_remainder a12 is clear + bbsi a12, 3, _all_unalign_mod_16_mod_8_check // branch if 3-rd bit of loop_len_remainder a12 is set + + // Copy 16 bytes (8 RGB565 pixels) + ee.src.q q2, q2, q3 // Concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount + ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + addi a7, a7, -16 // Correct the src_buff pointer a7, caused by q reg preload + j _skip_mod16 + + _all_unalign_mod_16_mod_8_check: + // Copy 24 bytes (12 RGB565 pixels) + ee.src.q.ld.ip q4, a7, 0, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, don't increase src_buf pointer a7 + ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q q3, q3, q4 // Concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount + ee.vst.l.64.ip q3, a3, 8 // Store lower 8 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 8 + addi a7, a7, -8 // Correct the src_buff pointer a7, caused by q reg preload + j _skip_mod16 + _all_unalign_mod_16_check: + + bbci a12, 3, _all_unalign_mod_8_check // Branch if 3-rd bit of loop_len_remainder a12 is clear + // Copy 8 bytes (4 RGB565 pixels) + ee.src.q q2, q2, q3 // Concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount + ee.vst.l.64.ip q2, a3, 8 // Store lower 8 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 8 + addi a7, a7, -24 // Correct the src_buff pointer a7, caused by q reg preload + j _skip_mod16 + _all_unalign_mod_8_check: + + addi a7, a7, -32 // Correct the src_buff pointer a7, caused by q reg preload + + _skip_mod16: + + // Check modulo 4 of the loop_len_remainder, if - then copy 4 bytes (2 RGB565 pixels) + // src_buff a7, dest_buff a3, loop_len_remainder a12, copy register a15 + macro_memcpy_mod_4 a7, a3, a12, a15, __LINE__ + + // Check modulo 2 of the loop_len_remainder, if - then copy 2 bytes (1 RGB565 pixel) + // src_buff a7, dest_buff a3, loop_len_remainder a12, copy register a15 + macro_memcpy_mod_2 a7, a3, a12, a15, __LINE__ + + // Check modulo 1 of the loop_len_remainder, if - then copy 1 byte (1/2 RGB565 pixel) + // src_buff a7, dest_buff a3, loop_len_remainder a12, copy register a15 + macro_memcpy_mod_1 a7, a3, a12, a15, __LINE_ + + slli a11, a4, 1 // Refresh dest_w_bytes + add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6) + add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8) + addi.n a5, a5, -1 // Decrease the outer loop + bnez a5, .outer_loop_all_unalign + + movi.n a2, 1 // Return LV_RESULT_OK = 1 + retw.n // Return + +//********************************************************************************************************************** + + // Small matrix width, keep it simple for lengths less than 8 pixels + _matrix_width_check: // Matrix width is greater or equal 8 pixels + + // Convert strides to matrix paddings + sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11) + sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11) + + .outer_loop_short_matrix_length: + + // Run main loop which copies 2 bytes (one RGB565 pixel) in one loop run + loopnez a4, ._main_loop_short_matrix_length + l8ui a15, a7, 0 // Load 8 bits from src_buff a7 to a15, offset 0 + l8ui a14, a7, 1 // Load 8 bits from src_buff a7 to a14, offset 1 + s8i a15, a3, 0 // Save 8 bits from a15 to dest_buff a3, offset 0 + s8i a14, a3, 1 // Save 8 bits from a14 to dest_buff a3, offset 1 + addi.n a7, a7, 2 // Increment src_buff pointer a7 by 1 + addi.n a3, a3, 2 // Increment dest_buff pointer a3 by 2 + ._main_loop_short_matrix_length: + + // Finish remaining byte out of the main loop + + // Check modulo 1 of the dest_w_bytes (a11), if - then copy 1 byte (1/2 RGB565 pixel) + // src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15 + macro_memcpy_mod_1 a7, a3, a11, a15, __LINE__ + + add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6) + add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8) + addi.n a5, a5, -1 // Decrease the outer loop + bnez a5, .outer_loop_short_matrix_length + + movi.n a2, 1 // Return LV_RESULT_OK = 1 + retw.n // Return diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb888_blend_normal_to_rgb888_esp32.S b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb888_blend_normal_to_rgb888_esp32.S new file mode 100644 index 00000000..f35175fe --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb888_blend_normal_to_rgb888_esp32.S @@ -0,0 +1,261 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "lv_macro_memcpy.S" // Memcpy macros + +// This is LVGL RGB888 image blend to RGB888 for ESP32 processor + + .section .text + .align 4 + .global lv_rgb888_blend_normal_to_rgb888_esp + .type lv_rgb888_blend_normal_to_rgb888_esp,@function +// The function implements the following C code: +// void rgb888_image_blend(_lv_draw_sw_blend_image_dsc_t * dsc); + +// Input params +// +// dsc - a2 + +// typedef struct { +// uint32_t opa; l32i 0 +// void * dst_buf; l32i 4 +// uint32_t dst_w; l32i 8 +// uint32_t dst_h; l32i 12 +// uint32_t dst_stride; l32i 16 +// const void * src_buf; l32i 20 +// uint32_t src_stride; l32i 24 +// const lv_opa_t * mask_buf; l32i 28 +// uint32_t mask_stride; l32i 32 +// } asm_dsc_t; + +lv_rgb888_blend_normal_to_rgb888_esp: + + entry a1, 32 + l32i.n a3, a2, 4 // a3 - dest_buff + l32i.n a4, a2, 8 // a4 - dest_w in uint16_t + l32i.n a5, a2, 12 // a5 - dest_h in uint16_t + l32i.n a6, a2, 16 // a6 - dest_stride in bytes + l32i.n a7, a2, 20 // a7 - src_buff + l32i.n a8, a2, 24 // a8 - src_stride in bytes + slli a11, a4, 1 // a11 = (a4 << 1) + a4 + add a11, a11, a4 // a11 - dest_w_bytes = sizeof(uint24_t) * dest_w + + // No need to convert any colors here, we are copying from rgb888 to rgb888 + + // Check dest_w length + bltui a4, 5, _matrix_width_check // Branch if dest_w (a4) is lower than 5 (dest_w_bytes 15) + + // Check memory alignment and input parameters lengths and decide which implementation to use + movi.n a10, 0x3 // a10 = 0x3 alignment mask (4-byte alignment) + or a15, a7, a3 // a15 = src_buff (a7) OR dest_buff (a3) + or a15, a15, a6 // a15 = a15 OR dest_stride (a6) + or a15, a15, a8 // a15 = a15 OR src_stride (a8) + or a15, a15, a11 // a15 = a15 OR dest_w_bytes (a11) + and a15, a15, a10 // a15 = a15 AND alignment mask (a10) + bnez a15, _alignment_check // Branch if a15 not equals to zero + +//********************************************************************************************************************** + + // The most ideal case - both arrays aligned, both strides and dest_w are multiples of 4 + + // dest_buff (a3) - 4-byte aligned + // src_buff (a7) - 4-byte aligned + // dest_stride (a6) - 4-byte multiple + // src_stride (a8) - 4-byte multiple + // dest_w (a4) - 4-byte multiple + + srli a9, a11, 4 // a9 - loop_len = dest_w_bytes / 16 + // Convert strides to matrix paddings + sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11) + sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11) + + .outer_loop_align: + + // Run main loop which copies 16 bytes (5 and 1/3 of RGB888 pixels) in one loop run + loopnez a9, ._main_loop_aligned + l32i.n a15, a7, 0 // Load 32 bits from src_buff a7 to a15, offset 0 + l32i.n a14, a7, 4 // Load 32 bits from src_buff a7 to a14, offset 4 + l32i.n a13, a7, 8 // Load 32 bits from src_buff a7 to a13, offset 8 + l32i.n a12, a7, 12 // Load 32 bits from src_buff a7 to a12, offset 12 + s32i.n a15, a3, 0 // Save 32 bits from a15 to dest_buff a3, offset 0 + s32i.n a14, a3, 4 // Save 32 bits from a15 to dest_buff a3, offset 4 + s32i.n a13, a3, 8 // Save 32 bits from a15 to dest_buff a3, offset 8 + s32i.n a12, a3, 12 // Save 32 bits from a15 to dest_buff a3, offset 12 + addi.n a7, a7, 16 // Increment src_buff pointer a7 by 16 + addi.n a3, a3, 16 // Increment dest_buff pointer a3 by 16 + ._main_loop_aligned: + + // Finish the remaining bytes out of the main loop + + // Check modulo 8 of the dest_w_bytes (a11), if - then copy 8 bytes (2 and 2/3 of RGB888 pixels) + // src_buff a7, dest_buff a3, dest_w_bytes a11, copy registers a14 a15 + macro_memcpy_mod_8 a7, a3, a11, a14, a15 __LINE__ + + // Check modulo 4 of the dest_w_bytes (a11), if - then copy 4 bytes (1 and 1/3 of RGB888 pixels) + // src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15 + macro_memcpy_mod_4 a7, a3, a11, a15 __LINE__ + + // Check modulo 2 of the dest_w_bytes (a11), if - then copy 2 bytes (2/3 of RGB888 pixel) + // src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15 + macro_memcpy_mod_2 a7, a3, a11, a15 __LINE__ + + // Check modulo 1 of the dest_w_bytes (a11), if - then copy 1 byte (1/3 of RGB888 pixel) + // src_buff a7, dest_buff a3, dest_w_bytes a11, copy register a15 + macro_memcpy_mod_1 a7, a3, a11, a15 __LINE__ + + add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6) + add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8) + addi.n a5, a5, -1 // Decrease the outer loop + bnez a5, .outer_loop_align + + movi.n a2, 1 // Return LV_RESULT_OK = 1 + retw.n // Return + + +//********************************************************************************************************************** + + // The most general case - at leas one array is not aligned, or one parameter is not multiple of 4 + _alignment_check: + + // dest_buff (a3) - 4-byte aligned, or not + // src_buff (a7) - 4-byte aligned, or not + // dest_stride (a6) - 4-byte multiple, or not + // src_stride (a8) - 4-byte multiple, or not + // dest_w (a4) - 4-byte multiple, or not + + // Convert strides to matrix paddings + sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11) + sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11) + + .outer_loop_unalign: + + extui a13, a3, 0, 2 // Get last two bits of the dest_buff address a3, to a13 + movi.n a15, 4 // Move 4 to a15, for calculation of the destination alignment loop + sub a14, a15, a13 // Calculate destination alignment loop length (a14 = 4 - a13) + + // In case of the dest_buff a3 being already aligned (for example by matrix padding), correct a14 value, + // to prevent the destination aligning loop to run 4 times (to prevent aligning already aligned memory) + moveqz a14, a13, a13 // If a13 is zero, move a13 to a14, move 0 to a14 + + sub a10, a11, a14 // Get the dest_w_bytes after the aligning loop + srli a9, a10, 4 // Calculate main loop len (a9 = dest_w_bytes_local / 16) + + // Run dest_buff aligning loop byte by byte + loopnez a14, ._dest_aligning_loop + l8ui a15, a7, 0 // Load 8 bits from src_buff a7 to a15, offset 0 + addi.n a7, a7, 1 // Increment src_buff pointer a7 by 1 + s8i a15, a3, 0 // Save 8 bits from a15 to dest_buff a3, offset 0 + addi.n a3, a3, 1 // Increment dest_buff pointer a3 by 1 + ._dest_aligning_loop: + + // Destination is aligned, source is unaligned + + // For more information about this implementation, see chapter 3.3.2 Shifts and the Shift Amount Register (SAR) + // in Xtensa Instruction Set Architecture (ISA) Reference Manual + + ssa8l a7 // Set SAR_BYTE from src_buff a7 unalignment + extui a4, a7, 0, 2 // Get last 2 bits of the src_buff, a4 = src_buff_unalignment + sub a7, a7, a4 // "align" the src_buff a7, to 4-byte boundary by decreasing it's pointer to the nearest aligned boundary + + // First preload for the loopnez cycle + l32i.n a15, a7, 0 // Load 32 bits from 4-byte aligned src_buff a7 to a15, offset 0 + + // Run main loop which copies 16 bytes (5 and 1/3 of RGB888 pixels) in one loop run + loopnez a9, ._main_loop_unalign + l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4 + l32i.n a13, a7, 8 // Load 32 bits from 4-byte aligned src_buff a7 to a13, offset 8 + src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15 + s32i.n a15, a3, 0 // Save 32 bits from shift-corrected a15 to dest_buff a3, offset 0 + l32i.n a12, a7, 12 // Load 32 bits from 4-byte aligned src_buff a7 to a12, offset 12 + src a14, a13, a14 // Concatenate a13 and a14 and shift by SAR_BYTE amount to a14 + s32i.n a14, a3, 4 // Save 32 bits from shift-corrected a14 to dest_buff a3, offset 4 + l32i.n a15, a7, 16 // Load 32 bits from 4-byte aligned src_buff a7 to a15, offset 16 + src a13, a12, a13 // Concatenate a12 and a13 and shift by SAR_BYTE amount to a13 + s32i.n a13, a3, 8 // Save 32 bits from shift-corrected a13 to dest_buff a3, offset 8 + addi.n a7, a7, 16 // Increment src_buff pointer a7 by 16 + src a12, a15, a12 // Concatenate a15 and a12 and shift by SAR_BYTE amount to a12 + s32i.n a12, a3, 12 // Save 32 bits from shift-corrected a12 to dest_buff a3, offset 12 + addi.n a3, a3, 16 // Increment dest_buff pointer a3 by 16 + ._main_loop_unalign: + + // Finish the remaining bytes out of the loop + // Check modulo 8 of the dest_w_bytes_local (a10), if - then copy 8 bytes + bbci a10, 3, _mod_8_check // Branch if 3-rd bit of dest_w_bytes_local is clear + l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4 + l32i.n a13, a7, 8 // Load 32 bits from 4-byte aligned src_buff a7 to a13, offset 8 + src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15 (value in a15 is already prepared from previous steps) + s32i.n a15, a3, 0 // Save 32 bits from shift-corrected a15 to dest_buff a3, offset 0 + addi.n a7, a7, 8 // Increment src_buff pointer a7 by 8 + src a14, a13, a14 // Concatenate a13 and a14 and shift by SAR_BYTE amount to a14 + s32i.n a14, a3, 4 // Save 32 bits from shift-corrected a14 to dest_buff a3, offset 4 + addi.n a3, a3, 8 // Increment dest_buff pointer a3 by 8 + mov a15, a13 // Prepare a15 for the next steps (copy a13 to a15) + _mod_8_check: + + // Check modulo 4 of the dest_w_bytes_local (a10), if - then copy 4 bytes + bbci a10, 2, _mod_4_check // Branch if 2-nd bit of dest_w_bytes_local is clear + l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4 + addi.n a7, a7, 4 // Increment src_buff pointer a7 by 4 + src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15 (value in a15 is already prepared from previous steps) + s32i.n a15, a3, 0 // Save 32 bits from shift-corrected a15 to dest_buff a3, offset 0 + addi.n a3, a3, 4 // Increment dest_buff pointer a3 by 4 + mov a15, a14 // Prepare a15 for the next steps (copy a14 to a15) + _mod_4_check: + + extui a13, a10, 0, 2 // Get the last 2 bytes of the dest_w_bytes_local (a10), a13 = a10[1:0], to find out how many bytes are needs copied and to increase src and dest pointer accordingly + beqz a13, _mod_1_2_check // Branch if a13 equal to zero, E.G. if there are no bytes to be copied + l32i.n a14, a7, 4 // Load 32 bits from 4-byte aligned src_buff a7 to a14, offset 4 + l32i.n a12, a3, 0 // Get dest_buff value: Load 32 bits from 4-byte aligned dest_buff a3 to a12, offset 0 + src a15, a14, a15 // Concatenate a14 and a15 and shift by SAR_BYTE amount to a15 (value in a15 is already prepared from previous steps) + ssa8l a10 // Set SAR_BYTE from dest_w_bytes_local a10 length + sll a15, a15 // Shift the dest word a15 by SAR_BYTE amount + srl a12, a12 // Shift the src word a12 by SAR_BYTE amount + ssa8b a10 // Set SAR_BYTE from dest_w_bytes_local a10 length + src a12, a12, a15 // Concatenate a12 and a15 and shift by SAR_BYTE amount to a12 + s32i.n a12, a3, 0 // Save 32 bits from shift-corrected a12 to dest_buff a3, offset 0 + add a7, a7, a13 // Increment src_buff pointer a7, by amount of copied bytes (a13) + add a3, a3, a13 // Increment dest_buff pointer a3, by amount of copied bytes (a13) + _mod_1_2_check: + + add a7, a7, a4 // Correct the src_buff back by src_buff_unalignment (a4), after we have force-aligned it to 4-byte boundary before the main loop + add a3, a3, a6 // dest_buff + dest_stride + add a7, a7, a8 // src_buff + src_stride + addi.n a5, a5, -1 // Decrease the outer loop + bnez a5, .outer_loop_unalign + + movi.n a2, 1 // Return LV_RESULT_OK = 1 + retw.n // Return + +//********************************************************************************************************************** + + // Small matrix width, keep it simple for lengths less than 8 pixels + _matrix_width_check: // Matrix width is greater or equal 8 pixels + + // Convert strides to matrix paddings + sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11) + sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11) + + .outer_loop_short_matrix_length: + + // Run main loop which copies 3 bytes (one RGB888 pixel) in one loop run + loopnez a4, ._main_loop_short_matrix_length + l8ui a15, a7, 0 // Load 8 bits from src_buff a7 to a15, offset 0 + l8ui a14, a7, 1 // Load 8 bits from src_buff a7 to a14, offset 1 + l8ui a13, a7, 2 // Load 8 bits from src_buff a7 to a13, offset 2 + s8i a15, a3, 0 // Save 8 bits from a15 to dest_buff a3, offset 0 + s8i a14, a3, 1 // Save 8 bits from a14 to dest_buff a3, offset 1 + s8i a13, a3, 2 // Save 8 bits from a13 to dest_buff a3, offset 2 + addi.n a7, a7, 3 // Increment src_buff pointer a7 by 3 + addi.n a3, a3, 3 // Increment dest_buff pointer a3 by 3 + ._main_loop_short_matrix_length: + + add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6) + add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8) + addi.n a5, a5, -1 // Decrease the outer loop + bnez a5, .outer_loop_short_matrix_length + + movi.n a2, 1 // Return LV_RESULT_OK = 1 + retw.n // Return diff --git a/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb888_blend_normal_to_rgb888_esp32s3.S b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb888_blend_normal_to_rgb888_esp32s3.S new file mode 100644 index 00000000..cb31100f --- /dev/null +++ b/components/espressif__esp_lvgl_port/src/lvgl9/simd/lv_rgb888_blend_normal_to_rgb888_esp32s3.S @@ -0,0 +1,221 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "lv_macro_memcpy.S" // Memcpy macros + +// This is LVGL RGB888 image blend to RGB888 for ESP32S3 processor + + .section .text + .align 4 + .global lv_rgb888_blend_normal_to_rgb888_esp + .type lv_rgb888_blend_normal_to_rgb888_esp,@function +// The function implements the following C code: +// void lv_color_blend_to_rgb888(_lv_draw_sw_blend_fill_dsc_t * dsc); + +// Input params +// +// dsc - a2 + +// typedef struct { +// uint32_t opa; l32i 0 +// void * dst_buf; l32i 4 +// uint32_t dst_w; l32i 8 +// uint32_t dst_h; l32i 12 +// uint32_t dst_stride; l32i 16 +// const void * src_buf; l32i 20 +// uint32_t src_stride; l32i 24 +// const lv_opa_t * mask_buf; l32i 28 +// uint32_t mask_stride; l32i 32 +// } asm_dsc_t; + +lv_rgb888_blend_normal_to_rgb888_esp: + + entry a1, 32 + l32i.n a3, a2, 4 // a3 - dest_buff + l32i.n a4, a2, 8 // a4 - dest_w in uint16_t + l32i.n a5, a2, 12 // a5 - dest_h in uint16_t + l32i.n a6, a2, 16 // a6 - dest_stride in bytes + l32i.n a7, a2, 20 // a7 - src_buff + l32i.n a8, a2, 24 // a8 - src_stride in bytes + movi.n a10, 0xf // 0xf alignment mask (16-byte alignment) + slli a11, a4, 1 // a11 = (a4 << 1) + a4 + add a11, a11, a4 // a11 - dest_w_bytes = sizeof(uint24_t) * dest_w + + // No need to convert any colors here, we are copying from rgb888 to rgb888 + + // Check dest_w length + bltui a4, 8, _matrix_width_check // Branch if dest_w (a4) is lower than 8 + +//********************************************************************************************************************** + + // The most general case, can handle all the possible combinations + + // dest_buff (a3) - any alignment + // src_buff (a7) - any alignment + // dest_stride (a6) - any length + // src_stride (a8) - any length + // dest_w (a4) - any length + + // Convert strides to matrix paddings + sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11) + sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11) + + .outer_loop_all_unalign: + + // dest_buff alignment check + and a13, a10, a3 // Alignment mask 0xf (a10) AND dest_buff pointer + beqz a13, _dest_buff_aligned // Branch if a13 = 0 (if dest_buff is aligned) + + movi.n a14, 16 // a14 = 16 + sub a13, a14, a13 // a13 = 16 - unalignment + + // Check modulo 8 of the unalignment a13, if - then copy 8 bytes (2 and 2/3 of RGB888 pixels) + // src_buff a7, dest_buff a3, unalignment a13, copy registers a14, a15 + macro_memcpy_mod_8 a7, a3, a13, a15, a14, __LINE__ + + // Check modulo 4 of the unalignment, if - then copy 4 bytes (1 and 1/3 of RGB888 pixels) + // src_buff a7, dest_buff a3, unalignment a13, copy register a15 + macro_memcpy_mod_4 a7, a3, a13, a15, __LINE__ + + // Check modulo 2 of the unalignment, if - then copy 2 bytes (2/3 of RGB888 pixel) + // src_buff a7, dest_buff a3, unalignment a13, copy register a15 + macro_memcpy_mod_2 a7, a3, a13, a15, __LINE__ + + // Check modulo 1 of the unalignment, if - then copy 1 byte (1/3 of RGB888 pixel) + // src_buff a7, dest_buff a3, unalignment a13, copy register a15 + macro_memcpy_mod_1 a7, a3, a13, a15, __LINE__ + + _dest_buff_aligned: + + // Calculate modulo for non-aligned data + sub a11, a11, a13 // a11 = local_dest_w_bytes (a11) = dest_w_bytes (a11) - (16 - unalignment) + movi a15, 48 // a15 = 48 + quou a9, a11, a15 // a9 = local_dest_w_bytes (a11) DIV 48 (a15) + remu a12, a11, a15 // a12 = local_dest_w_bytes (a11) remainder after div 48 (a15) + + ee.ld.128.usar.ip q2, a7, 16 // Preload 16 bytes from src_buff a7 to q2, get value of the SAR_BYTE, increase src_buf pointer a7 by 16 + ee.ld.128.usar.ip q3, a7, 16 // Preload 16 bytes from src_buff a7 to q3, get value of the SAR_BYTE, increase src_buf pointer a7 by 16 + + // Run main loop which copies 48 bytes (16 RGB888 pixels) in one loop run + loopnez a9, ._main_loop_all_unalign // 48 bytes (16 RGB888 pixels) in one loop + ee.src.q.ld.ip q4, a7, 16, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, increase src_buf pointer a7 by 16 + ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q.ld.ip q2, a7, 16, q3, q4 // Load 16 bytes from src_buff a7 to q2, concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount, increase src_buf pointer a7 by 16 + ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q.ld.ip q3, a7, 16, q4, q2 // Load 16 bytes from src_buff a7 to q3, concatenate q4 and q2 and shift to q4 by the SAR_BYTE amount, increase src_buf pointer a7 by 16 + ee.vst.128.ip q4, a3, 16 // Store 16 bytes from q4 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ._main_loop_all_unalign: + + // Finish the main loop outside of the loop from Q registers preloads + + // Check modulo 32 and modulo 8 of the loop_len_remainder a12 + bbci a12, 5, _all_unalign_mod_32_check // Branch if 5-th bit of loop_len_remainder a12 is clear + bbsi a12, 3, _all_unalign_mod_32_mod_8_check // Branch if 3-rd bif of loop_len_remainder a12 is set + + // Copy 32 bytes (10 and 2/3 of RGB888 pixels) + ee.src.q.ld.ip q4, a7, 0, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, don't increase src_buf pointer a7 + ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q q3, q3, q4 // Concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount + ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + j _skip_mod16 + + _all_unalign_mod_32_mod_8_check: + // Copy 40 bytes (13 and 1/3 of RGB888 pixels) + ee.src.q.ld.ip q4, a7, 16, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, increase src_buf pointer a7 by 16 + ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q.ld.ip q2, a7, 0, q3, q4 // Load 16 bytes from src_buff a7 to q2, concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount, don't increase src_buf pointer a7 + ee.vst.128.ip q3, a3, 16 // Store 16 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q q4, q4, q2 // Concatenate q4 and q2 and shift to q4 by the SAR_BYTE amount + ee.vst.l.64.ip q4, a3, 8 // Store lower 8 bytes from q4 to aligned dest_buff a3, increase dest_buff pointer a3 by 8 + addi a7, a7, -8 // Correct the src_buff pointer a7, caused by q reg preload + j _skip_mod16 + + _all_unalign_mod_32_check: + + // Check modulo 16 and modulo 8 of the loop_len_remainder a12 + bbci a12, 4, _all_unalign_mod_16_check // branch if 4-th bit of loop_len_remainder a12 is clear + bbsi a12, 3, _all_unalign_mod_16_mod_8_check // branch if 3-rd bit of loop_len_remainder a12 is set + + // Copy 16 bytes (5 and 1/3 of RGB888 pixels) + ee.src.q q2, q2, q3 // Concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount + ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + addi a7, a7, -16 // Correct the src_buff pointer a7, caused by q reg preload + j _skip_mod16 + + _all_unalign_mod_16_mod_8_check: + // Copy 24 bytes (8 RGB888 pixels) + ee.src.q.ld.ip q4, a7, 0, q2, q3 // Load 16 bytes from src_buff a7 to q4, concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount, don't increase src_buf pointer a7 + ee.vst.128.ip q2, a3, 16 // Store 16 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 16 + ee.src.q q3, q3, q4 // Concatenate q3 and q4 and shift to q3 by the SAR_BYTE amount + ee.vst.l.64.ip q3, a3, 8 // Store lower 8 bytes from q3 to aligned dest_buff a3, increase dest_buff pointer a3 by 8 + addi a7, a7, -8 // Correct the src_buff pointer a7, caused by q reg preload + j _skip_mod16 + _all_unalign_mod_16_check: + + bbci a12, 3, _all_unalign_mod_8_check // Branch if 3-rd bit of loop_len_remainder a12 is clear + // Copy 8 bytes (2 and 2/3 of RGB888 pixels) + ee.src.q q2, q2, q3 // Concatenate q2 and q3 and shift to q2 by the SAR_BYTE amount + ee.vst.l.64.ip q2, a3, 8 // Store lower 8 bytes from q2 to aligned dest_buff a3, increase dest_buff pointer a3 by 8 + addi a7, a7, -24 // Correct the src_buff pointer a7, caused by q reg preload + j _skip_mod16 + _all_unalign_mod_8_check: + + addi a7, a7, -32 // Correct the src_buff pointer a7, caused by q reg preload + + _skip_mod16: + + // Check modulo 4 of the loop_len_remainder, if - then copy 4 bytes (1 and 1/3 of RGB888 pixels) + // src_buff a7, dest_buff a3, loop_len_remainder a12, copy register a15 + macro_memcpy_mod_4 a7, a3, a12, a15, __LINE__ + + // Check modulo 2 of the loop_len_remainder, if - then copy 2 bytes (2/3 of RGB888 pixel) + // src_buff a7, dest_buff a3, loop_len_remainder a12, copy register a15 + macro_memcpy_mod_2 a7, a3, a12, a15, __LINE__ + + // Check modulo 1 of the loop_len_remainder, if - then copy 1 byte (1/3 of RGB888 pixel) + // src_buff a7, dest_buff a3, loop_len_remainder a12, copy register a15 + macro_memcpy_mod_1 a7, a3, a12, a15, __LINE_ + + slli a11, a4, 1 // Refresh dest_w_bytes + add a11, a11, a4 + add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6) + add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8) + addi.n a5, a5, -1 // Decrease the outer loop + bnez a5, .outer_loop_all_unalign + + movi.n a2, 1 // Return LV_RESULT_OK = 1 + retw.n // Return + +//********************************************************************************************************************** + + // Small matrix width, keep it simple for lengths less than 8 pixels + _matrix_width_check: // Matrix width is greater or equal 8 pixels + + // Convert strides to matrix paddings + sub a6, a6, a11 // dest_matrix_padding (a6) = dest_stride (a6) - dest_w_bytes (a11) + sub a8, a8, a11 // src_matrix_padding (a8) = src_stride (a8) - dest_w_bytes (a11) + + .outer_loop_short_matrix_length: + + // Run main loop which copies 3 bytes (one RGB888 pixel) in one loop run + loopnez a4, ._main_loop_short_matrix_length + l8ui a15, a7, 0 // Load 8 bits from src_buff a7 to a15, offset 0 + l8ui a14, a7, 1 // Load 8 bits from src_buff a7 to a14, offset 1 + l8ui a13, a7, 2 // Load 8 bits from src_buff a7 to a13, offset 2 + s8i a15, a3, 0 // Save 8 bits from a15 to dest_buff a3, offset 0 + s8i a14, a3, 1 // Save 8 bits from a14 to dest_buff a3, offset 1 + s8i a13, a3, 2 // Save 8 bits from a13 to dest_buff a3, offset 2 + addi.n a7, a7, 3 // Increment src_buff pointer a7 by 3 + addi.n a3, a3, 3 // Increment dest_buff pointer a3 by 3 + ._main_loop_short_matrix_length: + + add a3, a3, a6 // dest_buff (a3) = dest_buff (a3) + dest_matrix_padding (a6) + add a7, a7, a8 // src_buff (a7) = src_buff (a7) + src_matrix_padding (a8) + addi.n a5, a5, -1 // Decrease the outer loop + bnez a5, .outer_loop_short_matrix_length + + movi.n a2, 1 // Return LV_RESULT_OK = 1 + retw.n // Return diff --git a/components/espressif__esp_lvgl_port/test_apps/lvgl_port/CMakeLists.txt b/components/espressif__esp_lvgl_port/test_apps/lvgl_port/CMakeLists.txt new file mode 100644 index 00000000..6324f9c5 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/lvgl_port/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +set(COMPONENTS main) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(test_esp_lvgl_port) diff --git a/components/espressif__esp_lvgl_port/test_apps/lvgl_port/main/CMakeLists.txt b/components/espressif__esp_lvgl_port/test_apps/lvgl_port/main/CMakeLists.txt new file mode 100644 index 00000000..ea368014 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/lvgl_port/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register( + SRCS "test.c" + REQUIRES unity driver esp_lcd +) diff --git a/components/espressif__esp_lvgl_port/test_apps/lvgl_port/main/idf_component.yml b/components/espressif__esp_lvgl_port/test_apps/lvgl_port/main/idf_component.yml new file mode 100644 index 00000000..91c60775 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/lvgl_port/main/idf_component.yml @@ -0,0 +1,10 @@ +## IDF Component Manager Manifest File +dependencies: + idf: ">=4.4" + esp_lcd_ili9341: "^2.0.1" + esp_lcd_touch_gt911: + version: "^1" + override_path: "../../../../lcd_touch/esp_lcd_touch_gt911/" + esp_lvgl_port: + version: "*" + override_path: "../../../" diff --git a/components/espressif__esp_lvgl_port/test_apps/lvgl_port/main/test.c b/components/espressif__esp_lvgl_port/test_apps/lvgl_port/main/test.c new file mode 100644 index 00000000..dbb13dc5 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/lvgl_port/main/test.c @@ -0,0 +1,409 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + + +#include "esp_err.h" +#include "esp_log.h" +#include "esp_check.h" +#include "driver/i2c_master.h" +#include "driver/gpio.h" +#include "driver/spi_master.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lvgl_port.h" +#include "esp_lcd_ili9341.h" + +#include "esp_lcd_touch_gt911.h" + +#include "unity.h" + +/* LCD size */ +#define EXAMPLE_LCD_H_RES (320) +#define EXAMPLE_LCD_V_RES (240) + +/* LCD settings */ +#define EXAMPLE_LCD_SPI_NUM (SPI3_HOST) +#define EXAMPLE_LCD_PIXEL_CLK_HZ (40 * 1000 * 1000) +#define EXAMPLE_LCD_CMD_BITS (8) +#define EXAMPLE_LCD_PARAM_BITS (8) +#define EXAMPLE_LCD_BITS_PER_PIXEL (16) +#define EXAMPLE_LCD_DRAW_BUFF_DOUBLE (1) +#define EXAMPLE_LCD_DRAW_BUFF_HEIGHT (50) +#define EXAMPLE_LCD_BL_ON_LEVEL (1) + +/* LCD pins */ +#define EXAMPLE_LCD_GPIO_SCLK (GPIO_NUM_7) +#define EXAMPLE_LCD_GPIO_MOSI (GPIO_NUM_6) +#define EXAMPLE_LCD_GPIO_RST (GPIO_NUM_48) +#define EXAMPLE_LCD_GPIO_DC (GPIO_NUM_4) +#define EXAMPLE_LCD_GPIO_CS (GPIO_NUM_5) +#define EXAMPLE_LCD_GPIO_BL (GPIO_NUM_47) + +/* Touch settings */ +#define EXAMPLE_TOUCH_I2C_NUM (0) +#define EXAMPLE_TOUCH_I2C_CLK_HZ (400000) + +/* LCD touch pins */ +#define EXAMPLE_TOUCH_I2C_SCL (GPIO_NUM_18) +#define EXAMPLE_TOUCH_I2C_SDA (GPIO_NUM_8) +#define EXAMPLE_TOUCH_GPIO_INT (GPIO_NUM_3) + +static char *TAG = "test"; + +/* LCD IO and panel */ +static esp_lcd_panel_io_handle_t lcd_io = NULL; +static esp_lcd_panel_handle_t lcd_panel = NULL; +static esp_lcd_panel_io_handle_t tp_io_handle = NULL; +static esp_lcd_touch_handle_t touch_handle = NULL; + +/* LVGL display and touch */ +static lv_display_t *lvgl_disp = NULL; +static lv_indev_t *lvgl_touch_indev = NULL; +static i2c_master_bus_handle_t i2c_handle = NULL; + +static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { + {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0}, + {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0}, + {0xC5, (uint8_t []){0xD0}, 1, 0}, + {0xC1, (uint8_t []){0x02}, 1, 0}, + {0xB4, (uint8_t []){0x02}, 1, 0}, + {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0}, + {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0}, + + {0xB1, (uint8_t []){00, 0x1B}, 2, 0}, + {0x36, (uint8_t []){0x08}, 1, 0}, + {0x3A, (uint8_t []){0x55}, 1, 0}, + {0xB7, (uint8_t []){0x06}, 1, 0}, + + {0x11, (uint8_t []){0}, 0x80, 0}, + {0x29, (uint8_t []){0}, 0x80, 0}, + + {0, (uint8_t []){0}, 0xff, 0}, +}; + +static esp_err_t app_lcd_init(void) +{ + esp_err_t ret = ESP_OK; + + /* LCD backlight */ + gpio_config_t bk_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << EXAMPLE_LCD_GPIO_BL + }; + ESP_ERROR_CHECK(gpio_config(&bk_gpio_config)); + + /* LCD initialization */ + ESP_LOGD(TAG, "Initialize SPI bus"); + const spi_bus_config_t buscfg = { + .sclk_io_num = EXAMPLE_LCD_GPIO_SCLK, + .mosi_io_num = EXAMPLE_LCD_GPIO_MOSI, + .miso_io_num = GPIO_NUM_NC, + .quadwp_io_num = GPIO_NUM_NC, + .quadhd_io_num = GPIO_NUM_NC, + .max_transfer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_DRAW_BUFF_HEIGHT * sizeof(uint16_t), + }; + ESP_RETURN_ON_ERROR(spi_bus_initialize(EXAMPLE_LCD_SPI_NUM, &buscfg, SPI_DMA_CH_AUTO), TAG, "SPI init failed"); + + ESP_LOGD(TAG, "Install panel IO"); + const esp_lcd_panel_io_spi_config_t io_config = { + .dc_gpio_num = EXAMPLE_LCD_GPIO_DC, + .cs_gpio_num = EXAMPLE_LCD_GPIO_CS, + .pclk_hz = EXAMPLE_LCD_PIXEL_CLK_HZ, + .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS, + .lcd_param_bits = EXAMPLE_LCD_PARAM_BITS, + .spi_mode = 0, + .trans_queue_depth = 10, + }; + ESP_GOTO_ON_ERROR(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)EXAMPLE_LCD_SPI_NUM, &io_config, &lcd_io), err, + TAG, "New panel IO failed"); + + ESP_LOGD(TAG, "Install LCD driver"); + const ili9341_vendor_config_t vendor_config = { + .init_cmds = &vendor_specific_init[0], + .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t), + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = EXAMPLE_LCD_GPIO_RST, + .flags.reset_active_high = 1, +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0) + .rgb_endian = LCD_RGB_ENDIAN_BGR, +#else + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR, +#endif + .bits_per_pixel = EXAMPLE_LCD_BITS_PER_PIXEL, + .vendor_config = (void *) &vendor_config, + }; + ESP_GOTO_ON_ERROR(esp_lcd_new_panel_ili9341(lcd_io, &panel_config, &lcd_panel), err, TAG, "New panel failed"); + + esp_lcd_panel_reset(lcd_panel); + esp_lcd_panel_init(lcd_panel); + esp_lcd_panel_mirror(lcd_panel, true, true); + esp_lcd_panel_disp_on_off(lcd_panel, true); + + /* LCD backlight on */ + ESP_ERROR_CHECK(gpio_set_level(EXAMPLE_LCD_GPIO_BL, EXAMPLE_LCD_BL_ON_LEVEL)); + + return ret; + +err: + if (lcd_panel) { + esp_lcd_panel_del(lcd_panel); + } + if (lcd_io) { + esp_lcd_panel_io_del(lcd_io); + } + spi_bus_free(EXAMPLE_LCD_SPI_NUM); + return ret; +} + +static esp_err_t app_lcd_deinit(void) +{ + ESP_RETURN_ON_ERROR(esp_lcd_panel_del(lcd_panel), TAG, "LCD panel deinit failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_del(lcd_io), TAG, "LCD IO deinit failed"); + ESP_RETURN_ON_ERROR(spi_bus_free(EXAMPLE_LCD_SPI_NUM), TAG, "SPI BUS free failed"); + ESP_RETURN_ON_ERROR(gpio_reset_pin(EXAMPLE_LCD_GPIO_BL), TAG, "Reset BL pin failed"); + return ESP_OK; +} + +static esp_err_t app_touch_init(void) +{ + /* Initilize I2C */ + const i2c_master_bus_config_t i2c_config = { + .i2c_port = EXAMPLE_TOUCH_I2C_NUM, + .sda_io_num = EXAMPLE_TOUCH_I2C_SDA, + .scl_io_num = EXAMPLE_TOUCH_I2C_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + }; + ESP_RETURN_ON_ERROR(i2c_new_master_bus(&i2c_config, &i2c_handle), TAG, ""); + + /* Initialize touch HW */ + const esp_lcd_touch_config_t tp_cfg = { + .x_max = EXAMPLE_LCD_H_RES, + .y_max = EXAMPLE_LCD_V_RES, + .rst_gpio_num = GPIO_NUM_NC, // Shared with LCD reset + .int_gpio_num = EXAMPLE_TOUCH_GPIO_INT, + .levels = { + .reset = 0, + .interrupt = 0, + }, + .flags = { + .swap_xy = 0, + .mirror_x = 1, + .mirror_y = 0, + }, + }; + esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); + tp_io_config.scl_speed_hz = EXAMPLE_TOUCH_I2C_CLK_HZ; + ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_i2c(i2c_handle, &tp_io_config, &tp_io_handle), TAG, ""); + return esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &touch_handle); +} + +static esp_err_t app_touch_deinit(void) +{ + ESP_RETURN_ON_ERROR(esp_lcd_touch_del(touch_handle), TAG, "Touch deinit failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_del(tp_io_handle), TAG, "Touch IO deinit failed"); + ESP_RETURN_ON_ERROR(i2c_del_master_bus(i2c_handle), TAG, "I2C deinit failed"); + return ESP_OK; +} + +static esp_err_t app_lvgl_init(int task_affinity) +{ + /* Initialize LVGL */ + lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + lvgl_cfg.task_affinity = task_affinity; + ESP_RETURN_ON_ERROR(lvgl_port_init(&lvgl_cfg), TAG, "LVGL port initialization failed"); + + /* Add LCD screen */ + ESP_LOGD(TAG, "Add LCD screen"); + const lvgl_port_display_cfg_t disp_cfg = { + .io_handle = lcd_io, + .panel_handle = lcd_panel, + .buffer_size = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_DRAW_BUFF_HEIGHT * sizeof(uint16_t), + .double_buffer = EXAMPLE_LCD_DRAW_BUFF_DOUBLE, + .hres = EXAMPLE_LCD_H_RES, + .vres = EXAMPLE_LCD_V_RES, + .monochrome = false, + /* Rotation values must be same as used in esp_lcd for initial settings of the screen */ + .rotation = { + .swap_xy = false, + .mirror_x = true, + .mirror_y = true, + }, + .flags = { + .buff_dma = true, +#if LVGL_VERSION_MAJOR >= 9 + .swap_bytes = true, +#endif + } + }; + lvgl_disp = lvgl_port_add_disp(&disp_cfg); + + /* Add touch input (for selected screen) */ + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = lvgl_disp, + .handle = touch_handle, + }; + lvgl_touch_indev = lvgl_port_add_touch(&touch_cfg); + + return ESP_OK; +} + +static esp_err_t app_lvgl_deinit(void) +{ + ESP_RETURN_ON_ERROR(lvgl_port_remove_touch(lvgl_touch_indev), TAG, "LVGL touch removing failed"); + gpio_uninstall_isr_service(); + + ESP_RETURN_ON_ERROR(lvgl_port_remove_disp(lvgl_disp), TAG, "LVGL disp removing failed"); + ESP_RETURN_ON_ERROR(lvgl_port_deinit(), TAG, "LVGL deinit failed"); + + return ESP_OK; +} + +static void _app_button_cb(lv_event_t *e) +{ + lv_disp_rotation_t rotation = lv_disp_get_rotation(lvgl_disp); + rotation++; + if (rotation > LV_DISPLAY_ROTATION_270) { + rotation = LV_DISPLAY_ROTATION_0; + } + + /* LCD HW rotation */ + lv_disp_set_rotation(lvgl_disp, rotation); +} + +static void app_main_display(void) +{ + lv_obj_t *scr = lv_scr_act(); + + /* Task lock */ + lvgl_port_lock(0); + + /* Your LVGL objects code here .... */ + + /* Label */ + lv_obj_t *label = lv_label_create(scr); + lv_obj_set_width(label, EXAMPLE_LCD_H_RES); + lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0); +#if LVGL_VERSION_MAJOR == 8 + lv_label_set_recolor(label, true); + lv_label_set_text(label, + "#FF0000 "LV_SYMBOL_BELL" Hello world Espressif and LVGL "LV_SYMBOL_BELL"#\n#FF9400 "LV_SYMBOL_WARNING" For simplier initialization, use BSP "LV_SYMBOL_WARNING" #"); +#else + lv_label_set_text(label, + LV_SYMBOL_BELL" Hello world Espressif and LVGL "LV_SYMBOL_BELL"\n "LV_SYMBOL_WARNING" For simplier initialization, use BSP "LV_SYMBOL_WARNING); +#endif + lv_obj_align(label, LV_ALIGN_CENTER, 0, -30); + + /* Button */ + lv_obj_t *btn = lv_btn_create(scr); + label = lv_label_create(btn); + lv_label_set_text_static(label, "Rotate screen"); + lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -30); + lv_obj_add_event_cb(btn, _app_button_cb, LV_EVENT_CLICKED, NULL); + + /* Task unlock */ + lvgl_port_unlock(); +} + +// Some resources are lazy allocated in the LCD driver, the threadhold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (200) + +static void check_leak(size_t start_free, size_t end_free, const char *type) +{ + ssize_t delta = start_free - end_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, start_free, end_free, delta); + TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE (delta, TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +TEST_CASE("Main test LVGL port", "[lvgl port]") +{ + size_t start_freemem_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t start_freemem_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + + ESP_LOGI(TAG, "Initilize LCD."); + + /* LCD HW initialization */ + TEST_ASSERT_EQUAL(app_lcd_init(), ESP_OK); + + /* Touch initialization */ + TEST_ASSERT_EQUAL(app_touch_init(), ESP_OK); + + size_t start_lvgl_freemem_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t start_lvgl_freemem_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + + ESP_LOGI(TAG, "Initilize LVGL."); + + /* LVGL initialization - no task affinity */ + TEST_ASSERT_EQUAL(app_lvgl_init(-1), ESP_OK); + + /* Show LVGL objects */ + app_main_display(); + + vTaskDelay(5000 / portTICK_PERIOD_MS); + + /* LVGL deinit */ + TEST_ASSERT_EQUAL(app_lvgl_deinit(), ESP_OK); + + /* When using LVGL8, it takes some time to release all memory */ + vTaskDelay(1000 / portTICK_PERIOD_MS); + + ESP_LOGI(TAG, "LVGL deinitialized."); + + esp_reent_cleanup(); + size_t end_lvgl_freemem_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t end_lvgl_freemem_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(start_lvgl_freemem_8bit, end_lvgl_freemem_8bit, "8BIT LVGL"); + check_leak(start_lvgl_freemem_32bit, end_lvgl_freemem_32bit, "32BIT LVGL"); + + ESP_LOGI(TAG, "Initilize LVGL - task affinity to core 1"); + + /* LVGL initialization - task affinity to core 1 */ + TEST_ASSERT_EQUAL(app_lvgl_init(1), ESP_OK); + + /* Show LVGL objects */ + app_main_display(); + + vTaskDelay(5000 / portTICK_PERIOD_MS); + + /* LVGL deinit */ + TEST_ASSERT_EQUAL(app_lvgl_deinit(), ESP_OK); + + /* When using LVGL8, it takes some time to release all memory */ + vTaskDelay(1000 / portTICK_PERIOD_MS); + + ESP_LOGI(TAG, "LVGL deinitialized."); + + esp_reent_cleanup(); + end_lvgl_freemem_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + end_lvgl_freemem_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(start_lvgl_freemem_8bit, end_lvgl_freemem_8bit, "8BIT LVGL"); + check_leak(start_lvgl_freemem_32bit, end_lvgl_freemem_32bit, "32BIT LVGL"); + + /* Touch deinit */ + TEST_ASSERT_EQUAL(app_touch_deinit(), ESP_OK); + + /* LCD deinit */ + TEST_ASSERT_EQUAL(app_lcd_deinit(), ESP_OK); + + vTaskDelay(1000 / portTICK_PERIOD_MS); + + ESP_LOGI(TAG, "LCD deinitilized."); + + esp_reent_cleanup(); + size_t end_freemem_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t end_freemem_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(start_freemem_8bit, end_freemem_8bit, "8BIT"); + check_leak(start_freemem_32bit, end_freemem_32bit, "32BIT"); + + +} + +void app_main(void) +{ + printf("TEST ESP LVGL port\n\r"); + unity_run_menu(); +} diff --git a/components/espressif__esp_lvgl_port/test_apps/lvgl_port/sdkconfig.ci.asm_render b/components/espressif__esp_lvgl_port/test_apps/lvgl_port/sdkconfig.ci.asm_render new file mode 100644 index 00000000..30815ee1 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/lvgl_port/sdkconfig.ci.asm_render @@ -0,0 +1,6 @@ +# sdkconfig to enable the SIMD in the lvgl_port + +# Set custom ASM render and provide a header file with function prototypes +CONFIG_LV_DRAW_SW_ASM_CUSTOM=y +CONFIG_LV_USE_DRAW_SW_ASM=255 +CONFIG_LV_DRAW_SW_ASM_CUSTOM_INCLUDE="esp_lvgl_port_lv_blend.h" diff --git a/components/espressif__esp_lvgl_port/test_apps/lvgl_port/sdkconfig.defaults b/components/espressif__esp_lvgl_port/test_apps/lvgl_port/sdkconfig.defaults new file mode 100644 index 00000000..fa2105d6 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/lvgl_port/sdkconfig.defaults @@ -0,0 +1,6 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT_EN=n +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/CMakeLists.txt b/components/espressif__esp_lvgl_port/test_apps/simd/CMakeLists.txt new file mode 100644 index 00000000..735c48dc --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +project(test_lvgl_simd) \ No newline at end of file diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/README.md b/components/espressif__esp_lvgl_port/test_apps/simd/README.md new file mode 100644 index 00000000..38eeb79b --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/README.md @@ -0,0 +1,135 @@ +# HW Acceleration using SIMD assembly instructions + +Test app accommodates two types of tests: [`functionality test`](#Functionality-test) and [`benchmark test`](#Benchmark-test). Both tests are provided per each function written in assembly (typically per each assembly file). Both test apps use a hard copy of LVGL blending API, representing an ANSI implementation of the LVGL blending functions. The hard copy is present in [`lv_blend`](main/lv_blend/) folder. + +Assembly source files could be found in the [`lvgl_port`](../../src/lvgl9/simd/) component. Header file with the assembly function prototypes is provided into the LVGL using Kconfig option `LV_DRAW_SW_ASM_CUSTOM_INCLUDE` and can be found in the [`lvgl_port/include`](../../include/esp_lvgl_port_lv_blend.h) + +## Benchmark results for LV Fill functions (memset) + +| Color format | Matrix size | Memory alignment | ASM version | ANSI C version | +| :----------- | :---------- | :--------------- | :------------- | :------------- | +| ARGB8888 | 128x128 | 16 byte | 0.327 | 1.600 | +| | 127x127 | 1 byte | 0.488 | 1.597 | +| RGB565 | 128x128 | 16 byte | 0.196 | 1.146 | +| | 127x127 | 1 byte | 0.497 | 1.124 | +| RGB888 | 128x128 | 16 byte | 0.608 | 4.062 | +| | 127x127 | 1 byte | 0.818 | 3.969 | +* this data was obtained by running [benchmark tests](#benchmark-test) on 128x128 16 byte aligned matrix (ideal case) and 127x127 1 byte aligned matrix (worst case) +* the values represent cycles per sample to perform simple fill of the matrix on esp32s3 + +## Benchmark results for LV Image functions (memcpy) + +| Color format | Matrix size | Memory alignment | ASM version | ANSI C version | +| :----------- | :---------- | :--------------- | :------------- | :------------- | +| RGB565 | 128x128 | 16 byte | 0.352 | 3.437 | +| | 127x128 | 1 byte | 0.866 | 5.978 | +| RGB888 | 128x128 | 16 byte | 0.744 | 4.002 | +| | 127x128 | 1 byte | 1.002 | 7.998 | +* this data was obtained by running [benchmark tests](#benchmark-test) on 128x128 16 byte aligned matrix (ideal case) and 127x128 1 byte aligned matrix (worst case) +* the values represent cycles per sample to perform memory copy between two matrices on esp32s3 + +## Functionality test +* Tests, whether the HW accelerated assembly version of an LVGL function provides the same results as the ANSI version +* A top-level flow of the functionality test: + * generate a test matrix with test parameters (matrix width, matrix height, memory alignment.. ) + * run an ANSI version of a DUT function with the generated input parameters + * run an assembly version of a DUT function with the same input parameters + * compare the results given by the ANSI and the assembly DUTs + * the results shall be the same + * repeat all the steps for a set of different input parameters, checking different matrix heights, widths.. + +## Benchmark test +* Tests, whether the HW accelerated assembly version of an LVGL function provides a performance increase over the ANSI version +* A top-level flow of the functionality test: + * generate a test matrix with test parameters (matrix width, matrix height, memory alignment.. ) + * run an ANSI version of a DUT function with the generated input parameters multiple times (1000 times for example), while counting CPU cycles + * run an assembly version of a DUT function with the generated input parameters multiple times (1000 times for example), while counting CPU cycles + * compare the results given by the ANSI and the assembly DUTs + * the assembly version of the DUT function shall be faster than the ANSI version of the DUT function + +## Run the test app + +The test app is intended to be used only with esp32 and esp32s3 + + idf.py build + +## Example output + +``` +I (302) main_task: Started on CPU0 +I (322) main_task: Calling app_main() +______ _____ ______ _ _ +| _ \/ ___|| ___ \ | | | | +| | | |\ `--. | |_/ / | |_ ___ ___ | |_ +| | | | `--. \| __/ | __| / _ \/ __|| __| +| |/ / /\__/ /| | | |_ | __/\__ \| |_ +|___/ \____/ \_| \__| \___||___/ \__| + + +Press ENTER to see the list of tests. + + + +Here's the test menu, pick your combo: +(1) "Test fill functionality ARGB8888" [fill][functionality][ARGB8888] +(2) "Test fill functionality RGB565" [fill][functionality][RGB565] +(3) "LV Fill benchmark ARGB8888" [fill][benchmark][ARGB8888] +(4) "LV Fill benchmark RGB565" [fill][benchmark][RGB565] +(5) "LV Image functionality RGB565 blend to RGB565" [image][functionality][RGB565] +(6) "LV Image benchmark RGB565 blend to RGB565" [image][benchmark][RGB565] + +Enter test for running. +``` + +### Example of a functionality test run + +``` +Running Test fill functionality ARGB8888... +I (81512) LV Fill Functionality: running test for ARGB8888 color format +I (84732) LV Fill Functionality: test combinations: 31824 + +MALLOC_CAP_8BIT usage: Free memory delta: 0 Leak threshold: -800 +MALLOC_CAP_32BIT usage: Free memory delta: 0 Leak threshold: -800 +./main/test_lv_fill_functionality.c:102:Test fill functionality ARGB8888:PASS +Test ran in 3242ms +``` +The test gives a simple FAIL/PASS result after comparison of the two DUTs results. +Also gives us an information about how many combinations (input parameters) the functionality test run with, `31824` in this case. + +### Example of a benchmark test run + +``` +Running LV Fill benchmark ARGB8888... +I (163492) LV Fill Benchmark: running test for ARGB8888 color format +I (163522) LV Fill Benchmark: ASM ideal case: 5363.123 cycles for 128x128 matrix, 0.327 cycles per sample +I (163572) LV Fill Benchmark: ASM corner case: 7868.724 cycles for 127x127 matrix, 0.488 cycles per sample + +I (163732) LV Fill Benchmark: ANSI ideal case: 26219.137 cycles for 128x128 matrix, 1.600 cycles per sample +I (163902) LV Fill Benchmark: ANSI corner case: 25762.178 cycles for 127x127 matrix, 1.597 cycles per sample + +MALLOC_CAP_8BIT usage: Free memory delta: -220 Leak threshold: -800 +MALLOC_CAP_8BIT potential leak: Before 393820 bytes free, After 393600 bytes free (delta 220) +MALLOC_CAP_32BIT usage: Free memory delta: -220 Leak threshold: -800 +MALLOC_CAP_32BIT potential leak: Before 393820 bytes free, After 393600 bytes free (delta 220) +./main/test_lv_fill_benchmark.c:69:LV Fill benchmark ARGB8888:PASS +Test ran in 458ms +``` + +The test provides couple of information: +* Total number of CPU cycles for the whole DUT function + * `5363.123` cycles for the assembly DUT function + * `26219.137` cycles for the ANSI DUT function +* Number of CPU cycles per sample, which is basically the total number of CPU cycles divided by the test matrix area + * `0.327` cycles per sample for the assembly DUT + * `1.6` cycles per sample for the ANSI DUT + * In this case, the assembly implementation has achieved a performance increase in around 4.9-times, comparing to the ANSI implementation. +* Range of the CPU cycles (a best case and a corner case scenarios) into which, the DUT functions are expected to fit into + * The execution time of those function highly depends on the input parameters, thus a boundary scenarios for input parameters shall be set + * An example of such a boundaries is in a table below + * The benchmark boundary would help us to get an performance expectations of the real scenarios + +Example of an best and corner case input parameters for benchmark test, for a color format `ARGB8888` +| Test matrix params | Memory alignment | Width | Height | Stride | +| :----------------- | :--------------- | :------------- | :------------- | :------------- | +| Best case | 16-byte aligned | Multiple of 8 | Multiple of 8 | Multiple of 8 | +| Corner case | 1-byte aligned | Not power of 2 | Not power of 2 | Not power of 2 | diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/CMakeLists.txt b/components/espressif__esp_lvgl_port/test_apps/simd/main/CMakeLists.txt new file mode 100644 index 00000000..20c061ff --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/CMakeLists.txt @@ -0,0 +1,31 @@ +# Include SIMD assembly source code for rendering +if(CONFIG_IDF_TARGET_ESP32 OR CONFIG_IDF_TARGET_ESP32S3) + message(VERBOSE "Compiling SIMD") + set(PORT_PATH "../../../src/lvgl9") + + if(CONFIG_IDF_TARGET_ESP32S3) + file(GLOB_RECURSE ASM_SOURCES ${PORT_PATH}/simd/*_esp32s3.S) # Select only esp32s3 related files + else() + file(GLOB_RECURSE ASM_SOURCES ${PORT_PATH}/simd/*_esp32.S) # Select only esp32 related files + endif() + + file(GLOB_RECURSE ASM_MACROS ${PORT_PATH}/simd/lv_macro_*.S) # Explicitly add all assembler macro files + +else() + message(WARNING "This test app is intended only for esp32 and esp32s3") +endif() + +# Hard copy of LV files +file(GLOB_RECURSE BLEND_SRCS lv_blend/src/*.c) + +idf_component_register(SRCS "test_app_main.c" + "test_lv_fill_functionality.c" # memset tests + "test_lv_fill_benchmark.c" + "test_lv_image_functionality.c" # memcpy tests + "test_lv_image_benchmark.c" + ${BLEND_SRCS} # Hard copy of LVGL's blend API, to simplify testing + ${ASM_SOURCES} # Assembly src files + ${ASM_MACROS} # Assembly macro files + INCLUDE_DIRS "lv_blend/include" "../../../include" + REQUIRES unity + WHOLE_ARCHIVE) diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/Kconfig.projbuild b/components/espressif__esp_lvgl_port/test_apps/simd/main/Kconfig.projbuild new file mode 100644 index 00000000..8d2c596c --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/Kconfig.projbuild @@ -0,0 +1,5 @@ +# Creating CONFIG_LV_DRAW_SW_ASM_CUSTOM avaliable in lvgl Kconfig to enable assembler source files by deafult + +config LV_DRAW_SW_ASM_CUSTOM + bool + default y \ No newline at end of file diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_assert.h b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_assert.h new file mode 100644 index 00000000..6fe5589d --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_assert.h @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_assert.h + * + */ + +#ifndef LV_ASSERT_H +#define LV_ASSERT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lv_log.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * MACROS + **********************/ +#define LV_ASSERT_HANDLER while(1); /*Halt by default*/ + +#define LV_ASSERT(expr) \ + do { \ + if(!(expr)) { \ + LV_LOG_ERROR("Asserted at expression: %s", #expr); \ + LV_ASSERT_HANDLER \ + } \ + } while(0) + +/*----------------- + * ASSERTS + *-----------------*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_ASSERT_H*/ diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_color.h b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_color.h new file mode 100644 index 00000000..ecb6017e --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_color.h @@ -0,0 +1,272 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_color.h + * + */ + +#ifndef LV_COLOR_H +#define LV_COLOR_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "stdint.h" +#include "stdbool.h" +#include "sdkconfig.h" + +/********************* + * DEFINES + *********************/ +#define LV_ATTRIBUTE_FAST_MEM + +#ifndef LV_COLOR_MIX_ROUND_OFS +#ifdef CONFIG_LV_COLOR_MIX_ROUND_OFS +#define LV_COLOR_MIX_ROUND_OFS CONFIG_LV_COLOR_MIX_ROUND_OFS +#else +#define LV_COLOR_MIX_ROUND_OFS 0 +#endif +#endif + +/** + * Opacity percentages. + */ + +typedef enum { + LV_OPA_TRANSP = 0, + LV_OPA_0 = 0, + LV_OPA_10 = 25, + LV_OPA_20 = 51, + LV_OPA_30 = 76, + LV_OPA_40 = 102, + LV_OPA_50 = 127, + LV_OPA_60 = 153, + LV_OPA_70 = 178, + LV_OPA_80 = 204, + LV_OPA_90 = 229, + LV_OPA_100 = 255, + LV_OPA_COVER = 255, +} lv_opa_t; + +#define LV_OPA_MIN 2 /*Opacities below this will be transparent*/ +#define LV_OPA_MAX 253 /*Opacities above this will fully cover*/ + +#define LV_COLOR_FORMAT_GET_BPP(cf) ( \ + (cf) == LV_COLOR_FORMAT_I1 ? 1 : \ + (cf) == LV_COLOR_FORMAT_A1 ? 1 : \ + (cf) == LV_COLOR_FORMAT_I2 ? 2 : \ + (cf) == LV_COLOR_FORMAT_A2 ? 2 : \ + (cf) == LV_COLOR_FORMAT_I4 ? 4 : \ + (cf) == LV_COLOR_FORMAT_A4 ? 4 : \ + (cf) == LV_COLOR_FORMAT_L8 ? 8 : \ + (cf) == LV_COLOR_FORMAT_A8 ? 8 : \ + (cf) == LV_COLOR_FORMAT_I8 ? 8 : \ + (cf) == LV_COLOR_FORMAT_AL88 ? 16 : \ + (cf) == LV_COLOR_FORMAT_RGB565 ? 16 : \ + (cf) == LV_COLOR_FORMAT_RGB565A8 ? 16 : \ + (cf) == LV_COLOR_FORMAT_ARGB8565 ? 24 : \ + (cf) == LV_COLOR_FORMAT_RGB888 ? 24 : \ + (cf) == LV_COLOR_FORMAT_ARGB8888 ? 32 : \ + (cf) == LV_COLOR_FORMAT_XRGB8888 ? 32 : \ + 0 \ + ) + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + uint8_t blue; + uint8_t green; + uint8_t red; +} lv_color_t; + +typedef struct { + uint16_t blue : 5; + uint16_t green : 6; + uint16_t red : 5; +} lv_color16_t; + +typedef struct { + uint8_t blue; + uint8_t green; + uint8_t red; + uint8_t alpha; +} lv_color32_t; + +typedef struct { + uint16_t h; + uint8_t s; + uint8_t v; +} lv_color_hsv_t; + +typedef struct { + uint8_t lumi; + uint8_t alpha; +} lv_color16a_t; + +typedef enum { + LV_COLOR_FORMAT_UNKNOWN = 0, + + LV_COLOR_FORMAT_RAW = 0x01, + LV_COLOR_FORMAT_RAW_ALPHA = 0x02, + + /*<=1 byte (+alpha) formats*/ + LV_COLOR_FORMAT_L8 = 0x06, + LV_COLOR_FORMAT_I1 = 0x07, + LV_COLOR_FORMAT_I2 = 0x08, + LV_COLOR_FORMAT_I4 = 0x09, + LV_COLOR_FORMAT_I8 = 0x0A, + LV_COLOR_FORMAT_A8 = 0x0E, + + /*2 byte (+alpha) formats*/ + LV_COLOR_FORMAT_RGB565 = 0x12, + LV_COLOR_FORMAT_ARGB8565 = 0x13, /**< Not supported by sw renderer yet. */ + LV_COLOR_FORMAT_RGB565A8 = 0x14, /**< Color array followed by Alpha array*/ + LV_COLOR_FORMAT_AL88 = 0x15, /**< L8 with alpha >*/ + + /*3 byte (+alpha) formats*/ + LV_COLOR_FORMAT_RGB888 = 0x0F, + LV_COLOR_FORMAT_ARGB8888 = 0x10, + LV_COLOR_FORMAT_XRGB8888 = 0x11, + + /*Formats not supported by software renderer but kept here so GPU can use it*/ + LV_COLOR_FORMAT_A1 = 0x0B, + LV_COLOR_FORMAT_A2 = 0x0C, + LV_COLOR_FORMAT_A4 = 0x0D, + + /* reference to https://wiki.videolan.org/YUV/ */ + /*YUV planar formats*/ + LV_COLOR_FORMAT_YUV_START = 0x20, + LV_COLOR_FORMAT_I420 = LV_COLOR_FORMAT_YUV_START, /*YUV420 planar(3 plane)*/ + LV_COLOR_FORMAT_I422 = 0x21, /*YUV422 planar(3 plane)*/ + LV_COLOR_FORMAT_I444 = 0x22, /*YUV444 planar(3 plane)*/ + LV_COLOR_FORMAT_I400 = 0x23, /*YUV400 no chroma channel*/ + LV_COLOR_FORMAT_NV21 = 0x24, /*YUV420 planar(2 plane), UV plane in 'V, U, V, U'*/ + LV_COLOR_FORMAT_NV12 = 0x25, /*YUV420 planar(2 plane), UV plane in 'U, V, U, V'*/ + + /*YUV packed formats*/ + LV_COLOR_FORMAT_YUY2 = 0x26, /*YUV422 packed like 'Y U Y V'*/ + LV_COLOR_FORMAT_UYVY = 0x27, /*YUV422 packed like 'U Y V Y'*/ + + LV_COLOR_FORMAT_YUV_END = LV_COLOR_FORMAT_UYVY, + + /*Color formats in which LVGL can render*/ +#if LV_COLOR_DEPTH == 8 + LV_COLOR_FORMAT_NATIVE = LV_COLOR_FORMAT_L8, + LV_COLOR_FORMAT_NATIVE_WITH_ALPHA = LV_COLOR_FORMAT_AL88, +#elif LV_COLOR_DEPTH == 16 + LV_COLOR_FORMAT_NATIVE = LV_COLOR_FORMAT_RGB565, + LV_COLOR_FORMAT_NATIVE_WITH_ALPHA = LV_COLOR_FORMAT_RGB565A8, +#elif LV_COLOR_DEPTH == 24 + LV_COLOR_FORMAT_NATIVE = LV_COLOR_FORMAT_RGB888, + LV_COLOR_FORMAT_NATIVE_WITH_ALPHA = LV_COLOR_FORMAT_ARGB8888, +#elif LV_COLOR_DEPTH == 32 + LV_COLOR_FORMAT_NATIVE = LV_COLOR_FORMAT_XRGB8888, + LV_COLOR_FORMAT_NATIVE_WITH_ALPHA = LV_COLOR_FORMAT_ARGB8888, +#endif +} lv_color_format_t; + +/********************** + * MACROS + **********************/ + +#define LV_COLOR_MAKE(r8, g8, b8) {b8, g8, r8} + +#define LV_OPA_MIX2(a1, a2) (((int32_t)(a1) * (a2)) >> 8) +#define LV_OPA_MIX3(a1, a2, a3) (((int32_t)(a1) * (a2) * (a3)) >> 16) + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create an ARGB8888 color from RGB888 + alpha + * @param color an RGB888 color + * @param opa the alpha value + * @return the ARGB8888 color + */ +lv_color32_t lv_color_to_32(lv_color_t color, lv_opa_t opa); + +/** + * Convert am RGB888 color to RGB565 stored in `uint16_t` + * @param color and RGB888 color + * @return `color` as RGB565 on `uin16_t` + */ +uint16_t lv_color_to_u16(lv_color_t color); + +/** + * Convert am RGB888 color to XRGB8888 stored in `uint32_t` + * @param color and RGB888 color + * @return `color` as XRGB8888 on `uin32_t` (the alpha channel is always set to 0xFF) + */ +uint32_t lv_color_to_u32(lv_color_t color); + +/** + * Mix two RGB565 colors + * @param c1 the first color (typically the foreground color) + * @param c2 the second color (typically the background color) + * @param mix 0..255, or LV_OPA_0/10/20... + * @return mix == 0: c2 + * mix == 255: c1 + * mix == 128: 0.5 x c1 + 0.5 x c2 + */ +static inline uint16_t LV_ATTRIBUTE_FAST_MEM lv_color_16_16_mix(uint16_t c1, uint16_t c2, uint8_t mix) +{ + if (mix == 255) { + return c1; + } + if (mix == 0) { + return c2; + } + if (c1 == c2) { + return c1; + } + + uint16_t ret; + + /* Source: https://stackoverflow.com/a/50012418/1999969*/ + mix = (uint32_t)((uint32_t)mix + 4) >> 3; + + /*0x7E0F81F = 0b00000111111000001111100000011111*/ + uint32_t bg = (uint32_t)(c2 | ((uint32_t)c2 << 16)) & 0x7E0F81F; + uint32_t fg = (uint32_t)(c1 | ((uint32_t)c1 << 16)) & 0x7E0F81F; + uint32_t result = ((((fg - bg) * mix) >> 5) + bg) & 0x7E0F81F; + ret = (uint16_t)(result >> 16) | result; + + return ret; +} + +/** + * Check if two ARGB8888 color are equal + * @param c1 the first color + * @param c2 the second color + * @return true: equal + */ +static inline bool lv_color32_eq(lv_color32_t c1, lv_color32_t c2) +{ + return *((uint32_t *)&c1) == *((uint32_t *)&c2); +} + +/********************** + * MACROS + **********************/ + +#include "lv_color_op.h" + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_COLOR_H*/ diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_color_op.h b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_color_op.h new file mode 100644 index 00000000..083fcd83 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_color_op.h @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_color_op.h + * + */ + +#ifndef LV_COLOR_OP_H +#define LV_COLOR_OP_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lv_math.h" +#include "lv_color.h" +#include "lv_types.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Mix two colors with a given ratio. + * @param c1 the first color to mix (usually the foreground) + * @param c2 the second color to mix (usually the background) + * @param mix The ratio of the colors. 0: full `c2`, 255: full `c1`, 127: half `c1` and half`c2` + * @return the mixed color + */ +static inline lv_color_t LV_ATTRIBUTE_FAST_MEM lv_color_mix(lv_color_t c1, lv_color_t c2, uint8_t mix) +{ + lv_color_t ret; + + ret.red = LV_UDIV255((uint16_t)c1.red * mix + c2.red * (255 - mix) + LV_COLOR_MIX_ROUND_OFS); + ret.green = LV_UDIV255((uint16_t)c1.green * mix + c2.green * (255 - mix) + LV_COLOR_MIX_ROUND_OFS); + ret.blue = LV_UDIV255((uint16_t)c1.blue * mix + c2.blue * (255 - mix) + LV_COLOR_MIX_ROUND_OFS); + return ret; +} + +/** + * + * @param fg + * @param bg + * @return + * @note Use bg.alpha in the return value + * @note Use fg.alpha as mix ratio + */ +static inline lv_color32_t lv_color_mix32(lv_color32_t fg, lv_color32_t bg) +{ + if (fg.alpha >= LV_OPA_MAX) { + fg.alpha = bg.alpha; + return fg; + } + if (fg.alpha <= LV_OPA_MIN) { + return bg; + } + bg.red = (uint32_t)((uint32_t)fg.red * fg.alpha + (uint32_t)bg.red * (255 - fg.alpha)) >> 8; + bg.green = (uint32_t)((uint32_t)fg.green * fg.alpha + (uint32_t)bg.green * (255 - fg.alpha)) >> 8; + bg.blue = (uint32_t)((uint32_t)fg.blue * fg.alpha + (uint32_t)bg.blue * (255 - fg.alpha)) >> 8; + return bg; +} + +/********************** + * PREDEFINED COLORS + **********************/ + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_COLOR_H*/ diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend.h b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend.h new file mode 100644 index 00000000..40ab1e84 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend.h @@ -0,0 +1,75 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_draw_sw_blend.h + * + */ + +#ifndef LV_DRAW_SW_BLEND_H +#define LV_DRAW_SW_BLEND_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lv_style.h" +#include "lv_color.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + void *dest_buf; + int32_t dest_w; + int32_t dest_h; + int32_t dest_stride; + const lv_opa_t *mask_buf; + int32_t mask_stride; + lv_color_t color; + lv_opa_t opa; + bool use_asm; +} _lv_draw_sw_blend_fill_dsc_t; + +typedef struct { + void *dest_buf; + int32_t dest_w; + int32_t dest_h; + int32_t dest_stride; + const lv_opa_t *mask_buf; + int32_t mask_stride; + const void *src_buf; + int32_t src_stride; + lv_color_format_t src_color_format; + lv_opa_t opa; + lv_blend_mode_t blend_mode; + bool use_asm; +} _lv_draw_sw_blend_image_dsc_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_DRAW_SW_BLEND_H*/ diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_argb8888.h b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_argb8888.h new file mode 100644 index 00000000..a590d955 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_argb8888.h @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_draw_sw_blend_to_argb8888.h + * + */ + +#ifndef LV_DRAW_SW_BLEND_ARGB8888_H +#define LV_DRAW_SW_BLEND_ARGB8888_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lv_draw_sw_blend.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +void /* LV_ATTRIBUTE_FAST_MEM */ lv_draw_sw_blend_color_to_argb8888(_lv_draw_sw_blend_fill_dsc_t *dsc); + +void /* LV_ATTRIBUTE_FAST_MEM */ lv_draw_sw_blend_image_to_argb8888(_lv_draw_sw_blend_image_dsc_t *dsc); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_DRAW_SW_BLEND_ARGB8888_H*/ diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_rgb565.h b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_rgb565.h new file mode 100644 index 00000000..23981888 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_rgb565.h @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_draw_sw_blend_to_rgb565.h + * + */ + +#ifndef LV_DRAW_SW_BLEND_RGB565_H +#define LV_DRAW_SW_BLEND_RGB565_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lv_draw_sw_blend.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +void /* LV_ATTRIBUTE_FAST_MEM */ lv_draw_sw_blend_color_to_rgb565(_lv_draw_sw_blend_fill_dsc_t *dsc); + +void /* LV_ATTRIBUTE_FAST_MEM */ lv_draw_sw_blend_image_to_rgb565(_lv_draw_sw_blend_image_dsc_t *dsc); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_DRAW_SW_BLEND_RGB565_H*/ diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_rgb888.h b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_rgb888.h new file mode 100644 index 00000000..aac3b86e --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_draw_sw_blend_to_rgb888.h @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_draw_sw_blend_to_rgb888.h + * + */ + +#ifndef LV_DRAW_SW_BLEND_RGB888_H +#define LV_DRAW_SW_BLEND_RGB888_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lv_draw_sw_blend.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +void /* LV_ATTRIBUTE_FAST_MEM */ lv_draw_sw_blend_color_to_rgb888(_lv_draw_sw_blend_fill_dsc_t *dsc, + uint32_t dest_px_size); + +void /* LV_ATTRIBUTE_FAST_MEM */ lv_draw_sw_blend_image_to_rgb888(_lv_draw_sw_blend_image_dsc_t *dsc, + uint32_t dest_px_size); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_DRAW_SW_BLEND_RGB888_H*/ diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_log.h b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_log.h new file mode 100644 index 00000000..c7250c96 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_log.h @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_log.h + * + */ + +#ifndef LV_LOG_H +#define LV_LOG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lv_types.h" + +/********************* + * DEFINES + *********************/ + +/*Do nothing if `LV_USE_LOG 0`*/ +#define _lv_log_add(level, file, line, ...) +#define LV_LOG_TRACE(...) do {}while(0) +#define LV_LOG_INFO(...) do {}while(0) +#define LV_LOG_WARN(...) do {}while(0) +#define LV_LOG_ERROR(...) do {}while(0) +#define LV_LOG_USER(...) do {}while(0) +#define LV_LOG(...) do {}while(0) + + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_LOG_H*/ diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_math.h b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_math.h new file mode 100644 index 00000000..52508a8e --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_math.h @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_math.h + * + */ + +#ifndef LV_MATH_H +#define LV_MATH_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lv_types.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * MACROS + **********************/ +#define LV_MIN(a, b) ((a) < (b) ? (a) : (b)) +#define LV_MIN3(a, b, c) (LV_MIN(LV_MIN(a,b), c)) +#define LV_MIN4(a, b, c, d) (LV_MIN(LV_MIN(a,b), LV_MIN(c,d))) + +#define LV_MAX(a, b) ((a) > (b) ? (a) : (b)) +#define LV_MAX3(a, b, c) (LV_MAX(LV_MAX(a,b), c)) +#define LV_MAX4(a, b, c, d) (LV_MAX(LV_MAX(a,b), LV_MAX(c,d))) + +#define LV_CLAMP(min, val, max) (LV_MAX(min, (LV_MIN(val, max)))) + +#define LV_ABS(x) ((x) > 0 ? (x) : (-(x))) +#define LV_UDIV255(x) (((x) * 0x8081U) >> 0x17) + +#define LV_IS_SIGNED(t) (((t)(-1)) < ((t)0)) +#define LV_UMAX_OF(t) (((0x1ULL << ((sizeof(t) * 8ULL) - 1ULL)) - 1ULL) | (0xFULL << ((sizeof(t) * 8ULL) - 4ULL))) +#define LV_SMAX_OF(t) (((0x1ULL << ((sizeof(t) * 8ULL) - 1ULL)) - 1ULL) | (0x7ULL << ((sizeof(t) * 8ULL) - 4ULL))) +#define LV_MAX_OF(t) ((unsigned long)(LV_IS_SIGNED(t) ? LV_SMAX_OF(t) : LV_UMAX_OF(t))) + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_string.h b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_string.h new file mode 100644 index 00000000..2ffd5ac2 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_string.h @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_string.h + * + */ + +#ifndef LV_STRING_H +#define LV_STRING_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +//#include "../lv_conf_internal.h" +#include +#include +#include "lv_types.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * @brief Copies a block of memory from a source address to a destination address. + * @param dst Pointer to the destination array where the content is to be copied. + * @param src Pointer to the source of data to be copied. + * @param len Number of bytes to copy. + * @return Pointer to the destination array. + * @note The function does not check for any overlapping of the source and destination memory blocks. + */ +void *lv_memcpy(void *dst, const void *src, size_t len); + +/** + * @brief Fills a block of memory with a specified value. + * @param dst Pointer to the destination array to fill with the specified value. + * @param v Value to be set. The value is passed as an int, but the function fills + * the block of memory using the unsigned char conversion of this value. + * @param len Number of bytes to be set to the value. + */ +void lv_memset(void *dst, uint8_t v, size_t len); + +/** + * @brief Move a block of memory from source to destination + * @param dst Pointer to the destination array where the content is to be copied. + * @param src Pointer to the source of data to be copied. + * @param len Number of bytes to copy + * @return Pointer to the destination array. + */ +void *lv_memmove(void *dst, const void *src, size_t len); + + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_STRING_H*/ diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_style.h b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_style.h new file mode 100644 index 00000000..dd813add --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_style.h @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_style.h + * + */ + +#ifndef LV_STYLE_H +#define LV_STYLE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/** + * Possible options how to blend opaque drawings + */ +typedef enum { + LV_BLEND_MODE_NORMAL, /**< Simply mix according to the opacity value*/ + LV_BLEND_MODE_ADDITIVE, /**< Add the respective color channels*/ + LV_BLEND_MODE_SUBTRACTIVE,/**< Subtract the foreground from the background*/ + LV_BLEND_MODE_MULTIPLY, /**< Multiply the foreground and background*/ +} lv_blend_mode_t; + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_STYLE_H*/ diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_types.h b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_types.h new file mode 100644 index 00000000..f97a51eb --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/include/lv_types.h @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_types.h + * + */ + +#ifndef LV_TYPES_H +#define LV_TYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/********************** + * TYPEDEFS + **********************/ + +/** + * LVGL error codes. + */ +typedef enum { + LV_RESULT_INVALID = 0, /*Typically indicates that the object is deleted (become invalid) in the action + function or an operation was failed*/ + LV_RESULT_OK, /*The object is valid (no deleted) after the action*/ +} lv_result_t; + +/********************** + * TYPEDEFS + **********************/ + +typedef uintptr_t lv_uintptr_t; + +/********************** + * MACROS + **********************/ + +#define LV_UNUSED(x) ((void)x) + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_TYPES_H*/ diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_color.c b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_color.c new file mode 100644 index 00000000..a2865e47 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_color.c @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_color.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_color.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * GLOBAL VARIABLES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_color32_t lv_color_to_32(lv_color_t color, lv_opa_t opa) +{ + lv_color32_t c32; + c32.red = color.red; + c32.green = color.green; + c32.blue = color.blue; + c32.alpha = opa; + return c32; +} + +uint16_t lv_color_to_u16(lv_color_t color) +{ + return ((color.red & 0xF8) << 8) + ((color.green & 0xFC) << 3) + ((color.blue & 0xF8) >> 3); +} + +uint32_t lv_color_to_u32(lv_color_t color) +{ + return (uint32_t)((uint32_t)0xff << 24) + (color.red << 16) + (color.green << 8) + (color.blue); +} diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_argb8888.c b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_argb8888.c new file mode 100644 index 00000000..bbab6117 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_argb8888.c @@ -0,0 +1,911 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_draw_sw_blend_to_argb8888.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_draw_sw_blend_to_argb8888.h" + +#include "lv_assert.h" +#include "lv_types.h" +#include "lv_log.h" +#include "lv_draw_sw_blend.h" +#include "lv_math.h" +#include "lv_color.h" +#include "lv_string.h" + +#include "esp_lvgl_port_lv_blend.h" + +/********************* + * DEFINES + *********************/ + +#define LV_ATTRIBUTE_FAST_MEM + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_color32_t fg_saved; + lv_color32_t bg_saved; + lv_color32_t res_saved; + lv_opa_t res_alpha_saved; + lv_opa_t ratio_saved; +} lv_color_mix_alpha_cache_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void /* LV_ATTRIBUTE_FAST_MEM */ al88_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc); + +static void /* LV_ATTRIBUTE_FAST_MEM */ l8_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc); + +static void /* LV_ATTRIBUTE_FAST_MEM */ rgb565_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc); + +static void /* LV_ATTRIBUTE_FAST_MEM */ rgb888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, + const uint8_t src_px_size); + +static void /* LV_ATTRIBUTE_FAST_MEM */ argb8888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc); + +static inline void /* LV_ATTRIBUTE_FAST_MEM */ lv_color_8_32_mix(const uint8_t src, lv_color32_t *dest, uint8_t mix); + +static inline lv_color32_t /* LV_ATTRIBUTE_FAST_MEM */ lv_color_32_32_mix(lv_color32_t fg, lv_color32_t bg, + lv_color_mix_alpha_cache_t *cache); + +static void lv_color_mix_with_alpha_cache_init(lv_color_mix_alpha_cache_t *cache); + +static inline void /* LV_ATTRIBUTE_FAST_MEM */ blend_non_normal_pixel(lv_color32_t *dest, lv_color32_t src, + lv_blend_mode_t mode, lv_color_mix_alpha_cache_t *cache); +static inline void * /* LV_ATTRIBUTE_FAST_MEM */ drawbuf_next_row(const void *buf, uint32_t stride); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888 +#define LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_WITH_OPA +#define LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_WITH_MASK +#define LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_MIX_MASK_OPA +#define LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888 +#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_WITH_OPA +#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_WITH_MASK +#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA +#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888 +#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_WITH_OPA +#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_WITH_MASK +#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA +#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888 +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_WITH_OPA +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_WITH_MASK +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888 +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_WITH_OPA +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_WITH_MASK +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888 +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_WITH_OPA +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_WITH_MASK +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void LV_ATTRIBUTE_FAST_MEM lv_draw_sw_blend_color_to_argb8888(_lv_draw_sw_blend_fill_dsc_t *dsc) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + const lv_opa_t *mask = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + int32_t dest_stride = dsc->dest_stride; + + lv_color_mix_alpha_cache_t cache; + lv_color_mix_with_alpha_cache_init(&cache); + + int32_t x; + int32_t y; + + LV_UNUSED(w); + LV_UNUSED(h); + LV_UNUSED(x); + LV_UNUSED(y); + LV_UNUSED(opa); + LV_UNUSED(mask); + LV_UNUSED(mask_stride); + LV_UNUSED(dest_stride); + + /*Simple fill*/ + if (mask == NULL && opa >= LV_OPA_MAX) { + if (dsc->use_asm) { + LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888(dsc); + } else { + uint32_t color32 = lv_color_to_u32(dsc->color); + uint32_t *dest_buf = dsc->dest_buf; + for (y = 0; y < h; y++) { + for (x = 0; x < w - 16; x += 16) { + dest_buf[x + 0] = color32; + dest_buf[x + 1] = color32; + dest_buf[x + 2] = color32; + dest_buf[x + 3] = color32; + + dest_buf[x + 4] = color32; + dest_buf[x + 5] = color32; + dest_buf[x + 6] = color32; + dest_buf[x + 7] = color32; + + dest_buf[x + 8] = color32; + dest_buf[x + 9] = color32; + dest_buf[x + 10] = color32; + dest_buf[x + 11] = color32; + + dest_buf[x + 12] = color32; + dest_buf[x + 13] = color32; + dest_buf[x + 14] = color32; + dest_buf[x + 15] = color32; + } + for (; x < w; x ++) { + dest_buf[x] = color32; + } + + dest_buf = drawbuf_next_row(dest_buf, dest_stride); + } + } + + } + /*Opacity only*/ + else if (mask == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_WITH_OPA(dsc)) { + lv_color32_t color_argb = lv_color_to_32(dsc->color, opa); + lv_color32_t *dest_buf = dsc->dest_buf; + + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + dest_buf[x] = lv_color_32_32_mix(color_argb, dest_buf[x], &cache); + } + dest_buf = drawbuf_next_row(dest_buf, dest_stride); + } + } + + } + /*Masked with full opacity*/ + else if (mask && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_WITH_MASK(dsc)) { + lv_color32_t color_argb = lv_color_to_32(dsc->color, 0xff); + lv_color32_t *dest_buf = dsc->dest_buf; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + color_argb.alpha = mask[x]; + dest_buf[x] = lv_color_32_32_mix(color_argb, dest_buf[x], &cache); + } + + dest_buf = drawbuf_next_row(dest_buf, dest_stride); + mask += mask_stride; + } + } + + } + /*Masked with opacity*/ + else { + if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888_MIX_MASK_OPA(dsc)) { + lv_color32_t color_argb = lv_color_to_32(dsc->color, opa); + lv_color32_t *dest_buf = dsc->dest_buf; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + color_argb.alpha = LV_OPA_MIX2(mask[x], opa); + dest_buf[x] = lv_color_32_32_mix(color_argb, dest_buf[x], &cache); + } + dest_buf = drawbuf_next_row(dest_buf, dest_stride); + mask += mask_stride; + } + } + } +} + +void LV_ATTRIBUTE_FAST_MEM lv_draw_sw_blend_image_to_argb8888(_lv_draw_sw_blend_image_dsc_t *dsc) +{ + switch (dsc->src_color_format) { + case LV_COLOR_FORMAT_RGB565: + rgb565_image_blend(dsc); + break; + case LV_COLOR_FORMAT_RGB888: + rgb888_image_blend(dsc, 3); + break; + case LV_COLOR_FORMAT_XRGB8888: + rgb888_image_blend(dsc, 4); + break; + case LV_COLOR_FORMAT_ARGB8888: + argb8888_image_blend(dsc); + break; + case LV_COLOR_FORMAT_L8: + l8_image_blend(dsc); + break; + case LV_COLOR_FORMAT_AL88: + al88_image_blend(dsc); + break; + default: + LV_LOG_WARN("Not supported source color format"); + break; + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void LV_ATTRIBUTE_FAST_MEM al88_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + lv_color32_t *dest_buf_c32 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const lv_color16a_t *src_buf_al88 = dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + int32_t dest_x; + int32_t src_x; + int32_t y; + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) { + /* + dest_buf_c32[dest_x].alpha = src_buf_al88[src_x].alpha; + dest_buf_c32[dest_x].red = src_buf_al88[src_x].lumi; + dest_buf_c32[dest_x].green = src_buf_al88[src_x].lumi; + dest_buf_c32[dest_x].blue = src_buf_al88[src_x].lumi; + */ + lv_color_8_32_mix(src_buf_al88[src_x].lumi, &dest_buf_c32[dest_x], src_buf_al88[src_x].alpha); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + } + } + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) { + lv_color_8_32_mix(src_buf_al88[src_x].lumi, &dest_buf_c32[dest_x], LV_OPA_MIX2(src_buf_al88[src_x].alpha, opa)); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + } + } + } else if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) { + lv_color_8_32_mix(src_buf_al88[src_x].lumi, &dest_buf_c32[dest_x], LV_OPA_MIX2(src_buf_al88[src_x].alpha, + mask_buf[src_x])); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + mask_buf += mask_stride; + } + } + } else if (mask_buf && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) { + lv_color_8_32_mix(src_buf_al88[src_x].lumi, &dest_buf_c32[dest_x], LV_OPA_MIX3(src_buf_al88[src_x].alpha, + mask_buf[src_x], opa)); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + mask_buf += mask_stride; + } + } + } + } else { + lv_color32_t src_argb; + lv_color_mix_alpha_cache_t cache; + lv_color_mix_with_alpha_cache_init(&cache); + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) { + src_argb.red = src_buf_al88[src_x].lumi; + src_argb.green = src_buf_al88[src_x].lumi; + src_argb.blue = src_buf_al88[src_x].lumi; + if (mask_buf == NULL) { + src_argb.alpha = LV_OPA_MIX2(src_buf_al88[src_x].alpha, opa); + } else { + src_argb.alpha = LV_OPA_MIX3(src_buf_al88[src_x].alpha, mask_buf[dest_x], opa); + } + blend_non_normal_pixel(&dest_buf_c32[dest_x], src_argb, dsc->blend_mode, &cache); + } + if (mask_buf) { + mask_buf += mask_stride; + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + } + } +} + +static void LV_ATTRIBUTE_FAST_MEM l8_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + lv_color32_t *dest_buf_c32 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const uint8_t *src_buf_l8 = dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + int32_t dest_x; + int32_t src_x; + int32_t y; + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) { + dest_buf_c32[dest_x].alpha = src_buf_l8[src_x]; + dest_buf_c32[dest_x].red = src_buf_l8[src_x]; + dest_buf_c32[dest_x].green = src_buf_l8[src_x]; + dest_buf_c32[dest_x].blue = src_buf_l8[src_x]; + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride); + } + } + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) { + lv_color_8_32_mix(src_buf_l8[src_x], &dest_buf_c32[dest_x], opa); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride); + } + } + } else if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) { + lv_color_8_32_mix(src_buf_l8[src_x], &dest_buf_c32[dest_x], mask_buf[src_x]); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride); + mask_buf += mask_stride; + } + } + } else if (mask_buf && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) { + lv_color_8_32_mix(src_buf_l8[src_x], &dest_buf_c32[dest_x], LV_OPA_MIX2(mask_buf[src_x], opa)); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride); + mask_buf += mask_stride; + } + } + } + } else { + lv_color32_t src_argb; + lv_color_mix_alpha_cache_t cache; + lv_color_mix_with_alpha_cache_init(&cache); + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x++, src_x++) { + src_argb.red = src_buf_l8[src_x]; + src_argb.green = src_buf_l8[src_x]; + src_argb.blue = src_buf_l8[src_x]; + if (mask_buf == NULL) { + src_argb.alpha = opa; + } else { + src_argb.alpha = LV_OPA_MIX2(mask_buf[dest_x], opa); + } + blend_non_normal_pixel(&dest_buf_c32[dest_x], src_argb, dsc->blend_mode, &cache); + } + if (mask_buf) { + mask_buf += mask_stride; + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride); + } + } +} + +static void LV_ATTRIBUTE_FAST_MEM rgb565_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + lv_color32_t *dest_buf_c32 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const lv_color16_t *src_buf_c16 = (const lv_color16_t *) dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + lv_color32_t color_argb; + lv_color_mix_alpha_cache_t cache; + lv_color_mix_with_alpha_cache_init(&cache); + + int32_t x; + int32_t y; + + LV_UNUSED(color_argb); + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + if (mask_buf == NULL) { + lv_result_t accelerated; + if (opa >= LV_OPA_MAX) { + accelerated = LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888(dsc); + } else { + accelerated = LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(dsc); + } + if (LV_RESULT_INVALID == accelerated) { + color_argb.alpha = opa; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + color_argb.red = (src_buf_c16[x].red * 2106) >> 8; /*To make it rounded*/ + color_argb.green = (src_buf_c16[x].green * 1037) >> 8; + color_argb.blue = (src_buf_c16[x].blue * 2106) >> 8; + dest_buf_c32[x] = lv_color_32_32_mix(color_argb, dest_buf_c32[x], &cache); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride); + } + } + } else if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(dsc)) { + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + color_argb.alpha = mask_buf[x]; + color_argb.red = (src_buf_c16[x].red * 2106) >> 8; /*To make it rounded*/ + color_argb.green = (src_buf_c16[x].green * 1037) >> 8; + color_argb.blue = (src_buf_c16[x].blue * 2106) >> 8; + dest_buf_c32[x] = lv_color_32_32_mix(color_argb, dest_buf_c32[x], &cache); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride); + mask_buf += mask_stride; + } + } + } else { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + color_argb.alpha = LV_OPA_MIX2(mask_buf[x], opa); + color_argb.red = (src_buf_c16[x].red * 2106) >> 8; /*To make it rounded*/ + color_argb.green = (src_buf_c16[x].green * 1037) >> 8; + color_argb.blue = (src_buf_c16[x].blue * 2106) >> 8; + dest_buf_c32[x] = lv_color_32_32_mix(color_argb, dest_buf_c32[x], &cache); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride); + mask_buf += mask_stride; + } + } + } + } else { + lv_color32_t src_argb; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + src_argb.red = (src_buf_c16[x].red * 2106) >> 8; + src_argb.green = (src_buf_c16[x].green * 1037) >> 8; + src_argb.blue = (src_buf_c16[x].blue * 2106) >> 8; + if (mask_buf == NULL) { + src_argb.alpha = opa; + } else { + src_argb.alpha = LV_OPA_MIX2(mask_buf[x], opa); + } + blend_non_normal_pixel(&dest_buf_c32[x], src_argb, dsc->blend_mode, &cache); + } + if (mask_buf) { + mask_buf += mask_stride; + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride); + } + } +} + +static void LV_ATTRIBUTE_FAST_MEM rgb888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, const uint8_t src_px_size) +{ + + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + lv_color32_t *dest_buf_c32 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const uint8_t *src_buf = dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + lv_color32_t color_argb; + lv_color_mix_alpha_cache_t cache; + lv_color_mix_with_alpha_cache_init(&cache); + + int32_t dest_x; + int32_t src_x; + int32_t y; + + LV_UNUSED(color_argb); + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + /*Special case*/ + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888(dsc, src_px_size)) { + if (src_px_size == 4) { + uint32_t line_in_bytes = w * 4; + for (y = 0; y < h; y++) { + lv_memcpy(dest_buf_c32, src_buf, line_in_bytes); + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf = drawbuf_next_row(src_buf, src_stride); + } + } else if (src_px_size == 3) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 3) { + dest_buf_c32[dest_x].red = src_buf[src_x + 2]; + dest_buf_c32[dest_x].green = src_buf[src_x + 1]; + dest_buf_c32[dest_x].blue = src_buf[src_x + 0]; + dest_buf_c32[dest_x].alpha = 0xff; + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf = drawbuf_next_row(src_buf, src_stride); + } + } + } + + } + if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(dsc, src_px_size)) { + color_argb.alpha = opa; + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) { + color_argb.red = src_buf[src_x + 2]; + color_argb.green = src_buf[src_x + 1]; + color_argb.blue = src_buf[src_x + 0]; + dest_buf_c32[dest_x] = lv_color_32_32_mix(color_argb, dest_buf_c32[dest_x], &cache); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf = drawbuf_next_row(src_buf, src_stride); + } + } + + } + if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(dsc, src_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) { + color_argb.alpha = mask_buf[dest_x]; + color_argb.red = src_buf[src_x + 2]; + color_argb.green = src_buf[src_x + 1]; + color_argb.blue = src_buf[src_x + 0]; + dest_buf_c32[dest_x] = lv_color_32_32_mix(color_argb, dest_buf_c32[dest_x], &cache); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf = drawbuf_next_row(src_buf, src_stride); + mask_buf += mask_stride; + } + } + } + if (mask_buf && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(dsc, src_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) { + color_argb.alpha = (opa * mask_buf[dest_x]) >> 8; + color_argb.red = src_buf[src_x + 2]; + color_argb.green = src_buf[src_x + 1]; + color_argb.blue = src_buf[src_x + 0]; + dest_buf_c32[dest_x] = lv_color_32_32_mix(color_argb, dest_buf_c32[dest_x], &cache); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf = drawbuf_next_row(src_buf, src_stride); + mask_buf += mask_stride; + } + } + } + } else { + lv_color32_t src_argb; + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) { + src_argb.red = src_buf[src_x + 2]; + src_argb.green = src_buf[src_x + 1]; + src_argb.blue = src_buf[src_x + 0]; + if (mask_buf == NULL) { + src_argb.alpha = opa; + } else { + src_argb.alpha = LV_OPA_MIX2(mask_buf[dest_x], opa); + } + + blend_non_normal_pixel(&dest_buf_c32[dest_x], src_argb, dsc->blend_mode, &cache); + } + if (mask_buf) { + mask_buf += mask_stride; + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf = drawbuf_next_row(src_buf, src_stride); + } + } +} + +static void LV_ATTRIBUTE_FAST_MEM argb8888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + lv_color32_t *dest_buf_c32 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const lv_color32_t *src_buf_c32 = dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + lv_color32_t color_argb; + lv_color_mix_alpha_cache_t cache; + lv_color_mix_with_alpha_cache_init(&cache); + + int32_t x; + int32_t y; + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888(dsc)) { + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + dest_buf_c32[x] = lv_color_32_32_mix(src_buf_c32[x], dest_buf_c32[x], &cache); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride); + } + } + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_WITH_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + color_argb = src_buf_c32[x]; + color_argb.alpha = LV_OPA_MIX2(color_argb.alpha, opa); + dest_buf_c32[x] = lv_color_32_32_mix(color_argb, dest_buf_c32[x], &cache); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride); + } + } + } else if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_WITH_MASK(dsc)) { + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + color_argb = src_buf_c32[x]; + color_argb.alpha = LV_OPA_MIX2(color_argb.alpha, mask_buf[x]); + dest_buf_c32[x] = lv_color_32_32_mix(color_argb, dest_buf_c32[x], &cache); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride); + mask_buf += mask_stride; + } + } + } else if (mask_buf && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_ARGB8888_MIX_MASK_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + color_argb = src_buf_c32[x]; + color_argb.alpha = LV_OPA_MIX3(color_argb.alpha, opa, mask_buf[x]); + dest_buf_c32[x] = lv_color_32_32_mix(color_argb, dest_buf_c32[x], &cache); + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride); + mask_buf += mask_stride; + } + } + } + } else { + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + color_argb = src_buf_c32[x]; + if (mask_buf == NULL) { + color_argb.alpha = LV_OPA_MIX2(color_argb.alpha, opa); + } else { + color_argb.alpha = LV_OPA_MIX3(color_argb.alpha, mask_buf[x], opa); + } + blend_non_normal_pixel(&dest_buf_c32[x], color_argb, dsc->blend_mode, &cache); + } + if (mask_buf) { + mask_buf += mask_stride; + } + dest_buf_c32 = drawbuf_next_row(dest_buf_c32, dest_stride); + src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride); + } + } +} + +static inline void LV_ATTRIBUTE_FAST_MEM lv_color_8_32_mix(const uint8_t src, lv_color32_t *dest, uint8_t mix) +{ + + if (mix == 0) { + return; + } + + dest->alpha = 255; + if (mix >= LV_OPA_MAX) { + dest->red = src; + dest->green = src; + dest->blue = src; + } else { + lv_opa_t mix_inv = 255 - mix; + dest->red = (uint32_t)((uint32_t)src * mix + dest->red * mix_inv) >> 8; + dest->green = (uint32_t)((uint32_t)src * mix + dest->green * mix_inv) >> 8; + dest->blue = (uint32_t)((uint32_t)src * mix + dest->blue * mix_inv) >> 8; + } +} + +static inline lv_color32_t LV_ATTRIBUTE_FAST_MEM lv_color_32_32_mix(lv_color32_t fg, lv_color32_t bg, + lv_color_mix_alpha_cache_t *cache) +{ + /*Pick the foreground if it's fully opaque or the Background is fully transparent*/ + if (fg.alpha >= LV_OPA_MAX || bg.alpha <= LV_OPA_MIN) { + return fg; + } + /*Transparent foreground: use the Background*/ + else if (fg.alpha <= LV_OPA_MIN) { + return bg; + } + /*Opaque background: use simple mix*/ + else if (bg.alpha == 255) { + return lv_color_mix32(fg, bg); + } + /*Both colors have alpha. Expensive calculation need to be applied*/ + else { + /*Save the parameters and the result. If they will be asked again don't compute again*/ + + /*Update the ratio and the result alpha value if the input alpha values change*/ + if (bg.alpha != cache->bg_saved.alpha || fg.alpha != cache->fg_saved.alpha) { + /*Info: + * https://en.wikipedia.org/wiki/Alpha_compositing#Analytical_derivation_of_the_over_operator*/ + cache->res_alpha_saved = 255 - LV_OPA_MIX2(255 - fg.alpha, 255 - bg.alpha); + LV_ASSERT(cache->res_alpha_saved != 0); + cache->ratio_saved = (uint32_t)((uint32_t)fg.alpha * 255) / cache->res_alpha_saved; + } + + if (!lv_color32_eq(bg, cache->bg_saved) || !lv_color32_eq(fg, cache->fg_saved)) { + cache->fg_saved = fg; + cache->bg_saved = bg; + fg.alpha = cache->ratio_saved; + cache->res_saved = lv_color_mix32(fg, bg); + cache->res_saved.alpha = cache->res_alpha_saved; + } + + return cache->res_saved; + } +} + +void lv_color_mix_with_alpha_cache_init(lv_color_mix_alpha_cache_t *cache) +{ + lv_memset(&cache->fg_saved, 0x00, sizeof(lv_color32_t)); //lv_memzero + lv_memset(&cache->bg_saved, 0x00, sizeof(lv_color32_t)); //lv_memzero + lv_memset(&cache->res_saved, 0x00, sizeof(lv_color32_t)); //lv_memzero + cache->res_alpha_saved = 255; + cache->ratio_saved = 255; +} + +static inline void LV_ATTRIBUTE_FAST_MEM blend_non_normal_pixel(lv_color32_t *dest, lv_color32_t src, + lv_blend_mode_t mode, lv_color_mix_alpha_cache_t *cache) +{ + lv_color32_t res; + switch (mode) { + case LV_BLEND_MODE_ADDITIVE: + res.red = LV_MIN(dest->red + src.red, 255); + res.green = LV_MIN(dest->green + src.green, 255); + res.blue = LV_MIN(dest->blue + src.blue, 255); + break; + case LV_BLEND_MODE_SUBTRACTIVE: + res.red = LV_MAX(dest->red - src.red, 0); + res.green = LV_MAX(dest->green - src.green, 0); + res.blue = LV_MAX(dest->blue - src.blue, 0); + break; + case LV_BLEND_MODE_MULTIPLY: + res.red = (dest->red * src.red) >> 8; + res.green = (dest->green * src.green) >> 8; + res.blue = (dest->blue * src.blue) >> 8; + break; + default: + LV_LOG_WARN("Not supported blend mode: %d", mode); + return; + } + res.alpha = src.alpha; + *dest = lv_color_32_32_mix(res, *dest, cache); +} + +static inline void *LV_ATTRIBUTE_FAST_MEM drawbuf_next_row(const void *buf, uint32_t stride) +{ + return (void *)((uint8_t *)buf + stride); +} diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_rgb565.c b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_rgb565.c new file mode 100644 index 00000000..dd6e5392 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_rgb565.c @@ -0,0 +1,962 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_draw_sw_blend_to_rgb565.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_draw_sw_blend_to_rgb565.h" + +#include "lv_assert.h" +#include "lv_types.h" +#include "lv_log.h" +#include "lv_draw_sw_blend.h" +#include "lv_math.h" +#include "lv_color.h" +#include "lv_string.h" + +#include "esp_lvgl_port_lv_blend.h" + + +/********************* + * DEFINES + *********************/ + +#define LV_ATTRIBUTE_FAST_MEM + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void /* LV_ATTRIBUTE_FAST_MEM */ al88_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc); + +static void /* LV_ATTRIBUTE_FAST_MEM */ l8_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc); + +static void /* LV_ATTRIBUTE_FAST_MEM */ rgb565_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc); + +static void /* LV_ATTRIBUTE_FAST_MEM */ rgb888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, + const uint8_t src_px_size); + +static void /* LV_ATTRIBUTE_FAST_MEM */ argb8888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc); + +static inline uint16_t /* LV_ATTRIBUTE_FAST_MEM */ l8_to_rgb565(const uint8_t c1); + +static inline uint16_t /* LV_ATTRIBUTE_FAST_MEM */ lv_color_8_16_mix(const uint8_t c1, uint16_t c2, uint8_t mix); + +static inline uint16_t /* LV_ATTRIBUTE_FAST_MEM */ lv_color_24_16_mix(const uint8_t *c1, uint16_t c2, uint8_t mix); + +static inline void * /* LV_ATTRIBUTE_FAST_MEM */ drawbuf_next_row(const void *buf, uint32_t stride); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB565 +#define LV_DRAW_SW_COLOR_BLEND_TO_RGB565(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB565_WITH_OPA +#define LV_DRAW_SW_COLOR_BLEND_TO_RGB565_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB565_WITH_MASK +#define LV_DRAW_SW_COLOR_BLEND_TO_RGB565_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB565_MIX_MASK_OPA +#define LV_DRAW_SW_COLOR_BLEND_TO_RGB565_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565 +#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_WITH_OPA +#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_WITH_MASK +#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA +#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565 +#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_WITH_OPA +#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_WITH_MASK +#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA +#define LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565 +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_WITH_OPA +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_WITH_MASK +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565 +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_WITH_OPA +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_WITH_MASK +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565 +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_WITH_OPA +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_WITH_MASK +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Fill an area with a color. + * Supports normal fill, fill with opacity, fill with mask, and fill with mask and opacity. + * dest_buf and color have native color depth. (RGB565, RGB888, XRGB8888) + * The background (dest_buf) cannot have alpha channel + * @param dest_buf + * @param dest_area + * @param dest_stride + * @param color + * @param opa + * @param mask + * @param mask_stride + */ +void LV_ATTRIBUTE_FAST_MEM lv_draw_sw_blend_color_to_rgb565(_lv_draw_sw_blend_fill_dsc_t *dsc) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + uint16_t color16 = lv_color_to_u16(dsc->color); + lv_opa_t opa = dsc->opa; + const lv_opa_t *mask = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + uint16_t *dest_buf_u16 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + + int32_t x; + int32_t y; + + LV_UNUSED(w); + LV_UNUSED(h); + LV_UNUSED(x); + LV_UNUSED(y); + LV_UNUSED(opa); + LV_UNUSED(mask); + LV_UNUSED(color16); + LV_UNUSED(mask_stride); + LV_UNUSED(dest_stride); + LV_UNUSED(dest_buf_u16); + + /*Simple fill*/ + if (mask == NULL && opa >= LV_OPA_MAX) { + if (dsc->use_asm) { + LV_DRAW_SW_COLOR_BLEND_TO_RGB565(dsc); + } else { + for (y = 0; y < h; y++) { + uint16_t *dest_end_final = dest_buf_u16 + w; + uint32_t *dest_end_mid = (uint32_t *)((uint16_t *) dest_buf_u16 + ((w - 1) & ~(0xF))); + if ((lv_uintptr_t)&dest_buf_u16[0] & 0x3) { + dest_buf_u16[0] = color16; + dest_buf_u16++; + } + + uint32_t c32 = (uint32_t)color16 + ((uint32_t)color16 << 16); + uint32_t *dest32 = (uint32_t *)dest_buf_u16; + while (dest32 < dest_end_mid) { + dest32[0] = c32; + dest32[1] = c32; + dest32[2] = c32; + dest32[3] = c32; + dest32[4] = c32; + dest32[5] = c32; + dest32[6] = c32; + dest32[7] = c32; + dest32 += 8; + } + + dest_buf_u16 = (uint16_t *)dest32; + + while (dest_buf_u16 < dest_end_final) { + *dest_buf_u16 = color16; + dest_buf_u16++; + } + + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + dest_buf_u16 -= w; + } + } + + } + /*Opacity only*/ + else if (mask == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_RGB565_WITH_OPA(dsc)) { + uint32_t last_dest32_color = dest_buf_u16[0] + 1; /*Set to value which is not equal to the first pixel*/ + uint32_t last_res32_color = 0; + + for (y = 0; y < h; y++) { + x = 0; + if ((lv_uintptr_t)&dest_buf_u16[0] & 0x3) { + dest_buf_u16[0] = lv_color_16_16_mix(color16, dest_buf_u16[0], opa); + x = 1; + } + + for (; x < w - 2; x += 2) { + if (dest_buf_u16[x] != dest_buf_u16[x + 1]) { + dest_buf_u16[x + 0] = lv_color_16_16_mix(color16, dest_buf_u16[x + 0], opa); + dest_buf_u16[x + 1] = lv_color_16_16_mix(color16, dest_buf_u16[x + 1], opa); + } else { + volatile uint32_t *dest32 = (uint32_t *)&dest_buf_u16[x]; + if (last_dest32_color == *dest32) { + *dest32 = last_res32_color; + } else { + last_dest32_color = *dest32; + + dest_buf_u16[x] = lv_color_16_16_mix(color16, dest_buf_u16[x + 0], opa); + dest_buf_u16[x + 1] = dest_buf_u16[x]; + + last_res32_color = *dest32; + } + } + } + + for (; x < w ; x++) { + dest_buf_u16[x] = lv_color_16_16_mix(color16, dest_buf_u16[x], opa); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + } + } + + } + + /*Masked with full opacity*/ + else if (mask && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_RGB565_WITH_MASK(dsc)) { + for (y = 0; y < h; y++) { + x = 0; + if ((lv_uintptr_t)(mask) & 0x1) { + dest_buf_u16[x] = lv_color_16_16_mix(color16, dest_buf_u16[x], mask[x]); + x++; + } + + for (; x <= w - 2; x += 2) { + uint16_t mask16 = *((uint16_t *)&mask[x]); + if (mask16 == 0xFFFF) { + dest_buf_u16[x + 0] = color16; + dest_buf_u16[x + 1] = color16; + } else if (mask16 != 0) { + dest_buf_u16[x + 0] = lv_color_16_16_mix(color16, dest_buf_u16[x + 0], mask[x + 0]); + dest_buf_u16[x + 1] = lv_color_16_16_mix(color16, dest_buf_u16[x + 1], mask[x + 1]); + } + } + + for (; x < w ; x++) { + dest_buf_u16[x] = lv_color_16_16_mix(color16, dest_buf_u16[x], mask[x]); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + mask += mask_stride; + } + } + + } + /*Masked with opacity*/ + else if (mask && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_RGB565_MIX_MASK_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + dest_buf_u16[x] = lv_color_16_16_mix(color16, dest_buf_u16[x], LV_OPA_MIX2(mask[x], opa)); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + mask += mask_stride; + } + } + } +} + +void LV_ATTRIBUTE_FAST_MEM lv_draw_sw_blend_image_to_rgb565(_lv_draw_sw_blend_image_dsc_t *dsc) +{ + switch (dsc->src_color_format) { + case LV_COLOR_FORMAT_RGB565: + rgb565_image_blend(dsc); + break; + case LV_COLOR_FORMAT_RGB888: + rgb888_image_blend(dsc, 3); + break; + case LV_COLOR_FORMAT_XRGB8888: + rgb888_image_blend(dsc, 4); + break; + case LV_COLOR_FORMAT_ARGB8888: + argb8888_image_blend(dsc); + break; + case LV_COLOR_FORMAT_L8: + l8_image_blend(dsc); + break; + case LV_COLOR_FORMAT_AL88: + al88_image_blend(dsc); + break; + default: + LV_LOG_WARN("Not supported source color format"); + break; + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void LV_ATTRIBUTE_FAST_MEM al88_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + uint16_t *dest_buf_u16 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const lv_color16a_t *src_buf_al88 = dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + int32_t dest_x; + int32_t src_x; + int32_t y; + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) { + dest_buf_u16[dest_x] = lv_color_8_16_mix(src_buf_al88[src_x].lumi, dest_buf_u16[dest_x], src_buf_al88[src_x].alpha); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + } + } + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_WITH_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) { + dest_buf_u16[dest_x] = lv_color_8_16_mix(src_buf_al88[src_x].lumi, dest_buf_u16[dest_x], + LV_OPA_MIX2(src_buf_al88[src_x].alpha, opa)); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + } + } + } else if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_WITH_MASK(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) { + dest_buf_u16[dest_x] = lv_color_8_16_mix(src_buf_al88[src_x].lumi, dest_buf_u16[dest_x], + LV_OPA_MIX2(src_buf_al88[src_x].alpha, mask_buf[dest_x])); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + mask_buf += mask_stride; + } + } + } else if (mask_buf && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_AL88_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) { + dest_buf_u16[dest_x] = lv_color_8_16_mix(src_buf_al88[src_x].lumi, dest_buf_u16[dest_x], + LV_OPA_MIX3(src_buf_al88[src_x].alpha, mask_buf[dest_x], opa)); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + mask_buf += mask_stride; + } + } + } + } else { + uint16_t res = 0; + for (y = 0; y < h; y++) { + lv_color16_t *dest_buf_c16 = (lv_color16_t *)dest_buf_u16; + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 4) { + uint8_t rb = src_buf_al88[src_x].lumi >> 3; + uint8_t g = src_buf_al88[src_x].lumi >> 2; + switch (dsc->blend_mode) { + case LV_BLEND_MODE_ADDITIVE: + res = (LV_MIN(dest_buf_c16[dest_x].red + rb, 31)) << 11; + res += (LV_MIN(dest_buf_c16[dest_x].green + g, 63)) << 5; + res += LV_MIN(dest_buf_c16[dest_x].blue + rb, 31); + break; + case LV_BLEND_MODE_SUBTRACTIVE: + res = (LV_MAX(dest_buf_c16[dest_x].red - rb, 0)) << 11; + res += (LV_MAX(dest_buf_c16[dest_x].green - g, 0)) << 5; + res += LV_MAX(dest_buf_c16[dest_x].blue - rb, 0); + break; + case LV_BLEND_MODE_MULTIPLY: + res = ((dest_buf_c16[dest_x].red * rb) >> 5) << 11; + res += ((dest_buf_c16[dest_x].green * g) >> 6) << 5; + res += (dest_buf_c16[dest_x].blue * rb) >> 5; + break; + default: + LV_LOG_WARN("Not supported blend mode: %d", dsc->blend_mode); + return; + } + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], src_buf_al88[src_x].alpha); + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], LV_OPA_MIX2(opa, src_buf_al88[src_x].alpha)); + } else { + if (opa >= LV_OPA_MAX) { + dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], mask_buf[dest_x]); + } else dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], LV_OPA_MIX3(mask_buf[dest_x], opa, + src_buf_al88[src_x].alpha)); + } + } + + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + if (mask_buf) { + mask_buf += mask_stride; + } + } + } +} + +static void LV_ATTRIBUTE_FAST_MEM l8_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + uint16_t *dest_buf_u16 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const uint8_t *src_buf_l8 = dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + int32_t dest_x; + int32_t src_x; + int32_t y; + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) { + dest_buf_u16[dest_x] = l8_to_rgb565(src_buf_l8[src_x]); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_l8 += src_stride; + } + } + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_WITH_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) { + dest_buf_u16[dest_x] = lv_color_8_16_mix(src_buf_l8[src_x], dest_buf_u16[dest_x], opa); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_l8 += src_stride; + } + } + } else if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_WITH_MASK(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) { + dest_buf_u16[dest_x] = lv_color_8_16_mix(src_buf_l8[src_x], dest_buf_u16[dest_x], mask_buf[dest_x]); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_l8 += src_stride; + mask_buf += mask_stride; + } + } + } else if (mask_buf && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x++) { + dest_buf_u16[dest_x] = lv_color_8_16_mix(src_buf_l8[src_x], dest_buf_u16[dest_x], LV_OPA_MIX2(mask_buf[dest_x], opa)); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_l8 += src_stride; + mask_buf += mask_stride; + } + } + } + } else { + uint16_t res = 0; + for (y = 0; y < h; y++) { + lv_color16_t *dest_buf_c16 = (lv_color16_t *)dest_buf_u16; + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 4) { + uint8_t rb = src_buf_l8[src_x] >> 3; + uint8_t g = src_buf_l8[src_x] >> 2; + switch (dsc->blend_mode) { + case LV_BLEND_MODE_ADDITIVE: + res = (LV_MIN(dest_buf_c16[dest_x].red + rb, 31)) << 11; + res += (LV_MIN(dest_buf_c16[dest_x].green + g, 63)) << 5; + res += LV_MIN(dest_buf_c16[dest_x].blue + rb, 31); + break; + case LV_BLEND_MODE_SUBTRACTIVE: + res = (LV_MAX(dest_buf_c16[dest_x].red - rb, 0)) << 11; + res += (LV_MAX(dest_buf_c16[dest_x].green - g, 0)) << 5; + res += LV_MAX(dest_buf_c16[dest_x].blue - rb, 0); + break; + case LV_BLEND_MODE_MULTIPLY: + res = ((dest_buf_c16[dest_x].red * rb) >> 5) << 11; + res += ((dest_buf_c16[dest_x].green * g) >> 6) << 5; + res += (dest_buf_c16[dest_x].blue * rb) >> 5; + break; + default: + LV_LOG_WARN("Not supported blend mode: %d", dsc->blend_mode); + return; + } + + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + dest_buf_u16[dest_x] = res; + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], opa); + } else { + if (opa >= LV_OPA_MAX) { + dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], mask_buf[dest_x]); + } else { + dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], LV_OPA_MIX2(mask_buf[dest_x], opa)); + } + } + } + + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_l8 += src_stride; + if (mask_buf) { + mask_buf += mask_stride; + } + } + } +} + +static void LV_ATTRIBUTE_FAST_MEM rgb565_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + uint16_t *dest_buf_u16 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const uint16_t *src_buf_u16 = dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + int32_t x; + int32_t y; + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (dsc->use_asm) { + LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565(dsc); + } else { + uint32_t line_in_bytes = w * 2; + for (y = 0; y < h; y++) { + lv_memcpy(dest_buf_u16, src_buf_u16, line_in_bytes); + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u16 = drawbuf_next_row(src_buf_u16, src_stride); + } + } + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_WITH_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + dest_buf_u16[x] = lv_color_16_16_mix(src_buf_u16[x], dest_buf_u16[x], opa); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u16 = drawbuf_next_row(src_buf_u16, src_stride); + } + } + } else if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_WITH_MASK(dsc)) { + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + dest_buf_u16[x] = lv_color_16_16_mix(src_buf_u16[x], dest_buf_u16[x], mask_buf[x]); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u16 = drawbuf_next_row(src_buf_u16, src_stride); + mask_buf += mask_stride; + } + } + } else { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + dest_buf_u16[x] = lv_color_16_16_mix(src_buf_u16[x], dest_buf_u16[x], LV_OPA_MIX2(mask_buf[x], opa)); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u16 = drawbuf_next_row(src_buf_u16, src_stride); + mask_buf += mask_stride; + } + } + } + } else { + uint16_t res = 0; + for (y = 0; y < h; y++) { + lv_color16_t *dest_buf_c16 = (lv_color16_t *) dest_buf_u16; + lv_color16_t *src_buf_c16 = (lv_color16_t *) src_buf_u16; + for (x = 0; x < w; x++) { + switch (dsc->blend_mode) { + case LV_BLEND_MODE_ADDITIVE: + if (src_buf_u16[x] == 0x0000) { + continue; /*Do not add pure black*/ + } + res = (LV_MIN(dest_buf_c16[x].red + src_buf_c16[x].red, 31)) << 11; + res += (LV_MIN(dest_buf_c16[x].green + src_buf_c16[x].green, 63)) << 5; + res += LV_MIN(dest_buf_c16[x].blue + src_buf_c16[x].blue, 31); + break; + case LV_BLEND_MODE_SUBTRACTIVE: + if (src_buf_u16[x] == 0x0000) { + continue; /*Do not subtract pure black*/ + } + res = (LV_MAX(dest_buf_c16[x].red - src_buf_c16[x].red, 0)) << 11; + res += (LV_MAX(dest_buf_c16[x].green - src_buf_c16[x].green, 0)) << 5; + res += LV_MAX(dest_buf_c16[x].blue - src_buf_c16[x].blue, 0); + break; + case LV_BLEND_MODE_MULTIPLY: + if (src_buf_u16[x] == 0xffff) { + continue; /*Do not multiply with pure white (considered as 1)*/ + } + res = ((dest_buf_c16[x].red * src_buf_c16[x].red) >> 5) << 11; + res += ((dest_buf_c16[x].green * src_buf_c16[x].green) >> 6) << 5; + res += (dest_buf_c16[x].blue * src_buf_c16[x].blue) >> 5; + break; + default: + LV_LOG_WARN("Not supported blend mode: %d", dsc->blend_mode); + return; + } + + if (mask_buf == NULL) { + dest_buf_u16[x] = lv_color_16_16_mix(res, dest_buf_u16[x], opa); + } else { + if (opa >= LV_OPA_MAX) { + dest_buf_u16[x] = lv_color_16_16_mix(res, dest_buf_u16[x], mask_buf[x]); + } else { + dest_buf_u16[x] = lv_color_16_16_mix(res, dest_buf_u16[x], LV_OPA_MIX2(mask_buf[x], opa)); + } + } + } + + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u16 = drawbuf_next_row(src_buf_u16, src_stride); + if (mask_buf) { + mask_buf += mask_stride; + } + } + } +} + +static void LV_ATTRIBUTE_FAST_MEM rgb888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, const uint8_t src_px_size) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + uint16_t *dest_buf_u16 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const uint8_t *src_buf_u8 = dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + int32_t dest_x; + int32_t src_x; + int32_t y; + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565(dsc, src_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) { + dest_buf_u16[dest_x] = ((src_buf_u8[src_x + 2] & 0xF8) << 8) + + ((src_buf_u8[src_x + 1] & 0xFC) << 3) + + ((src_buf_u8[src_x + 0] & 0xF8) >> 3); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u8 += src_stride; + } + } + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_WITH_OPA(dsc, src_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) { + dest_buf_u16[dest_x] = lv_color_24_16_mix(&src_buf_u8[src_x], dest_buf_u16[dest_x], opa); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u8 += src_stride; + } + } + } + if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_WITH_MASK(dsc, src_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) { + dest_buf_u16[dest_x] = lv_color_24_16_mix(&src_buf_u8[src_x], dest_buf_u16[dest_x], mask_buf[dest_x]); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u8 += src_stride; + mask_buf += mask_stride; + } + } + } + if (mask_buf && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(dsc, src_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) { + dest_buf_u16[dest_x] = lv_color_24_16_mix(&src_buf_u8[src_x], dest_buf_u16[dest_x], LV_OPA_MIX2(mask_buf[dest_x], opa)); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u8 += src_stride; + mask_buf += mask_stride; + } + } + } + } else { + uint16_t res = 0; + for (y = 0; y < h; y++) { + lv_color16_t *dest_buf_c16 = (lv_color16_t *) dest_buf_u16; + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += src_px_size) { + switch (dsc->blend_mode) { + case LV_BLEND_MODE_ADDITIVE: + res = (LV_MIN(dest_buf_c16[dest_x].red + (src_buf_u8[src_x + 2] >> 3), 31)) << 11; + res += (LV_MIN(dest_buf_c16[dest_x].green + (src_buf_u8[src_x + 1] >> 2), 63)) << 5; + res += LV_MIN(dest_buf_c16[dest_x].blue + (src_buf_u8[src_x + 0] >> 3), 31); + break; + case LV_BLEND_MODE_SUBTRACTIVE: + res = (LV_MAX(dest_buf_c16[dest_x].red - (src_buf_u8[src_x + 2] >> 3), 0)) << 11; + res += (LV_MAX(dest_buf_c16[dest_x].green - (src_buf_u8[src_x + 1] >> 2), 0)) << 5; + res += LV_MAX(dest_buf_c16[dest_x].blue - (src_buf_u8[src_x + 0] >> 3), 0); + break; + case LV_BLEND_MODE_MULTIPLY: + res = ((dest_buf_c16[dest_x].red * (src_buf_u8[src_x + 2] >> 3)) >> 5) << 11; + res += ((dest_buf_c16[dest_x].green * (src_buf_u8[src_x + 1] >> 2)) >> 6) << 5; + res += (dest_buf_c16[dest_x].blue * (src_buf_u8[src_x + 0] >> 3)) >> 5; + break; + default: + LV_LOG_WARN("Not supported blend mode: %d", dsc->blend_mode); + return; + } + + if (mask_buf == NULL) { + dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], opa); + } else { + if (opa >= LV_OPA_MAX) { + dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], mask_buf[dest_x]); + } else { + dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], LV_OPA_MIX2(mask_buf[dest_x], opa)); + } + } + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u8 += src_stride; + if (mask_buf) { + mask_buf += mask_stride; + } + } + + } +} + +static void LV_ATTRIBUTE_FAST_MEM argb8888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + uint16_t *dest_buf_u16 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const uint8_t *src_buf_u8 = dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + int32_t dest_x; + int32_t src_x; + int32_t y; + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 4) { + dest_buf_u16[dest_x] = lv_color_24_16_mix(&src_buf_u8[src_x], dest_buf_u16[dest_x], src_buf_u8[src_x + 3]); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u8 += src_stride; + } + } + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_WITH_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 4) { + dest_buf_u16[dest_x] = lv_color_24_16_mix(&src_buf_u8[src_x], dest_buf_u16[dest_x], LV_OPA_MIX2(src_buf_u8[src_x + 3], + opa)); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u8 += src_stride; + } + } + } else if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_WITH_MASK(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 4) { + dest_buf_u16[dest_x] = lv_color_24_16_mix(&src_buf_u8[src_x], dest_buf_u16[dest_x], + LV_OPA_MIX2(src_buf_u8[src_x + 3], mask_buf[dest_x])); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u8 += src_stride; + mask_buf += mask_stride; + } + } + } else if (mask_buf && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_MIX_MASK_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 4) { + dest_buf_u16[dest_x] = lv_color_24_16_mix(&src_buf_u8[src_x], dest_buf_u16[dest_x], + LV_OPA_MIX3(src_buf_u8[src_x + 3], mask_buf[dest_x], opa)); + } + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u8 += src_stride; + mask_buf += mask_stride; + } + } + } + } else { + uint16_t res = 0; + for (y = 0; y < h; y++) { + lv_color16_t *dest_buf_c16 = (lv_color16_t *) dest_buf_u16; + for (dest_x = 0, src_x = 0; dest_x < w; dest_x++, src_x += 4) { + switch (dsc->blend_mode) { + case LV_BLEND_MODE_ADDITIVE: + res = (LV_MIN(dest_buf_c16[dest_x].red + (src_buf_u8[src_x + 2] >> 3), 31)) << 11; + res += (LV_MIN(dest_buf_c16[dest_x].green + (src_buf_u8[src_x + 1] >> 2), 63)) << 5; + res += LV_MIN(dest_buf_c16[dest_x].blue + (src_buf_u8[src_x + 0] >> 3), 31); + break; + case LV_BLEND_MODE_SUBTRACTIVE: + res = (LV_MAX(dest_buf_c16[dest_x].red - (src_buf_u8[src_x + 2] >> 3), 0)) << 11; + res += (LV_MAX(dest_buf_c16[dest_x].green - (src_buf_u8[src_x + 1] >> 2), 0)) << 5; + res += LV_MAX(dest_buf_c16[dest_x].blue - (src_buf_u8[src_x + 0] >> 3), 0); + break; + case LV_BLEND_MODE_MULTIPLY: + res = ((dest_buf_c16[dest_x].red * (src_buf_u8[src_x + 2] >> 3)) >> 5) << 11; + res += ((dest_buf_c16[dest_x].green * (src_buf_u8[src_x + 1] >> 2)) >> 6) << 5; + res += (dest_buf_c16[dest_x].blue * (src_buf_u8[src_x + 0] >> 3)) >> 5; + break; + default: + LV_LOG_WARN("Not supported blend mode: %d", dsc->blend_mode); + return; + } + + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], src_buf_u8[src_x + 3]); + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], LV_OPA_MIX2(opa, src_buf_u8[src_x + 3])); + } else { + if (opa >= LV_OPA_MAX) { + dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], mask_buf[dest_x]); + } else dest_buf_u16[dest_x] = lv_color_16_16_mix(res, dest_buf_u16[dest_x], LV_OPA_MIX3(mask_buf[dest_x], opa, + src_buf_u8[src_x + 3])); + } + } + + dest_buf_u16 = drawbuf_next_row(dest_buf_u16, dest_stride); + src_buf_u8 += src_stride; + if (mask_buf) { + mask_buf += mask_stride; + } + } + } +} + +static inline uint16_t LV_ATTRIBUTE_FAST_MEM l8_to_rgb565(const uint8_t c1) +{ + return ((c1 & 0xF8) << 8) + ((c1 & 0xFC) << 3) + ((c1 & 0xF8) >> 3); +} + +static inline uint16_t LV_ATTRIBUTE_FAST_MEM lv_color_8_16_mix(const uint8_t c1, uint16_t c2, uint8_t mix) +{ + + if (mix == 0) { + return c2; + } else if (mix == 255) { + return ((c1 & 0xF8) << 8) + ((c1 & 0xFC) << 3) + ((c1 & 0xF8) >> 3); + } else { + lv_opa_t mix_inv = 255 - mix; + + return ((((c1 >> 3) * mix + ((c2 >> 11) & 0x1F) * mix_inv) << 3) & 0xF800) + + ((((c1 >> 2) * mix + ((c2 >> 5) & 0x3F) * mix_inv) >> 3) & 0x07E0) + + (((c1 >> 3) * mix + (c2 & 0x1F) * mix_inv) >> 8); + } +} + +static inline uint16_t LV_ATTRIBUTE_FAST_MEM lv_color_24_16_mix(const uint8_t *c1, uint16_t c2, uint8_t mix) +{ + if (mix == 0) { + return c2; + } else if (mix == 255) { + return ((c1[2] & 0xF8) << 8) + ((c1[1] & 0xFC) << 3) + ((c1[0] & 0xF8) >> 3); + } else { + lv_opa_t mix_inv = 255 - mix; + + return ((((c1[2] >> 3) * mix + ((c2 >> 11) & 0x1F) * mix_inv) << 3) & 0xF800) + + ((((c1[1] >> 2) * mix + ((c2 >> 5) & 0x3F) * mix_inv) >> 3) & 0x07E0) + + (((c1[0] >> 3) * mix + (c2 & 0x1F) * mix_inv) >> 8); + } +} + +static inline void *LV_ATTRIBUTE_FAST_MEM drawbuf_next_row(const void *buf, uint32_t stride) +{ + return (void *)((uint8_t *)buf + stride); +} diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_rgb888.c b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_rgb888.c new file mode 100644 index 00000000..bee9a549 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_draw_sw_blend_to_rgb888.c @@ -0,0 +1,954 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_draw_sw_blend_to_rgb888.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_draw_sw_blend_to_rgb888.h" + +#include "lv_assert.h" +#include "lv_types.h" +#include "lv_log.h" +#include "lv_draw_sw_blend.h" +#include "lv_math.h" +#include "lv_color.h" +#include "lv_string.h" + +#include "esp_lvgl_port_lv_blend.h" + +/********************* + * DEFINES + *********************/ + +#define LV_ATTRIBUTE_FAST_MEM + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void /* LV_ATTRIBUTE_FAST_MEM */ al88_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size); + +static void /* LV_ATTRIBUTE_FAST_MEM */ i1_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size); + +static inline uint8_t /* LV_ATTRIBUTE_FAST_MEM */ get_bit(const uint8_t *buf, int32_t bit_idx); + +static void /* LV_ATTRIBUTE_FAST_MEM */ l8_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size); + +static void /* LV_ATTRIBUTE_FAST_MEM */ rgb565_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size); + +static void /* LV_ATTRIBUTE_FAST_MEM */ rgb888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, + const uint8_t dest_px_size, + uint32_t src_px_size); + +static void /* LV_ATTRIBUTE_FAST_MEM */ argb8888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, + uint32_t dest_px_size); + +static inline void /* LV_ATTRIBUTE_FAST_MEM */ lv_color_8_24_mix(const uint8_t src, uint8_t *dest, uint8_t mix); + +static inline void /* LV_ATTRIBUTE_FAST_MEM */ lv_color_24_24_mix(const uint8_t *src, uint8_t *dest, uint8_t mix); + +static inline void /* LV_ATTRIBUTE_FAST_MEM */ blend_non_normal_pixel(uint8_t *dest, lv_color32_t src, + lv_blend_mode_t mode); +static inline void * /* LV_ATTRIBUTE_FAST_MEM */ drawbuf_next_row(const void *buf, uint32_t stride); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB888 +#define LV_DRAW_SW_COLOR_BLEND_TO_RGB888(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB888_WITH_OPA +#define LV_DRAW_SW_COLOR_BLEND_TO_RGB888_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB888_WITH_MASK +#define LV_DRAW_SW_COLOR_BLEND_TO_RGB888_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB888_MIX_MASK_OPA +#define LV_DRAW_SW_COLOR_BLEND_TO_RGB888_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888 +#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_OPA +#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_MASK +#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA +#define LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888 +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_WITH_OPA +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_WITH_MASK +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888 +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_WITH_OPA +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_WITH_MASK +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888 +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_WITH_OPA +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_WITH_MASK +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_I1_BLEND_NORMAL_TO_888 +#define LV_DRAW_SW_I1_BLEND_NORMAL_TO_888(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_WITH_OPA +#define LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_WITH_OPA(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_WITH_MASK +#define LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_WITH_MASK(...) LV_RESULT_INVALID +#endif + +#ifndef LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_MIX_MASK_OPA +#define LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_MIX_MASK_OPA(...) LV_RESULT_INVALID +#endif + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void LV_ATTRIBUTE_FAST_MEM lv_draw_sw_blend_color_to_rgb888(_lv_draw_sw_blend_fill_dsc_t *dsc, uint32_t dest_px_size) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + const lv_opa_t *mask = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + int32_t dest_stride = dsc->dest_stride; + + int32_t x; + int32_t y; + + LV_UNUSED(w); + LV_UNUSED(h); + LV_UNUSED(x); + LV_UNUSED(y); + LV_UNUSED(opa); + LV_UNUSED(mask); + LV_UNUSED(mask_stride); + LV_UNUSED(dest_stride); + + /*Simple fill*/ + if (mask == NULL && opa >= LV_OPA_MAX) { + if (dsc->use_asm && dest_px_size == 3) { + LV_DRAW_SW_COLOR_BLEND_TO_RGB888(dsc, dest_px_size); + } else { + if (dest_px_size == 3) { + uint8_t *dest_buf_u8 = dsc->dest_buf; + uint8_t *dest_buf_ori = dsc->dest_buf; + w *= dest_px_size; + + for (x = 0; x < w; x += 3) { + dest_buf_u8[x + 0] = dsc->color.blue; + dest_buf_u8[x + 1] = dsc->color.green; + dest_buf_u8[x + 2] = dsc->color.red; + } + + dest_buf_u8 += dest_stride; + + for (y = 1; y < h; y++) { + lv_memcpy(dest_buf_u8, dest_buf_ori, w); + dest_buf_u8 += dest_stride; + } + } + if (dest_px_size == 4) { + uint32_t color32 = lv_color_to_u32(dsc->color); + uint32_t *dest_buf_u32 = dsc->dest_buf; + for (y = 0; y < h; y++) { + for (x = 0; x <= w - 16; x += 16) { + dest_buf_u32[x + 0] = color32; + dest_buf_u32[x + 1] = color32; + dest_buf_u32[x + 2] = color32; + dest_buf_u32[x + 3] = color32; + + dest_buf_u32[x + 4] = color32; + dest_buf_u32[x + 5] = color32; + dest_buf_u32[x + 6] = color32; + dest_buf_u32[x + 7] = color32; + + dest_buf_u32[x + 8] = color32; + dest_buf_u32[x + 9] = color32; + dest_buf_u32[x + 10] = color32; + dest_buf_u32[x + 11] = color32; + + dest_buf_u32[x + 12] = color32; + dest_buf_u32[x + 13] = color32; + dest_buf_u32[x + 14] = color32; + dest_buf_u32[x + 15] = color32; + } + for (; x < w; x ++) { + dest_buf_u32[x] = color32; + } + + dest_buf_u32 = drawbuf_next_row(dest_buf_u32, dest_stride); + } + } + } + } + /*Opacity only*/ + else if (mask == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_RGB888_WITH_OPA(dsc, dest_px_size)) { + uint32_t color32 = lv_color_to_u32(dsc->color); + uint8_t *dest_buf = dsc->dest_buf; + w *= dest_px_size; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x += dest_px_size) { + lv_color_24_24_mix((const uint8_t *)&color32, &dest_buf[x], opa); + } + + dest_buf = drawbuf_next_row(dest_buf, dest_stride); + } + } + } + /*Masked with full opacity*/ + else if (mask && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_RGB888_WITH_MASK(dsc, dest_px_size)) { + uint32_t color32 = lv_color_to_u32(dsc->color); + uint8_t *dest_buf = dsc->dest_buf; + w *= dest_px_size; + + for (y = 0; y < h; y++) { + uint32_t mask_x; + for (x = 0, mask_x = 0; x < w; x += dest_px_size, mask_x++) { + lv_color_24_24_mix((const uint8_t *)&color32, &dest_buf[x], mask[mask_x]); + } + dest_buf += dest_stride; + mask += mask_stride; + } + } + } + /*Masked with opacity*/ + else { + if (LV_RESULT_INVALID == LV_DRAW_SW_COLOR_BLEND_TO_RGB888_MIX_MASK_OPA(dsc, dest_px_size)) { + uint32_t color32 = lv_color_to_u32(dsc->color); + uint8_t *dest_buf = dsc->dest_buf; + w *= dest_px_size; + + for (y = 0; y < h; y++) { + uint32_t mask_x; + for (x = 0, mask_x = 0; x < w; x += dest_px_size, mask_x++) { + lv_color_24_24_mix((const uint8_t *) &color32, &dest_buf[x], LV_OPA_MIX2(opa, mask[mask_x])); + } + dest_buf += dest_stride; + mask += mask_stride; + } + } + } +} + +void LV_ATTRIBUTE_FAST_MEM lv_draw_sw_blend_image_to_rgb888(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size) +{ + + switch (dsc->src_color_format) { + case LV_COLOR_FORMAT_RGB565: + rgb565_image_blend(dsc, dest_px_size); + break; + case LV_COLOR_FORMAT_RGB888: + rgb888_image_blend(dsc, dest_px_size, 3); + break; + case LV_COLOR_FORMAT_XRGB8888: + rgb888_image_blend(dsc, dest_px_size, 4); + break; + case LV_COLOR_FORMAT_ARGB8888: + argb8888_image_blend(dsc, dest_px_size); + break; + case LV_COLOR_FORMAT_L8: + l8_image_blend(dsc, dest_px_size); + break; + case LV_COLOR_FORMAT_AL88: + al88_image_blend(dsc, dest_px_size); + break; + case LV_COLOR_FORMAT_I1: + i1_image_blend(dsc, dest_px_size); + break; + default: + LV_LOG_WARN("Not supported source color format"); + break; + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void LV_ATTRIBUTE_FAST_MEM i1_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + uint8_t *dest_buf_u8 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const uint8_t *src_buf_i1 = dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + int32_t dest_x; + int32_t src_x; + int32_t y; + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_I1_BLEND_NORMAL_TO_888(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + uint8_t chan_val = get_bit(src_buf_i1, src_x) * 255; + dest_buf_u8[dest_x + 2] = chan_val; + dest_buf_u8[dest_x + 1] = chan_val; + dest_buf_u8[dest_x + 0] = chan_val; + } + dest_buf_u8 = drawbuf_next_row(dest_buf_u8, dest_stride); + src_buf_i1 = drawbuf_next_row(src_buf_i1, src_stride); + } + } + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_WITH_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + uint8_t chan_val = get_bit(src_buf_i1, src_x) * 255; + lv_color_8_24_mix(chan_val, &dest_buf_u8[dest_x], opa); + } + dest_buf_u8 = drawbuf_next_row(dest_buf_u8, dest_stride); + src_buf_i1 = drawbuf_next_row(src_buf_i1, src_stride); + } + } + } else if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_WITH_MASK(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + uint8_t chan_val = get_bit(src_buf_i1, src_x) * 255; + lv_color_8_24_mix(chan_val, &dest_buf_u8[dest_x], mask_buf[src_x]); + } + dest_buf_u8 = drawbuf_next_row(dest_buf_u8, dest_stride); + src_buf_i1 = drawbuf_next_row(src_buf_i1, src_stride); + mask_buf += mask_stride; + } + } + } else if (mask_buf && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_I1_BLEND_NORMAL_TO_888_MIX_MASK_OPA(dsc)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + uint8_t chan_val = get_bit(src_buf_i1, src_x) * 255; + lv_color_8_24_mix(chan_val, &dest_buf_u8[dest_x], LV_OPA_MIX2(opa, mask_buf[src_x])); + } + dest_buf_u8 = drawbuf_next_row(dest_buf_u8, dest_stride); + src_buf_i1 = drawbuf_next_row(src_buf_i1, src_stride); + mask_buf += mask_stride; + } + } + } + } else { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + lv_color32_t src_argb; + src_argb.red = get_bit(src_buf_i1, src_x) * 255; + src_argb.green = src_argb.red; + src_argb.blue = src_argb.red; + if (mask_buf == NULL) { + src_argb.alpha = opa; + } else { + src_argb.alpha = LV_OPA_MIX2(mask_buf[src_x], opa); + } + blend_non_normal_pixel(&dest_buf_u8[dest_x], src_argb, dsc->blend_mode); + } + if (mask_buf) { + mask_buf += mask_stride; + } + dest_buf_u8 = drawbuf_next_row(dest_buf_u8, dest_stride); + src_buf_i1 = drawbuf_next_row(src_buf_i1, src_stride); + } + } +} + +static void LV_ATTRIBUTE_FAST_MEM al88_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + uint8_t *dest_buf_u8 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const lv_color16a_t *src_buf_al88 = dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + int32_t dest_x; + int32_t src_x; + int32_t y; + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888(dsc, dest_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + lv_color_8_24_mix(src_buf_al88[src_x].lumi, &dest_buf_u8[dest_x], src_buf_al88[src_x].alpha); + } + dest_buf_u8 += dest_stride; + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + } + } + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_OPA(dsc, dest_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + lv_color_8_24_mix(src_buf_al88[src_x].lumi, &dest_buf_u8[dest_x], LV_OPA_MIX2(src_buf_al88[src_x].alpha, opa)); + } + dest_buf_u8 += dest_stride; + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + } + } + } else if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_MASK(dsc, dest_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + lv_color_8_24_mix(src_buf_al88[src_x].lumi, &dest_buf_u8[dest_x], LV_OPA_MIX2(src_buf_al88[src_x].alpha, + mask_buf[src_x])); + } + dest_buf_u8 += dest_stride; + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + mask_buf += mask_stride; + } + } + } else if (mask_buf && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(dsc, dest_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + lv_color_8_24_mix(src_buf_al88[src_x].lumi, &dest_buf_u8[dest_x], LV_OPA_MIX3(src_buf_al88[src_x].alpha, + mask_buf[src_x], opa)); + } + dest_buf_u8 += dest_stride; + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + mask_buf += mask_stride; + } + } + } + } else { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + lv_color32_t src_argb; + src_argb.red = src_argb.green = src_argb.blue = src_buf_al88[src_x].lumi; + if (mask_buf == NULL) { + src_argb.alpha = LV_OPA_MIX2(src_buf_al88[src_x].alpha, opa); + } else { + src_argb.alpha = LV_OPA_MIX3(src_buf_al88[src_x].alpha, mask_buf[dest_x], opa); + } + blend_non_normal_pixel(&dest_buf_u8[dest_x], src_argb, dsc->blend_mode); + } + if (mask_buf) { + mask_buf += mask_stride; + } + dest_buf_u8 += dest_stride; + src_buf_al88 = drawbuf_next_row(src_buf_al88, src_stride); + } + } +} + +static void LV_ATTRIBUTE_FAST_MEM l8_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + uint8_t *dest_buf_u8 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const uint8_t *src_buf_l8 = dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + int32_t dest_x; + int32_t src_x; + int32_t y; + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888(dsc, dest_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + dest_buf_u8[dest_x + 2] = src_buf_l8[src_x]; + dest_buf_u8[dest_x + 1] = src_buf_l8[src_x]; + dest_buf_u8[dest_x + 0] = src_buf_l8[src_x]; + } + dest_buf_u8 += dest_stride; + src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride); + } + } + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_OPA(dsc, dest_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + lv_color_8_24_mix(src_buf_l8[src_x], &dest_buf_u8[dest_x], opa); + } + dest_buf_u8 += dest_stride; + src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride); + } + } + } else if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_WITH_MASK(dsc, dest_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + lv_color_8_24_mix(src_buf_l8[src_x], &dest_buf_u8[dest_x], mask_buf[src_x]); + } + dest_buf_u8 += dest_stride; + src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride); + mask_buf += mask_stride; + } + } + } else if (mask_buf && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_L8_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(dsc, dest_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + lv_color_8_24_mix(src_buf_l8[src_x], &dest_buf_u8[dest_x], LV_OPA_MIX2(opa, mask_buf[src_x])); + } + dest_buf_u8 += dest_stride; + src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride); + mask_buf += mask_stride; + } + } + } + } else { + lv_color32_t src_argb; + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + src_argb.red = src_buf_l8[src_x]; + src_argb.green = src_buf_l8[src_x]; + src_argb.blue = src_buf_l8[src_x]; + if (mask_buf == NULL) { + src_argb.alpha = opa; + } else { + src_argb.alpha = LV_OPA_MIX2(mask_buf[dest_x], opa); + } + blend_non_normal_pixel(&dest_buf_u8[dest_x], src_argb, dsc->blend_mode); + } + if (mask_buf) { + mask_buf += mask_stride; + } + dest_buf_u8 += dest_stride; + src_buf_l8 = drawbuf_next_row(src_buf_l8, src_stride); + } + } +} + +static void LV_ATTRIBUTE_FAST_MEM rgb565_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + uint8_t *dest_buf_u8 = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const lv_color16_t *src_buf_c16 = (const lv_color16_t *) dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + int32_t src_x; + int32_t dest_x; + int32_t y; + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888(dsc, dest_px_size)) { + for (y = 0; y < h; y++) { + for (src_x = 0, dest_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + dest_buf_u8[dest_x + 2] = (src_buf_c16[src_x].red * 2106) >> 8; /*To make it rounded*/ + dest_buf_u8[dest_x + 1] = (src_buf_c16[src_x].green * 1037) >> 8; + dest_buf_u8[dest_x + 0] = (src_buf_c16[src_x].blue * 2106) >> 8; + } + dest_buf_u8 += dest_stride; + src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride); + } + } + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_WITH_OPA(dsc, dest_px_size)) { + uint8_t res[3]; + for (y = 0; y < h; y++) { + for (src_x = 0, dest_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + res[2] = (src_buf_c16[src_x].red * 2106) >> 8; /*To make it rounded*/ + res[1] = (src_buf_c16[src_x].green * 1037) >> 8; + res[0] = (src_buf_c16[src_x].blue * 2106) >> 8; + lv_color_24_24_mix(res, &dest_buf_u8[dest_x], opa); + } + dest_buf_u8 += dest_stride; + src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride); + } + } + } else if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_WITH_MASK(dsc, dest_px_size)) { + uint8_t res[3]; + for (y = 0; y < h; y++) { + for (src_x = 0, dest_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + res[2] = (src_buf_c16[src_x].red * 2106) >> 8; /*To make it rounded*/ + res[1] = (src_buf_c16[src_x].green * 1037) >> 8; + res[0] = (src_buf_c16[src_x].blue * 2106) >> 8; + lv_color_24_24_mix(res, &dest_buf_u8[dest_x], mask_buf[src_x]); + } + dest_buf_u8 += dest_stride; + src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride); + mask_buf += mask_stride; + } + } + } else { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(dsc, dest_px_size)) { + uint8_t res[3]; + for (y = 0; y < h; y++) { + for (src_x = 0, dest_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + res[2] = (src_buf_c16[src_x].red * 2106) >> 8; /*To make it rounded*/ + res[1] = (src_buf_c16[src_x].green * 1037) >> 8; + res[0] = (src_buf_c16[src_x].blue * 2106) >> 8; + lv_color_24_24_mix(res, &dest_buf_u8[dest_x], LV_OPA_MIX2(opa, mask_buf[src_x])); + } + dest_buf_u8 += dest_stride; + src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride); + mask_buf += mask_stride; + } + } + } + } else { + lv_color32_t src_argb; + for (y = 0; y < h; y++) { + for (src_x = 0, dest_x = 0; src_x < w; src_x++, dest_x += dest_px_size) { + src_argb.red = (src_buf_c16[src_x].red * 2106) >> 8; + src_argb.green = (src_buf_c16[src_x].green * 1037) >> 8; + src_argb.blue = (src_buf_c16[src_x].blue * 2106) >> 8; + if (mask_buf == NULL) { + src_argb.alpha = opa; + } else { + src_argb.alpha = LV_OPA_MIX2(mask_buf[src_x], opa); + } + blend_non_normal_pixel(&dest_buf_u8[dest_x], src_argb, dsc->blend_mode); + } + if (mask_buf) { + mask_buf += mask_stride; + } + dest_buf_u8 += dest_stride; + src_buf_c16 = drawbuf_next_row(src_buf_c16, src_stride); + } + } +} + +static void LV_ATTRIBUTE_FAST_MEM rgb888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, const uint8_t dest_px_size, + uint32_t src_px_size) +{ + int32_t w = dsc->dest_w * dest_px_size; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + uint8_t *dest_buf = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const uint8_t *src_buf = dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + int32_t dest_x; + int32_t src_x; + int32_t y; + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + /*Special case*/ + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (dsc->use_asm && dest_px_size == 3 && src_px_size == 3) { + LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888(dsc, src_px_size, dest_px_size); + } else { + if (src_px_size == dest_px_size) { + for (y = 0; y < h; y++) { + lv_memcpy(dest_buf, src_buf, w); + dest_buf += dest_stride; + src_buf += src_stride; + } + } else { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x += dest_px_size, src_x += src_px_size) { + dest_buf[dest_x + 0] = src_buf[src_x + 0]; + dest_buf[dest_x + 1] = src_buf[src_x + 1]; + dest_buf[dest_x + 2] = src_buf[src_x + 2]; + } + dest_buf += dest_stride; + src_buf += src_stride; + } + } + } + } + if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_WITH_OPA(dsc, dest_px_size, src_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x += dest_px_size, src_x += src_px_size) { + lv_color_24_24_mix(&src_buf[src_x], &dest_buf[dest_x], opa); + } + dest_buf += dest_stride; + src_buf += src_stride; + } + } + } + if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_WITH_MASK(dsc, dest_px_size, src_px_size)) { + uint32_t mask_x; + for (y = 0; y < h; y++) { + for (mask_x = 0, dest_x = 0, src_x = 0; dest_x < w; mask_x++, dest_x += dest_px_size, src_x += src_px_size) { + lv_color_24_24_mix(&src_buf[src_x], &dest_buf[dest_x], mask_buf[mask_x]); + } + dest_buf += dest_stride; + src_buf += src_stride; + mask_buf += mask_stride; + } + } + } + if (mask_buf && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(dsc, dest_px_size, src_px_size)) { + uint32_t mask_x; + for (y = 0; y < h; y++) { + for (mask_x = 0, dest_x = 0, src_x = 0; dest_x < w; mask_x++, dest_x += dest_px_size, src_x += src_px_size) { + lv_color_24_24_mix(&src_buf[src_x], &dest_buf[dest_x], LV_OPA_MIX2(opa, mask_buf[mask_x])); + } + dest_buf += dest_stride; + src_buf += src_stride; + mask_buf += mask_stride; + } + } + } + } else { + lv_color32_t src_argb; + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; dest_x < w; dest_x += dest_px_size, src_x += src_px_size) { + src_argb.red = src_buf[src_x + 2]; + src_argb.green = src_buf[src_x + 1]; + src_argb.blue = src_buf[src_x + 0]; + if (mask_buf == NULL) { + src_argb.alpha = opa; + } else { + src_argb.alpha = LV_OPA_MIX2(mask_buf[dest_x], opa); + } + + blend_non_normal_pixel(&dest_buf[dest_x], src_argb, dsc->blend_mode); + } + if (mask_buf) { + mask_buf += mask_stride; + } + dest_buf += dest_stride; + src_buf += src_stride; + } + } +} + +static void LV_ATTRIBUTE_FAST_MEM argb8888_image_blend(_lv_draw_sw_blend_image_dsc_t *dsc, uint32_t dest_px_size) +{ + int32_t w = dsc->dest_w; + int32_t h = dsc->dest_h; + lv_opa_t opa = dsc->opa; + uint8_t *dest_buf = dsc->dest_buf; + int32_t dest_stride = dsc->dest_stride; + const lv_color32_t *src_buf_c32 = dsc->src_buf; + int32_t src_stride = dsc->src_stride; + const lv_opa_t *mask_buf = dsc->mask_buf; + int32_t mask_stride = dsc->mask_stride; + + int32_t dest_x; + int32_t src_x; + int32_t y; + + if (dsc->blend_mode == LV_BLEND_MODE_NORMAL) { + if (mask_buf == NULL && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888(dsc, dest_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + lv_color_24_24_mix((const uint8_t *)&src_buf_c32[src_x], &dest_buf[dest_x], src_buf_c32[src_x].alpha); + } + dest_buf += dest_stride; + src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride); + } + } + } else if (mask_buf == NULL && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_WITH_OPA(dsc, dest_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + lv_color_24_24_mix((const uint8_t *)&src_buf_c32[src_x], &dest_buf[dest_x], LV_OPA_MIX2(src_buf_c32[src_x].alpha, opa)); + } + dest_buf += dest_stride; + src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride); + } + } + } else if (mask_buf && opa >= LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_WITH_MASK(dsc, dest_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + lv_color_24_24_mix((const uint8_t *)&src_buf_c32[src_x], &dest_buf[dest_x], + LV_OPA_MIX2(src_buf_c32[src_x].alpha, mask_buf[src_x])); + } + dest_buf += dest_stride; + src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride); + mask_buf += mask_stride; + } + } + } else if (mask_buf && opa < LV_OPA_MAX) { + if (LV_RESULT_INVALID == LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_MIX_MASK_OPA(dsc, dest_px_size)) { + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x++) { + lv_color_24_24_mix((const uint8_t *)&src_buf_c32[src_x], &dest_buf[dest_x], + LV_OPA_MIX3(src_buf_c32[src_x].alpha, mask_buf[src_x], opa)); + } + dest_buf += dest_stride; + src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride); + mask_buf += mask_stride; + } + } + } + } else { + lv_color32_t src_argb; + for (y = 0; y < h; y++) { + for (dest_x = 0, src_x = 0; src_x < w; dest_x += dest_px_size, src_x ++) { + src_argb = src_buf_c32[src_x]; + if (mask_buf == NULL) { + src_argb.alpha = LV_OPA_MIX2(src_argb.alpha, opa); + } else { + src_argb.alpha = LV_OPA_MIX3(src_argb.alpha, mask_buf[dest_x], opa); + } + + blend_non_normal_pixel(&dest_buf[dest_x], src_argb, dsc->blend_mode); + } + if (mask_buf) { + mask_buf += mask_stride; + } + dest_buf += dest_stride; + src_buf_c32 = drawbuf_next_row(src_buf_c32, src_stride); + } + } +} + +static inline void LV_ATTRIBUTE_FAST_MEM blend_non_normal_pixel(uint8_t *dest, lv_color32_t src, lv_blend_mode_t mode) +{ + uint8_t res[3] = {0, 0, 0}; + switch (mode) { + case LV_BLEND_MODE_ADDITIVE: + res[0] = LV_MIN(dest[0] + src.blue, 255); + res[1] = LV_MIN(dest[1] + src.green, 255); + res[2] = LV_MIN(dest[2] + src.red, 255); + break; + case LV_BLEND_MODE_SUBTRACTIVE: + res[0] = LV_MAX(dest[0] - src.blue, 0); + res[1] = LV_MAX(dest[1] - src.green, 0); + res[2] = LV_MAX(dest[2] - src.red, 0); + break; + case LV_BLEND_MODE_MULTIPLY: + res[0] = (dest[0] * src.blue) >> 8; + res[1] = (dest[1] * src.green) >> 8; + res[2] = (dest[2] * src.red) >> 8; + break; + default: + LV_LOG_WARN("Not supported blend mode: %d", mode); + return; + } + lv_color_24_24_mix(res, dest, src.alpha); +} + +static inline void LV_ATTRIBUTE_FAST_MEM lv_color_8_24_mix(const uint8_t src, uint8_t *dest, uint8_t mix) +{ + + if (mix == 0) { + return; + } + + if (mix >= LV_OPA_MAX) { + dest[0] = src; + dest[1] = src; + dest[2] = src; + } else { + lv_opa_t mix_inv = 255 - mix; + dest[0] = (uint32_t)((uint32_t)src * mix + dest[0] * mix_inv) >> 8; + dest[1] = (uint32_t)((uint32_t)src * mix + dest[1] * mix_inv) >> 8; + dest[2] = (uint32_t)((uint32_t)src * mix + dest[2] * mix_inv) >> 8; + } +} + +static inline void LV_ATTRIBUTE_FAST_MEM lv_color_24_24_mix(const uint8_t *src, uint8_t *dest, uint8_t mix) +{ + + if (mix == 0) { + return; + } + + if (mix >= LV_OPA_MAX) { + dest[0] = src[0]; + dest[1] = src[1]; + dest[2] = src[2]; + } else { + lv_opa_t mix_inv = 255 - mix; + dest[0] = (uint32_t)((uint32_t)src[0] * mix + dest[0] * mix_inv) >> 8; + dest[1] = (uint32_t)((uint32_t)src[1] * mix + dest[1] * mix_inv) >> 8; + dest[2] = (uint32_t)((uint32_t)src[2] * mix + dest[2] * mix_inv) >> 8; + } +} + +static inline uint8_t LV_ATTRIBUTE_FAST_MEM get_bit(const uint8_t *buf, int32_t bit_idx) +{ + return (buf[bit_idx / 8] >> (7 - (bit_idx % 8))) & 1; +} + +static inline void *LV_ATTRIBUTE_FAST_MEM drawbuf_next_row(const void *buf, uint32_t stride) +{ + return (void *)((uint8_t *)buf + stride); +} diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_string_builtin.c b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_string_builtin.c new file mode 100644 index 00000000..18959ea3 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_blend/src/lv_string_builtin.c @@ -0,0 +1,187 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file is derived from the LVGL project. + * See https://github.com/lvgl/lvgl for details. + */ + +/** + * @file lv_string_builtin.c + */ + +/********************* + * INCLUDES + *********************/ +//#include "../../lv_conf_internal.h" +#if LV_USE_STDLIB_STRING == LV_STDLIB_BUILTIN +#include "lv_assert.h" +#include "lv_log.h" +#include "lv_math.h" +#include "lv_string.h" + +/********************* + * DEFINES + *********************/ +#ifdef LV_ARCH_64 +#define MEM_UNIT uint64_t +#define ALIGN_MASK 0x7 +#else +#define MEM_UNIT uint32_t +#define ALIGN_MASK 0x3 +#endif + +#define LV_ATTRIBUTE_FAST_MEM + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ +#if LV_USE_LOG && LV_LOG_TRACE_MEM +#define LV_TRACE_MEM(...) LV_LOG_TRACE(__VA_ARGS__) +#else +#define LV_TRACE_MEM(...) +#endif + +#define _COPY(d, s) *d = *s; d++; s++; +#define _SET(d, v) *d = v; d++; +#define _REPEAT8(expr) expr expr expr expr expr expr expr expr + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void *LV_ATTRIBUTE_FAST_MEM lv_memcpy(void *dst, const void *src, size_t len) +{ + uint8_t *d8 = dst; + const uint8_t *s8 = src; + + /*Simplify for small memories*/ + if (len < 16) { + while (len) { + *d8 = *s8; + d8++; + s8++; + len--; + } + return dst; + } + + lv_uintptr_t d_align = (lv_uintptr_t)d8 & ALIGN_MASK; + lv_uintptr_t s_align = (lv_uintptr_t)s8 & ALIGN_MASK; + + /*Byte copy for unaligned memories*/ + if (s_align != d_align) { + while (len > 32) { + _REPEAT8(_COPY(d8, s8)); + _REPEAT8(_COPY(d8, s8)); + _REPEAT8(_COPY(d8, s8)); + _REPEAT8(_COPY(d8, s8)); + len -= 32; + } + while (len) { + _COPY(d8, s8) + len--; + } + return dst; + } + + /*Make the memories aligned*/ + if (d_align) { + d_align = ALIGN_MASK + 1 - d_align; + while (d_align && len) { + _COPY(d8, s8); + d_align--; + len--; + } + } + + uint32_t *d32 = (uint32_t *)d8; + const uint32_t *s32 = (uint32_t *)s8; + while (len > 32) { + _REPEAT8(_COPY(d32, s32)) + len -= 32; + } + + d8 = (uint8_t *)d32; + s8 = (const uint8_t *)s32; + while (len) { + _COPY(d8, s8) + len--; + } + + return dst; +} + +void LV_ATTRIBUTE_FAST_MEM lv_memset(void *dst, uint8_t v, size_t len) +{ + uint8_t *d8 = (uint8_t *)dst; + uintptr_t d_align = (lv_uintptr_t) d8 & ALIGN_MASK; + + /*Make the address aligned*/ + if (d_align) { + d_align = ALIGN_MASK + 1 - d_align; + while (d_align && len) { + _SET(d8, v); + len--; + d_align--; + } + } + + uint32_t v32 = (uint32_t)v + ((uint32_t)v << 8) + ((uint32_t)v << 16) + ((uint32_t)v << 24); + uint32_t *d32 = (uint32_t *)d8; + + while (len > 32) { + _REPEAT8(_SET(d32, v32)); + len -= 32; + } + + d8 = (uint8_t *)d32; + while (len) { + _SET(d8, v); + len--; + } +} + +void *LV_ATTRIBUTE_FAST_MEM lv_memmove(void *dst, const void *src, size_t len) +{ + if (dst < src || (char *)dst > ((char *)src + len)) { + return lv_memcpy(dst, src, len); + } + + if (dst > src) { + char *tmp = (char *)dst + len - 1; + char *s = (char *)src + len - 1; + + while (len--) { + *tmp-- = *s--; + } + } else { + char *tmp = (char *)dst; + char *s = (char *)src; + + while (len--) { + *tmp++ = *s++; + } + } + + return dst; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +#endif /*LV_STDLIB_BUILTIN*/ diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_fill_common.h b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_fill_common.h new file mode 100644 index 00000000..e4f22fd6 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_fill_common.h @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include +#include "lv_color.h" +#include "lv_draw_sw_blend.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------------------------------------- Macros and Types -------------------------------------------------- + +/** + * @brief Functionality test combinations + */ +typedef struct { + unsigned int min_w; // Minimum width of the test array + unsigned int min_h; // Minimum height of the test array + unsigned int max_w; // Maximum width of the test array + unsigned int max_h; // Maximum height of the test array + unsigned int min_unalign_byte; // Minimum amount of unaligned bytes of the test array + unsigned int max_unalign_byte; // Maximum amount of unaligned bytes of the test array + unsigned int unalign_step; // Increment step in bytes unalignment of the test array + unsigned int dest_stride_step; // Increment step in destination stride of the test array + unsigned int test_combinations_count; // Count of fest combinations +} test_matrix_params_t; + +/** + * @brief Functionality test case parameters + */ +typedef struct { + struct { + void *p_asm; // pointer to the working ASM test buf + void *p_ansi; // pointer to the working ANSI test buf + void *p_asm_alloc; // pointer to the beginning of the memory allocated for ASM test buf, used in free() + void *p_ansi_alloc; // pointer to the beginning of the memory allocated for ANSI test buf, used in free() + } buf; + void (*blend_api_func)(_lv_draw_sw_blend_fill_dsc_t *); // pointer to LVGL API function + void (*blend_api_px_func)(_lv_draw_sw_blend_fill_dsc_t *, + uint32_t); // pointer to LVGL API function with dest_px_size argument + lv_color_format_t color_format; // LV color format + size_t data_type_size; // Used data type size, eg sizeof() + size_t active_buf_len; // Length of buffer, where the actual data are stored (not including Canary bytes) + size_t total_buf_len; // Total length of buffer (including Canary bytes) + unsigned int dest_w; // Destination buffer width + unsigned int dest_h; // Destination buffer height + unsigned int dest_stride; // Destination buffer stride + unsigned int unalign_byte; // Destination buffer memory unalignment +} func_test_case_params_t; + +/** + * @brief Benchmark test case parameters + */ +typedef struct { + unsigned int height; // Test array height + unsigned int width; // Test array width + unsigned int stride; // Test array stride + unsigned int cc_height; // Corner case test array height + unsigned int cc_width; // Corner case test array width + unsigned int benchmark_cycles; // Count of benchmark cycles + void *array_align16; // test array with 16 byte alignment - testing most ideal case + void *array_align1; // test array with 1 byte alignment - testing worst case + void (*blend_api_func)(_lv_draw_sw_blend_fill_dsc_t *); // pointer to LVGL API function + void (*blend_api_px_func)(_lv_draw_sw_blend_fill_dsc_t *, + uint32_t); // pointer to LVGL API function with dest_px_size argument +} bench_test_case_params_t; + +#ifdef __cplusplus +} /*extern "C"*/ +#endif diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_image_common.h b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_image_common.h new file mode 100644 index 00000000..c836828e --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/lv_image_common.h @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_err.h" +#include +#include "lv_color.h" +#include "lv_draw_sw_blend.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------------------------------------- Macros and Types -------------------------------------------------- + +/** + * @brief Type of blend DUT function + */ +typedef enum { + OPERATION_FILL, + OPERATION_FILL_WITH_OPA, +} blend_operation_t; + +/** + * @brief Canary pixels amount depending on data type + * @note + * - We should use at least 16 bytes of memory for canary pixels because of esp32s3 TIE 16-bytes wide Q registers + * - Canary pixels are multiplied by sizeof(used_data_type) to get the memory length occupied by the canary pixels + * - The memory occupied by canary pixels should be in 16-byte multiples, to achieve 16-byte memory alignment in functionality test + * - For example, ideally, for RGB565 we would need 8 canary pixels -> 8 * sizeof(uint16_t) = 16 + */ +typedef enum { + CANARY_PIXELS_ARGB8888 = 4, /*!< Canary pixels: 4 * sizeof(uint32_t) = 16 */ + CANARY_PIXELS_RGB565 = 8, /*!< Canary pixels: 8 * sizeof(uint16_t) = 16 */ + CANARY_PIXELS_RGB888 = 6, /*!< Canary pixels: 6 * sizeof(uint24_t) = 18 */ +} canary_pixels_t; + +/** + * @brief Functionality test combinations for LV Image + */ +typedef struct { + unsigned int min_w; /*!< Minimum width of the test array */ + unsigned int min_h; /*!< Minimum height of the test array */ + unsigned int max_w; /*!< Maximum width of the test array */ + unsigned int max_h; /*!< Maximum height of the test array */ + unsigned int + src_min_unalign_byte; /*!< Minimum amount of unaligned bytes of the source test array */ + unsigned int + dest_min_unalign_byte; /*!< Minimum amount of unaligned bytes of the destination test array */ + unsigned int + src_max_unalign_byte; /*!< Maximum amount of unaligned bytes of the source test array */ + unsigned int + dest_max_unalign_byte; /*!< Maximum amount of unaligned bytes of the destination test array */ + unsigned int + src_unalign_step; /*!< Increment step in bytes unalignment of the source test array */ + unsigned int + dest_unalign_step; /*!< Increment step in bytes unalignment of the destination test array */ + unsigned int + src_stride_step; /*!< Increment step in destination stride of the source test array */ + unsigned int + dest_stride_step; /*!< Increment step in destination stride of the destination test array */ + unsigned int test_combinations_count; /*!< Count of fest combinations */ +} test_matrix_lv_image_params_t; + + +/** + * @brief Functionality test case parameters for LV Image + */ +typedef struct { + struct { + void *p_src; /*!< pointer to the source test buff (common src buffer for both the ANSI and ASM) */ + void *p_src_alloc; /*!< pointer to the beginning of the memory allocated for the source ASM test buf, used in free() */ + void *p_dest_asm; /*!< pointer to the destination ASM test buf */ + void *p_dest_ansi; /*!< pointer to the destination ANSI test buf */ + void *p_dest_asm_alloc; /*!< pointer to the beginning of the memory allocated for the destination ASM test buf, used in free() */ + void *p_dest_ansi_alloc; /*!< pointer to the beginning of the memory allocated for the destination ANSI test buf, used in free() */ + } buf; + void (*blend_api_func)(_lv_draw_sw_blend_image_dsc_t *); /*!< pointer to LVGL API function */ + void (*blend_api_func_px_size)(_lv_draw_sw_blend_image_dsc_t *, + uint32_t); /*!< pointer to LVGL API function, with additional parameter: pixel size */ + lv_color_format_t color_format; /*!< LV color format */ + size_t src_data_type_size; /*!< Used data type size in the source buffer, eg sizeof(src_buff[0]) */ + size_t dest_data_type_size; /*!< Used data type size in the destination buffer, eg sizeof(dest_buff[0]) */ + size_t src_buf_len; /*!< Length of the source buffer, including matrix padding (no Canary pixels are used for source buffer) */ + size_t active_dest_buf_len; /*!< Length of the destination buffer, where the actual data are stored, including matrix padding, not including Canary pixels */ + size_t total_dest_buf_len; /*!< Total length of the destination buffer (including Canary pixels and matrix padding) */ + size_t canary_pixels; /*!< Canary pixels must be adjusted according to the used color type, to achieve aligned memory effect */ + size_t memory_alignment_offset; /*!< Memory offset, to correct canary pixels memory shift for RGB888 */ + unsigned int dest_w; /*!< Destination buffer width */ + unsigned int dest_h; /*!< Destination buffer height */ + unsigned int src_stride; /*!< Source buffer stride */ + unsigned int dest_stride; /*!< Destination buffer stride */ + unsigned int src_unalign_byte; /*!< Source buffer memory unalignment */ + unsigned int dest_unalign_byte; /*!< Destination buffer memory unalignment */ + blend_operation_t operation_type; /*!< Type of fundamental blend operation */ +} func_test_case_lv_image_params_t; + + +/** + * @brief Benchmark test case parameters for LV Image + */ +typedef struct { + unsigned int height; /*!< Test array height */ + unsigned int width; /*!< Test array width */ + unsigned int dest_stride; /*!< Destination test array stride */ + unsigned int src_stride; /*!< Source test array stride */ + unsigned int cc_height; /*!< Corner case test array height */ + unsigned int cc_width; /*!< Corner case test array width */ + unsigned int benchmark_cycles; /*!< Count of benchmark cycles */ + void *src_array_align16; /*!< Source test array with 16 byte alignment - testing most ideal case */ + void *src_array_align1; /*!< Source test array with 1 byte alignment - testing worst case */ + void *dest_array_align16; /*!< Destination test array with 16 byte alignment - testing most ideal case */ + void *dest_array_align1; /*!< Destination test array with 1 byte alignment - testing worst case */ + void (*blend_api_func)(_lv_draw_sw_blend_image_dsc_t *); /*!< pointer to LVGL API function */ + void (*blend_api_func_px_size)(_lv_draw_sw_blend_image_dsc_t *, + uint32_t); /*!< pointer to LVGL API function, with additional parameter: pixel size */ + lv_color_format_t color_format; /*!< LV color format */ +} bench_test_case_lv_image_params_t; + +#ifdef __cplusplus +} /*extern "C"*/ +#endif diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/test_app_main.c b/components/espressif__esp_lvgl_port/test_apps/simd/main/test_app_main.c new file mode 100644 index 00000000..c02cc997 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/test_app_main.c @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "unity.h" +#include "unity_test_utils.h" +#include "lv_fill_common.h" + +#define TEST_MEMORY_LEAK_THRESHOLD (300) + +void app_main(void) +{ + + // ______ _____ ______ _ _ + // | _ \/ ___|| ___ \ | | | | + // | | | |\ `--. | |_/ / | |_ ___ ___ | |_ + // | | | | `--. \| __/ | __| / _ \/ __|| __| + // | |/ / /\__/ /| | | |_ | __/\__ \| |_ + // |___/ \____/ \_| \__| \___||___/ \__| + + printf("______ _____ ______ _ _ \r\n"); + printf("| _ \\/ ___|| ___ \\ | | | | \r\n"); + printf("| | | |\\ `--. | |_/ / | |_ ___ ___ | |_ \r\n"); + printf("| | | | `--. \\| __/ | __| / _ \\/ __|| __|\r\n"); + printf("| |/ / /\\__/ /| | | |_ | __/\\__ \\| |_ \r\n"); + printf("|___/ \\____/ \\_| \\__| \\___||___/ \\__|\r\n"); + + + UNITY_BEGIN(); + unity_run_menu(); + UNITY_END(); +} + +/* setUp runs before every test */ +void setUp(void) +{ + // Check for memory leaks + unity_utils_set_leak_level(TEST_MEMORY_LEAK_THRESHOLD); + unity_utils_record_free_mem(); +} + +/* tearDown runs after every test */ +void tearDown(void) +{ + // Evaluate memory leaks + unity_utils_evaluate_leaks(); +} diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_fill_benchmark.c b/components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_fill_benchmark.c new file mode 100644 index 00000000..988a4e89 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_fill_benchmark.c @@ -0,0 +1,214 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "unity.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" // for xthal_get_ccount() +#include "lv_fill_common.h" +#include "lv_draw_sw_blend.h" +#include "lv_draw_sw_blend_to_argb8888.h" +#include "lv_draw_sw_blend_to_rgb565.h" +#include "lv_draw_sw_blend_to_rgb888.h" + +#define WIDTH 128 +#define HEIGHT 128 +#define STRIDE WIDTH +#define UNALIGN_BYTES 1 +#define BENCHMARK_CYCLES 1000 + +// ------------------------------------------------- Macros and Types -------------------------------------------------- + +static const char *TAG_LV_FILL_BENCH = "LV Fill Benchmark"; +static const char *asm_ansi_func[] = {"ASM", "ANSI"}; +static lv_color_t test_color = { + .blue = 0x56, + .green = 0x34, + .red = 0x12, +}; + +// ------------------------------------------------ Static function headers -------------------------------------------- + +/** + * @brief Initialize the benchmark test + */ +static void lv_fill_benchmark_init(bench_test_case_params_t *test_params); + +/** + * @brief Run the benchmark test + */ +static float lv_fill_benchmark_run(bench_test_case_params_t *test_params, _lv_draw_sw_blend_fill_dsc_t *dsc); + +// ------------------------------------------------ Test cases --------------------------------------------------------- + +/* +Benchmark tests + +Requires: + - To pass functionality tests first + +Purpose: + - Test that an acceleration is achieved by an assembly implementation of LVGL blending API + +Procedure: + - Initialize input parameters (test array length, width, allocate array...) of the benchmark test + - Run assembly version of LVGL blending API multiple times (1000-times or so) + - Firstly use an input test parameters for the most ideal case (16-byte aligned array, array width and height divisible by 4 for ARGB8888 color format) + - Then use worst-case input test parameters (1-byte aligned array, array width and height NOT divisible by 4 for ARGB8888 color format) + - Count how many CPU cycles does it take to run a function from the LVGL blending API for each case (ideal and worst case) + - Run ansi version of LVGL blending API multiple times (1000-times or so) and repeat the 2 above steps for the ansi version + - Free test arrays and structures needed for LVGL blending API +*/ +// ------------------------------------------------ Test cases stages -------------------------------------------------- + +TEST_CASE("LV Fill benchmark ARGB8888", "[fill][benchmark][ARGB8888]") +{ + uint32_t *dest_array_align16 = (uint32_t *)memalign(16, STRIDE * HEIGHT * sizeof(uint32_t) + UNALIGN_BYTES); + TEST_ASSERT_NOT_EQUAL(NULL, dest_array_align16); + + // Apply byte unalignment for the worst-case test scenario + uint32_t *dest_array_align1 = dest_array_align16 + UNALIGN_BYTES; + + bench_test_case_params_t test_params = { + .height = HEIGHT, + .width = WIDTH, + .stride = STRIDE * sizeof(uint32_t), + .cc_height = HEIGHT - 1, + .cc_width = WIDTH - 1, + .benchmark_cycles = BENCHMARK_CYCLES, + .array_align16 = (void *)dest_array_align16, + .array_align1 = (void *)dest_array_align1, + .blend_api_func = &lv_draw_sw_blend_color_to_argb8888, + }; + + ESP_LOGI(TAG_LV_FILL_BENCH, "running test for ARGB8888 color format"); + lv_fill_benchmark_init(&test_params); + free(dest_array_align16); +} + +TEST_CASE("LV Fill benchmark RGB565", "[fill][benchmark][RGB565]") +{ + uint16_t *dest_array_align16 = (uint16_t *)memalign(16, STRIDE * HEIGHT * sizeof(uint16_t) + UNALIGN_BYTES); + TEST_ASSERT_NOT_EQUAL(NULL, dest_array_align16); + + // Apply byte unalignment for the worst-case test scenario + uint16_t *dest_array_align1 = dest_array_align16 + UNALIGN_BYTES; + + bench_test_case_params_t test_params = { + .height = HEIGHT, + .width = WIDTH, + .stride = STRIDE * sizeof(uint16_t), + .cc_height = HEIGHT - 1, + .cc_width = WIDTH - 1, + .benchmark_cycles = BENCHMARK_CYCLES, + .array_align16 = (void *)dest_array_align16, + .array_align1 = (void *)dest_array_align1, + .blend_api_func = &lv_draw_sw_blend_color_to_rgb565, + }; + + ESP_LOGI(TAG_LV_FILL_BENCH, "running test for RGB565 color format"); + lv_fill_benchmark_init(&test_params); + free(dest_array_align16); +} + +TEST_CASE("LV Fill benchmark RGB888", "[fill][benchmark][RGB888]") +{ + uint8_t *dest_array_align16 = (uint8_t *)memalign(16, STRIDE * HEIGHT * sizeof(uint8_t) * 3 + UNALIGN_BYTES); + TEST_ASSERT_NOT_EQUAL(NULL, dest_array_align16); + + // Apply byte unalignment for the worst-case test scenario + uint8_t *dest_array_align1 = dest_array_align16 + UNALIGN_BYTES; + + bench_test_case_params_t test_params = { + .height = HEIGHT, + .width = WIDTH, + .stride = STRIDE * 3, + .cc_height = HEIGHT - 1, + .cc_width = WIDTH - 1, + .benchmark_cycles = BENCHMARK_CYCLES, + .array_align16 = (void *)dest_array_align16, + .array_align1 = (void *)dest_array_align1, + .blend_api_px_func = &lv_draw_sw_blend_color_to_rgb888, + }; + + ESP_LOGI(TAG_LV_FILL_BENCH, "running test for RGB888 color format"); + lv_fill_benchmark_init(&test_params); + free(dest_array_align16); +} +// ------------------------------------------------ Static test functions ---------------------------------------------- + +static void lv_fill_benchmark_init(bench_test_case_params_t *test_params) +{ + // Init structure for LVGL blend API, to call the Assembly API + _lv_draw_sw_blend_fill_dsc_t dsc = { + .dest_buf = test_params->array_align16, + .dest_w = test_params->width, + .dest_h = test_params->height, + .dest_stride = test_params->stride, // stride * sizeof() + .mask_buf = NULL, + .color = test_color, + .opa = LV_OPA_MAX, + .use_asm = true, + }; + + // Init structure for LVGL blend API, to call the ANSI API + _lv_draw_sw_blend_fill_dsc_t dsc_cc = dsc; + dsc_cc.dest_buf = test_params->array_align1; + dsc_cc.dest_w = test_params->cc_width; + dsc_cc.dest_h = test_params->cc_height; + + // Run benchmark 2 times: + // First run using assembly, second run using ANSI + for (int i = 0; i < 2; i++) { + + // Run benchmark with the most ideal input parameters + // Dest array is 16 byte aligned, dest_w and dest_h are dividable by 4 + float cycles = lv_fill_benchmark_run(test_params, &dsc); // Call Benchmark cycle + float per_sample = cycles / ((float)(dsc.dest_w * dsc.dest_h)); + ESP_LOGI(TAG_LV_FILL_BENCH, " %s ideal case: %.3f cycles for %"PRIi32"x%"PRIi32" matrix, %.3f cycles per sample", + asm_ansi_func[i], cycles, dsc.dest_w, dsc.dest_h, per_sample); + + // Run benchmark with the corner case input parameters + // Dest array is 1 byte aligned, dest_w and dest_h are not dividable by 4 + cycles = lv_fill_benchmark_run(test_params, &dsc_cc); // Call Benchmark cycle + per_sample = cycles / ((float)(dsc_cc.dest_w * dsc_cc.dest_h)); + ESP_LOGI(TAG_LV_FILL_BENCH, " %s corner case: %.3f cycles for %"PRIi32"x%"PRIi32" matrix, %.3f cycles per sample\n", + asm_ansi_func[i], cycles, dsc_cc.dest_w, dsc_cc.dest_h, per_sample); + + // change to ANSI + dsc.use_asm = false; + dsc_cc.use_asm = false; + } +} + +static float lv_fill_benchmark_run(bench_test_case_params_t *test_params, _lv_draw_sw_blend_fill_dsc_t *dsc) +{ + // Call the DUT function for the first time to init the benchmark test + if (test_params->blend_api_func != NULL) { + test_params->blend_api_func(dsc); + } else if (test_params->blend_api_px_func != NULL) { + test_params->blend_api_px_func(dsc, 3); + } + + const unsigned int start_b = xthal_get_ccount(); + if (test_params->blend_api_func != NULL) { + for (int i = 0; i < test_params->benchmark_cycles; i++) { + test_params->blend_api_func(dsc); + } + } else if (test_params->blend_api_px_func != NULL) { + for (int i = 0; i < test_params->benchmark_cycles; i++) { + test_params->blend_api_px_func(dsc, 3); + } + } + const unsigned int end_b = xthal_get_ccount(); + + const float total_b = end_b - start_b; + const float cycles = total_b / (test_params->benchmark_cycles); + return cycles; +} diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_fill_functionality.c b/components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_fill_functionality.c new file mode 100644 index 00000000..e6c4d9f9 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_fill_functionality.c @@ -0,0 +1,401 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "unity.h" +#include "esp_log.h" +#include "lv_fill_common.h" +#include "lv_draw_sw_blend.h" +#include "lv_draw_sw_blend_to_argb8888.h" +#include "lv_draw_sw_blend_to_rgb565.h" +#include "lv_draw_sw_blend_to_rgb888.h" + +// ------------------------------------------------- Defines ----------------------------------------------------------- + +#define DBG_PRINT_OUTPUT false +#define CANARY_BYTES 4 + +// ------------------------------------------------- Macros and Types -------------------------------------------------- + +#define UPDATE_TEST_CASE(test_case_ptr, dest_w, dest_h, dest_stride, unalign_byte) ({ \ + (test_case_ptr)->active_buf_len = (size_t)(dest_h * dest_stride); \ + (test_case_ptr)->total_buf_len = (size_t)((dest_h * dest_stride) + (CANARY_BYTES * 2)); \ + (test_case_ptr)->dest_w = (dest_w); \ + (test_case_ptr)->dest_h = (dest_h); \ + (test_case_ptr)->dest_stride = (dest_stride); \ + (test_case_ptr)->unalign_byte = (unalign_byte); \ +}) + +static const char *TAG_LV_FILL_FUNC = "LV Fill Functionality"; +static char test_msg_buf[128]; + +static lv_color_t test_color = { + .blue = 0x56, + .green = 0x34, + .red = 0x12, +}; + +// ------------------------------------------------ Static function headers -------------------------------------------- + +/** + * @brief Generate all the functionality test combinations + * + * - generate functionality test combinations, based on the provided test_matrix struct + * + * @param[in] test_matrix Pointer to structure defining test matrix - all the test combinations + * @param[in] test_case Pointer to structure defining functionality test case + */ +static void functionality_test_matrix(test_matrix_params_t *test_matrix, func_test_case_params_t *test_case); + +/** + * @brief Fill test buffers for functionality test + * + * @param[in] test_case Pointer to structure defining functionality test case + */ +static void fill_test_bufs(func_test_case_params_t *test_case); + +/** + * @brief The actual functionality test + * + * - function prepares structures for functionality testing and runs the LVGL API + * + * @param[in] test_case Pointer to structure defining functionality test case + */ +static void lv_fill_functionality(func_test_case_params_t *test_case); + +/** + * @brief Evaluate results for 32bit data length + * + * @param[in] test_case Pointer to structure defining functionality test case + */ +static void test_eval_32bit_data(func_test_case_params_t *test_case); + +/** + * @brief Evaluate results for 16bit data length + * + * @param[in] test_case Pointer to structure defining functionality test case + */ +static void test_eval_16bit_data(func_test_case_params_t *test_case); + +/** + * @brief Evaluate results for 24bit data length + * + * @param[in] test_case Pointer to structure defining functionality test case + */ +static void test_eval_24bit_data(func_test_case_params_t *test_case); + +// ------------------------------------------------ Test cases --------------------------------------------------------- + +/* +Functionality tests + +Purpose: + - Test that an assembly version of LVGL blending API achieves the same results as the ANSI version + +Procedure: + - Prepare testing matrix, to cover all the possible combinations of destination array widths, lengths, memory alignment... + - Run assembly version of the LVGL blending API + - Run ANSI C version of the LVGL blending API + - Compare the results + - Repeat above 3 steps for each test matrix setup +*/ + +// ------------------------------------------------ Test cases stages -------------------------------------------------- + +TEST_CASE("Test fill functionality ARGB8888", "[fill][functionality][ARGB8888]") +{ + test_matrix_params_t test_matrix = { + .min_w = 8, // 8 is the lower limit for the esp32s3 asm implementation, otherwise esp32 is executed + .min_h = 1, + .max_w = 16, + .max_h = 16, + .min_unalign_byte = 0, + .max_unalign_byte = 16, + .unalign_step = 1, + .dest_stride_step = 1, + .test_combinations_count = 0, + }; + + func_test_case_params_t test_case = { + .blend_api_func = &lv_draw_sw_blend_color_to_argb8888, + .color_format = LV_COLOR_FORMAT_ARGB8888, + .data_type_size = sizeof(uint32_t), + }; + + ESP_LOGI(TAG_LV_FILL_FUNC, "running test for ARGB8888 color format"); + functionality_test_matrix(&test_matrix, &test_case); +} + +TEST_CASE("Test fill functionality RGB565", "[fill][functionality][RGB565]") +{ + test_matrix_params_t test_matrix = { + .min_w = 16, // 16 is the lower limit for the esp32s3 asm implementation, otherwise esp32 is executed + .min_h = 1, + .max_w = 32, + .max_h = 16, + .min_unalign_byte = 0, + .max_unalign_byte = 16, + .unalign_step = 1, + .dest_stride_step = 1, + .test_combinations_count = 0, + }; + + func_test_case_params_t test_case = { + .blend_api_func = &lv_draw_sw_blend_color_to_rgb565, + .color_format = LV_COLOR_FORMAT_RGB565, + .data_type_size = sizeof(uint16_t), + }; + + ESP_LOGI(TAG_LV_FILL_FUNC, "running test for RGB565 color format"); + functionality_test_matrix(&test_matrix, &test_case); +} + +TEST_CASE("Test fill functionality RGB888", "[fill][functionality][RGB888]") +{ + test_matrix_params_t test_matrix = { + .min_w = 12, // 12 is the lower limit for the esp32s3 asm implementation, otherwise esp32 is executed + .min_h = 1, + .max_w = 32, + .max_h = 3, + .min_unalign_byte = 0, + .max_unalign_byte = 16, + .unalign_step = 1, + .dest_stride_step = 1, + .test_combinations_count = 0, + }; + + func_test_case_params_t test_case = { + .blend_api_px_func = &lv_draw_sw_blend_color_to_rgb888, + .color_format = LV_COLOR_FORMAT_RGB888, + .data_type_size = sizeof(uint8_t) * 3, // 24-bit data length + }; + + ESP_LOGI(TAG_LV_FILL_FUNC, "running test for RGB888 color format"); + functionality_test_matrix(&test_matrix, &test_case); +} +// ------------------------------------------------ Static test functions ---------------------------------------------- + +static void functionality_test_matrix(test_matrix_params_t *test_matrix, func_test_case_params_t *test_case) +{ + // Step destination array width + for (int dest_w = test_matrix->min_w; dest_w <= test_matrix->max_w; dest_w++) { + + // Step destination array height + for (int dest_h = test_matrix->min_h; dest_h <= test_matrix->max_h; dest_h++) { + + // Step destination array stride + for (int dest_stride = dest_w; dest_stride <= dest_w * 2; dest_stride += test_matrix->dest_stride_step) { + + // Step destination array unalignment + for (int unalign_byte = test_matrix->min_unalign_byte; unalign_byte <= test_matrix->max_unalign_byte; + unalign_byte += test_matrix->unalign_step) { + + // Call functionality test + UPDATE_TEST_CASE(test_case, dest_w, dest_h, dest_stride, unalign_byte); + lv_fill_functionality(test_case); + test_matrix->test_combinations_count++; + } + } + } + } + ESP_LOGI(TAG_LV_FILL_FUNC, "test combinations: %d\n", test_matrix->test_combinations_count); +} + +static void lv_fill_functionality(func_test_case_params_t *test_case) +{ + fill_test_bufs(test_case); + + // Init structure for LVGL blend API, to call the Assembly API + _lv_draw_sw_blend_fill_dsc_t dsc_asm = { + .dest_buf = test_case->buf.p_asm, + .dest_w = test_case->dest_w, + .dest_h = test_case->dest_h, + .dest_stride = test_case->dest_stride * test_case->data_type_size, // stride * sizeof() + .mask_buf = NULL, + .color = test_color, + .opa = LV_OPA_MAX, + .use_asm = true, + }; + + // Init structure for LVGL blend API, to call the ANSI API + _lv_draw_sw_blend_fill_dsc_t dsc_ansi = dsc_asm; + dsc_ansi.dest_buf = test_case->buf.p_ansi; + dsc_ansi.use_asm = false; + + if (test_case->blend_api_func != NULL) { + test_case->blend_api_func(&dsc_asm); // Call the LVGL API with Assembly code + test_case->blend_api_func(&dsc_ansi); // Call the LVGL API with ANSI code + } else if (test_case->blend_api_px_func != NULL) { + test_case->blend_api_px_func(&dsc_asm, 3); // Call the LVGL API with Assembly code with set pixel size + test_case->blend_api_px_func(&dsc_ansi, 3); // Call the LVGL API with ANSI code with set pixel size + } + + // Shift array pointers by Canary Bytes amount back + test_case->buf.p_asm -= CANARY_BYTES * test_case->data_type_size; + test_case->buf.p_ansi -= CANARY_BYTES * test_case->data_type_size; + + // Evaluate the results + sprintf(test_msg_buf, "Test case: dest_w = %d, dest_h = %d, dest_stride = %d, unalign_byte = %d\n", test_case->dest_w, + test_case->dest_h, test_case->dest_stride, test_case->unalign_byte); + + switch (test_case->color_format) { + case LV_COLOR_FORMAT_ARGB8888: { + test_eval_32bit_data(test_case); + break; + } + + case LV_COLOR_FORMAT_RGB565: { + test_eval_16bit_data(test_case); + break; + } + + case LV_COLOR_FORMAT_RGB888: { + test_eval_24bit_data(test_case); + break; + } + + default: + TEST_ASSERT_MESSAGE(false, "LV Color format not found"); + } + + free(test_case->buf.p_asm_alloc); + free(test_case->buf.p_ansi_alloc); + +} + +static void fill_test_bufs(func_test_case_params_t *test_case) +{ + const size_t data_type_size = test_case->data_type_size; // sizeof() of used data type + const size_t total_buf_len = + test_case->total_buf_len; // Total buffer length, data part of the buffer including the Canary bytes + const size_t active_buf_len = test_case->active_buf_len; // Length of buffer + const unsigned int unalign_byte = test_case->unalign_byte; + + // Allocate destination arrays for Assembly and ANSI LVGL Blend API + void *mem_asm = memalign(16, (total_buf_len * data_type_size) + unalign_byte); + void *mem_ansi = memalign(16, (total_buf_len * data_type_size) + unalign_byte); + TEST_ASSERT_NOT_NULL_MESSAGE(mem_asm, "Lack of memory"); + TEST_ASSERT_NOT_NULL_MESSAGE(mem_ansi, "Lack of memory"); + + // Save a pointer to the beginning of the allocated memory which will be used to free() + test_case->buf.p_asm_alloc = mem_asm; + test_case->buf.p_ansi_alloc = mem_ansi; + + // Apply destination array unalignment + uint8_t *dest_buf_asm = (uint8_t *)mem_asm + unalign_byte; + uint8_t *dest_buf_ansi = (uint8_t *)mem_ansi + unalign_byte; + + // Set the whole buffer to 0, including the Canary bytes part + memset(dest_buf_asm, 0, total_buf_len * data_type_size); + memset(dest_buf_ansi, 0, total_buf_len * data_type_size); + + // Fill the actual part of the destination buffers with known values, + // Values must be same, because of the stride + for (int i = CANARY_BYTES; i < active_buf_len + CANARY_BYTES; i++) { + dest_buf_asm[i * data_type_size] = (uint8_t)(i % 255); + dest_buf_ansi[i * data_type_size] = (uint8_t)(i % 255); + } + + // Shift array pointers by Canary Bytes amount + dest_buf_asm += CANARY_BYTES * data_type_size; + dest_buf_ansi += CANARY_BYTES * data_type_size; + + // Save a pointer to the working part of the memory, where the test data are stored + test_case->buf.p_asm = (void *)dest_buf_asm; + test_case->buf.p_ansi = (void *)dest_buf_ansi; +} + +static void test_eval_32bit_data(func_test_case_params_t *test_case) +{ + // Print results 32bit data +#if DBG_PRINT_OUTPUT + for (uint32_t i = 0; i < test_case->total_buf_len; i++) { + printf("dest_buf[%"PRIi32"] %s ansi = %8"PRIx32" \t asm = %8"PRIx32" \n", i, ((i < 10) ? (" ") : ("")), + ((uint32_t *)test_case->buf.p_ansi)[i], ((uint32_t *)test_case->buf.p_asm)[i]); + } + printf("\n"); +#endif + + // Canary bytes area must stay 0 + TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(0, (uint32_t *)test_case->buf.p_ansi, CANARY_BYTES, test_msg_buf); + TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(0, (uint32_t *)test_case->buf.p_asm, CANARY_BYTES, test_msg_buf); + + // dest_buf_asm and dest_buf_ansi must be equal + TEST_ASSERT_EQUAL_UINT32_ARRAY_MESSAGE((uint32_t *)test_case->buf.p_asm + CANARY_BYTES, + (uint32_t *)test_case->buf.p_ansi + CANARY_BYTES, test_case->active_buf_len, test_msg_buf); + + // Canary bytes area must stay 0 + TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(0, (uint32_t *)test_case->buf.p_ansi + (test_case->total_buf_len - CANARY_BYTES), + CANARY_BYTES, test_msg_buf); + TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(0, (uint32_t *)test_case->buf.p_asm + (test_case->total_buf_len - CANARY_BYTES), + CANARY_BYTES, test_msg_buf); +} + +static void test_eval_16bit_data(func_test_case_params_t *test_case) +{ + // Print results, 16bit data +#if DBG_PRINT_OUTPUT + for (uint32_t i = 0; i < test_case->total_buf_len; i++) { + printf("dest_buf[%"PRIi32"] %s ansi = %8"PRIx16" \t asm = %8"PRIx16" \n", i, ((i < 10) ? (" ") : ("")), + ((uint16_t *)test_case->buf.p_ansi)[i], ((uint16_t *)test_case->buf.p_asm)[i]); + } + printf("\n"); +#endif + + // Canary bytes area must stay 0 + TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, (uint16_t *)test_case->buf.p_ansi, CANARY_BYTES, test_msg_buf); + TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, (uint16_t *)test_case->buf.p_asm, CANARY_BYTES, test_msg_buf); + + // dest_buf_asm and dest_buf_ansi must be equal + TEST_ASSERT_EQUAL_UINT16_ARRAY_MESSAGE((uint16_t *)test_case->buf.p_asm + CANARY_BYTES, + (uint16_t *)test_case->buf.p_ansi + CANARY_BYTES, test_case->active_buf_len, test_msg_buf); + + // Canary bytes area must stay 0 + TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, (uint16_t *)test_case->buf.p_ansi + (test_case->total_buf_len - CANARY_BYTES), + CANARY_BYTES, test_msg_buf); + TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, (uint16_t *)test_case->buf.p_asm + (test_case->total_buf_len - CANARY_BYTES), + CANARY_BYTES, test_msg_buf); +} + +static void test_eval_24bit_data(func_test_case_params_t *test_case) +{ + // Print results, 24bit data +#if DBG_PRINT_OUTPUT + size_t data_type_size = test_case->data_type_size; + for (uint32_t i = 0; i < test_case->total_buf_len; i++) { + uint32_t ansi_value = ((uint8_t *)test_case->buf.p_ansi)[i * data_type_size] + | (((uint8_t *)test_case->buf.p_ansi)[i * data_type_size + 1] << 8) + | (((uint8_t *)test_case->buf.p_ansi)[i * data_type_size + 2] << 16); + uint32_t asm_value = ((uint8_t *)test_case->buf.p_asm)[i * data_type_size] + | (((uint8_t *)test_case->buf.p_asm)[i * data_type_size + 1] << 8) + | (((uint8_t *)test_case->buf.p_asm)[i * data_type_size + 2] << 16); + printf("dest_buf[%"PRIi32"] %s ansi = %8"PRIx32" \t asm = %8"PRIx32" \n", i, ((i < 10) ? (" ") : ("")), ansi_value, + asm_value); + } + printf("\n"); +#endif + + const int canary_bytes_area = CANARY_BYTES * test_case->data_type_size; + + // Canary bytes area must stay 0 + TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, (uint8_t *)test_case->buf.p_ansi, canary_bytes_area, test_msg_buf); + TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, (uint8_t *)test_case->buf.p_asm, canary_bytes_area, test_msg_buf); + + // dest_buf_asm and dest_buf_ansi must be equal + TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE((uint8_t *)test_case->buf.p_asm + canary_bytes_area, + (uint8_t *)test_case->buf.p_ansi + canary_bytes_area, test_case->active_buf_len * test_case->data_type_size, + test_msg_buf); + + // Canary bytes area must stay 0 + TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, + (uint8_t *)test_case->buf.p_ansi + (test_case->total_buf_len - CANARY_BYTES) * test_case->data_type_size, + canary_bytes_area, test_msg_buf); + TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, + (uint8_t *)test_case->buf.p_asm + (test_case->total_buf_len - CANARY_BYTES) * test_case->data_type_size, + canary_bytes_area, test_msg_buf); +} diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_image_benchmark.c b/components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_image_benchmark.c new file mode 100644 index 00000000..66b731f5 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_image_benchmark.c @@ -0,0 +1,225 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "unity.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" // for xthal_get_ccount() +#include "lv_image_common.h" +#include "lv_draw_sw_blend.h" +#include "lv_draw_sw_blend_to_rgb565.h" +#include "lv_draw_sw_blend_to_rgb888.h" + +#define COMMON_DIM 128 // Common matrix dimension 128x128 pixels +#define WIDTH COMMON_DIM +#define HEIGHT COMMON_DIM +#define STRIDE WIDTH +#define UNALIGN_BYTES 3 +#define BENCHMARK_CYCLES 1000 + +// ------------------------------------------------ Static variables --------------------------------------------------- + +static const char *TAG_LV_IMAGE_BENCH = "LV Image Benchmark"; +static const char *asm_ansi_func[] = {"ASM", "ANSI"}; + +// ------------------------------------------------ Static function headers -------------------------------------------- + +/** + * @brief Initialize the benchmark test + */ +static void lv_image_benchmark_init(bench_test_case_lv_image_params_t *test_params); + +/** + * @brief Run the benchmark test + */ +static float lv_image_benchmark_run(bench_test_case_lv_image_params_t *test_params, _lv_draw_sw_blend_image_dsc_t *dsc); + +// ------------------------------------------------ Test cases --------------------------------------------------------- + +/* +Benchmark tests + +Requires: + - To pass functionality tests first + +Purpose: + - Test that an acceleration is achieved by an assembly implementation of LVGL blending API + +Procedure: + - Initialize input parameters (test array length, width, allocate array...) of the benchmark test + - Run assembly version of LVGL blending API multiple times (1000-times or so) + - Firstly use an input test parameters for the most ideal case (16-byte aligned arrays, arrays widths divisible by 2 for RGB565 color format) + - Then use worst-case input test parameters (1-byte aligned arrays, arrays width NOT divisible by 2 for RGB565 color format) + - Count how many CPU cycles does it take to run a function from the LVGL blending API for each case (ideal and worst case) + - Run ansi version of LVGL blending API multiple times (1000-times or so) and repeat the 2 above steps for the ansi version + - Compare the results + - Free test arrays and structures needed for LVGL blending API + +Inducing Most ideal and worst case scenarios: + - Most ideal: + - Both, the source and the destination buffers should be aligned by 16-byte (Xtensa PIE), or 4-byte (Xtensa base) boundaries + - Matrix width (in pixels) should be equal to the main loop length in the assembly src code + typically multiples of 16 bytes (for RGB565 it's either 32 bytes - 16 pixels or 48 bytes - 24 pixels) + - Matrix height does not have any effect on benchmark unit tests, unit the matrix is too large that cache limitations start to affect the performance + - Matrix strides, should be equal to the matrix widths (0 matrix padding), or their multiples (matrix width = matrix padding) + - Worst case: + - Both, hte source and the destination buffers should NOT be aligned by 16-byte (Xtensa PIE), or 4-byte (Xtensa base) boundaries, + Source buffer unalignment should be different from the destination unalignment, with one unalignment being even, the other being odd + The unalignments shall be small numbers (preferably 1 or 2 bytes) + - Matrix width should be one pixels smaller, than the matrix width for the most ideal case + - Matrix height does not have any effect on benchmark unit tests, unit the matrix is too large that cache limitations start to affect the performance + - Matrix strides, should NOT be equal to the matrix widths (non 0 matrix padding) +*/ +// ------------------------------------------------ Test cases stages -------------------------------------------------- + +TEST_CASE("LV Image benchmark RGB565 blend to RGB565", "[image][benchmark][RGB565]") +{ + uint16_t *dest_array_align16 = (uint16_t *)memalign(16, STRIDE * HEIGHT * sizeof(uint16_t) + UNALIGN_BYTES); + uint16_t *src_array_align16 = (uint16_t *)memalign(16, STRIDE * HEIGHT * sizeof(uint16_t) + UNALIGN_BYTES); + TEST_ASSERT_NOT_EQUAL_MESSAGE(NULL, dest_array_align16, "Lack of memory"); + TEST_ASSERT_NOT_EQUAL_MESSAGE(NULL, src_array_align16, "Lack of memory"); + + // Apply byte unalignment (different for each array) for the worst-case test scenario + uint16_t *dest_array_align1 = (uint16_t *)((uint8_t *)dest_array_align16 + UNALIGN_BYTES - 1); + uint16_t *src_array_align1 = (uint16_t *)((uint8_t *)src_array_align16 + UNALIGN_BYTES); + + bench_test_case_lv_image_params_t test_params = { + .height = HEIGHT, + .width = WIDTH, + .dest_stride = STRIDE * sizeof(uint16_t), + .src_stride = STRIDE * sizeof(uint16_t), + .cc_height = HEIGHT, + .cc_width = WIDTH - 1, + .benchmark_cycles = BENCHMARK_CYCLES, + .src_array_align16 = (void *)src_array_align16, + .src_array_align1 = (void *)src_array_align1, + .dest_array_align16 = (void *)dest_array_align16, + .dest_array_align1 = (void *)dest_array_align1, + .blend_api_func = &lv_draw_sw_blend_image_to_rgb565, + .color_format = LV_COLOR_FORMAT_RGB565, + }; + + ESP_LOGI(TAG_LV_IMAGE_BENCH, "running test for RGB565 color format"); + lv_image_benchmark_init(&test_params); + free(dest_array_align16); + free(src_array_align16); +} + +TEST_CASE("LV Image benchmark RGB888 blend to RGB888", "[image][benchmark][RGB888]") +{ + uint8_t *dest_array_align16 = (uint8_t *)memalign(16, (STRIDE * HEIGHT * sizeof(uint8_t) * 3) + UNALIGN_BYTES); + uint8_t *src_array_align16 = (uint8_t *)memalign(16, (STRIDE * HEIGHT * sizeof(uint8_t) * 3) + UNALIGN_BYTES); + TEST_ASSERT_NOT_EQUAL_MESSAGE(NULL, dest_array_align16, "Lack of memory"); + TEST_ASSERT_NOT_EQUAL_MESSAGE(NULL, src_array_align16, "Lack of memory"); + + // Apply byte unalignment (different for each array) for the worst-case test scenario + uint8_t *dest_array_align1 = dest_array_align16 + UNALIGN_BYTES - 1; + uint8_t *src_array_align1 = src_array_align16 + UNALIGN_BYTES; + + bench_test_case_lv_image_params_t test_params = { + .height = HEIGHT, + .width = WIDTH, + .dest_stride = STRIDE * sizeof(uint8_t) * 3, + .src_stride = STRIDE * sizeof(uint8_t) * 3, + .cc_height = HEIGHT, + .cc_width = WIDTH - 1, + .benchmark_cycles = BENCHMARK_CYCLES, + .src_array_align16 = (void *)src_array_align16, + .src_array_align1 = (void *)src_array_align1, + .dest_array_align16 = (void *)dest_array_align16, + .dest_array_align1 = (void *)dest_array_align1, + .blend_api_func_px_size = &lv_draw_sw_blend_image_to_rgb888, + .color_format = LV_COLOR_FORMAT_RGB888, + }; + + ESP_LOGI(TAG_LV_IMAGE_BENCH, "running test for RGB888 color format"); + lv_image_benchmark_init(&test_params); + free(dest_array_align16); + free(src_array_align16); +} +// ------------------------------------------------ Static test functions ---------------------------------------------- + +static void lv_image_benchmark_init(bench_test_case_lv_image_params_t *test_params) +{ + // Init structure for LVGL blend API, to call the Assembly API + _lv_draw_sw_blend_image_dsc_t dsc = { + .dest_buf = test_params->dest_array_align16, + .dest_w = test_params->width, + .dest_h = test_params->height, + .dest_stride = test_params->dest_stride, // stride * sizeof() + .mask_buf = NULL, + .src_buf = test_params->src_array_align16, + .src_stride = test_params->src_stride, + .src_color_format = test_params->color_format, + .opa = LV_OPA_MAX, + .blend_mode = LV_BLEND_MODE_NORMAL, + .use_asm = true, + }; + + // Init structure for LVGL blend API, to call the ANSI API + _lv_draw_sw_blend_image_dsc_t dsc_cc = dsc; + dsc_cc.dest_buf = test_params->dest_array_align1; + dsc_cc.dest_w = test_params->cc_width; + dsc_cc.dest_h = test_params->cc_height; + dsc_cc.src_buf = test_params->src_array_align1; + + // Run benchmark 2 times: + // First run using assembly, second run using ANSI + for (int i = 0; i < 2; i++) { + + // Run benchmark with the most ideal input parameters + float cycles = lv_image_benchmark_run(test_params, &dsc); // Call Benchmark cycle + float per_sample = cycles / ((float)(dsc.dest_w * dsc.dest_h)); + ESP_LOGI(TAG_LV_IMAGE_BENCH, " %s ideal case: %.3f cycles for %"PRIi32"x%"PRIi32" matrix, %.3f cycles per sample", + asm_ansi_func[i], cycles, dsc.dest_w, dsc.dest_h, per_sample); + + // Run benchmark with the corner case input parameters + cycles = lv_image_benchmark_run(test_params, &dsc_cc); // Call Benchmark cycle + per_sample = cycles / ((float)(dsc_cc.dest_w * dsc_cc.dest_h)); + ESP_LOGI(TAG_LV_IMAGE_BENCH, " %s corner case: %.3f cycles for %"PRIi32"x%"PRIi32" matrix, %.3f cycles per sample\n", + asm_ansi_func[i], cycles, dsc_cc.dest_w, dsc_cc.dest_h, per_sample); + + // change to ANSI + dsc.use_asm = false; + dsc_cc.use_asm = false; + } +} + +static float lv_image_benchmark_run(bench_test_case_lv_image_params_t *test_params, _lv_draw_sw_blend_image_dsc_t *dsc) +{ + // Call the DUT function for the first time to init the benchmark test + if (test_params->blend_api_func != NULL) { + test_params->blend_api_func(dsc); // Call the LVGL API + } else if (test_params->blend_api_func_px_size != NULL) { + test_params->blend_api_func_px_size(dsc, 3); // Call the LVGL API with set pixel size + } else { + TEST_ASSERT_MESSAGE(false, "Not supported: Both API pointers can't be NULL"); + } + + + // Run the benchmark + const unsigned int start_b = xthal_get_ccount(); + if (test_params->blend_api_func != NULL) { + + for (int i = 0; i < test_params->benchmark_cycles; i++) { + test_params->blend_api_func(dsc); + } + + } else if (test_params->blend_api_func_px_size != NULL) { + + for (int i = 0; i < test_params->benchmark_cycles; i++) { + test_params->blend_api_func_px_size(dsc, 3); + } + } + const unsigned int end_b = xthal_get_ccount(); + + const float total_b = end_b - start_b; + const float cycles = total_b / (test_params->benchmark_cycles); + return cycles; +} diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_image_functionality.c b/components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_image_functionality.c new file mode 100644 index 00000000..64973b81 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/main/test_lv_image_functionality.c @@ -0,0 +1,482 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "sdkconfig.h" +#include "unity.h" +#include "esp_log.h" +#include "lv_image_common.h" +#include "lv_draw_sw_blend.h" +#include "lv_draw_sw_blend_to_rgb565.h" +#include "lv_draw_sw_blend_to_rgb888.h" + +// ------------------------------------------------- Defines ----------------------------------------------------------- + +#define DBG_PRINT_OUTPUT false + +// ------------------------------------------------- Macros and Types -------------------------------------------------- + +#define UPDATE_TEST_CASE(test_case_ptr, dest_w, dest_h, src_stride, dest_stride, src_unalign_byte, dest_unalign_byte) ({ \ + (test_case_ptr)->src_buf_len = (size_t)(dest_h * src_stride); \ + (test_case_ptr)->active_dest_buf_len = (size_t)(dest_h * dest_stride); \ + (test_case_ptr)->total_dest_buf_len = (size_t)((dest_h * dest_stride) + (test_case_ptr->canary_pixels * 2)); \ + (test_case_ptr)->dest_w = (dest_w); \ + (test_case_ptr)->dest_h = (dest_h); \ + (test_case_ptr)->src_stride = (src_stride); \ + (test_case_ptr)->dest_stride = (dest_stride); \ + (test_case_ptr)->src_unalign_byte = (src_unalign_byte); \ + (test_case_ptr)->dest_unalign_byte = (dest_unalign_byte); \ +}) + +// ------------------------------------------------ Static variables --------------------------------------------------- + +static const char *TAG_LV_IMAGE_FUNC = "LV Image Functionality"; +static char test_msg_buf[200]; + +static const test_matrix_lv_image_params_t default_test_matrix_image_blend = { +#if CONFIG_IDF_TARGET_ESP32S3 + .min_w = 8, // 8 is the lower limit for the esp32s3 asm implementation, otherwise esp32 is executed + .min_h = 1, + .max_w = 24, + .max_h = 2, + .src_max_unalign_byte = 16, // Use 16-byte boundary check for Xtensa PIE + .dest_max_unalign_byte = 16, + .dest_unalign_step = 1, // Step 1 as the destination array is being aligned in the assembly code all the time + .src_unalign_step = 3, // Step 3 (more relaxed) as source array is used unaligned in the assembly code + .src_stride_step = 3, + .dest_stride_step = 3, +#else + .min_w = 1, + .min_h = 1, + .max_w = 16, + .max_h = 2, + .src_max_unalign_byte = 4, // Use 4-byte boundary check for Xtensa base + .dest_max_unalign_byte = 4, + .dest_unalign_step = 1, + .src_unalign_step = 1, + .src_stride_step = 1, + .dest_stride_step = 1, +#endif + .src_min_unalign_byte = 0, + .dest_min_unalign_byte = 0, + .test_combinations_count = 0, +}; + +// ------------------------------------------------ Static function headers -------------------------------------------- + +/** + * @brief Generate all the functionality test combinations + * + * - generate functionality test combinations, based on the provided test_matrix struct + * + * @param[in] test_matrix Pointer to structure defining test matrix - all the test combinations + * @param[in] test_case Pointer ot structure defining functionality test case + */ +static void functionality_test_matrix(test_matrix_lv_image_params_t *test_matrix, + func_test_case_lv_image_params_t *test_case); + +/** + * @brief Fill test buffers for image functionality test + * + * @param[in] test_case Pointer ot structure defining functionality test case + */ +static void fill_test_bufs(func_test_case_lv_image_params_t *test_case); + +/** + * @brief The actual functionality test + * + * - function prepares structures for functionality testing and runs the LVGL API + * + * @param[in] test_case Pointer ot structure defining functionality test case + */ +static void lv_image_functionality(func_test_case_lv_image_params_t *test_case); + +/** + * @brief Evaluate results of LV Image functionality for 16bit data length + * + * @param[in] test_case Pointer ot structure defining functionality test case + */ +static void test_eval_image_16bit_data(func_test_case_lv_image_params_t *test_case); + +/** + * @brief Evaluate results of LV Image functionality for 24bit data length + * + * @param[in] test_case Pointer ot structure defining functionality test case + */ +static void test_eval_image_24bit_data(func_test_case_lv_image_params_t *test_case); + +// ------------------------------------------------ Test cases --------------------------------------------------------- + +/* +Functionality tests + +Purpose: + - Test that an assembly version of LVGL blending API achieves the same results as the ANSI version + +Procedure: + - Prepare testing matrix, to cover all the possible combinations of destination and source arrays widths, + lengths, strides and memory alignments + - Run assembly version of the LVGL blending API + - Run ANSI C version of the LVGL blending API + - Compare the results + - Repeat above 3 steps for each test matrix setup +*/ + +// ------------------------------------------------ Test cases stages -------------------------------------------------- + +TEST_CASE("LV Image functionality RGB565 blend to RGB565", "[image][functionality][RGB565]") +{ + test_matrix_lv_image_params_t test_matrix = default_test_matrix_image_blend; + + func_test_case_lv_image_params_t test_case = { + .blend_api_func = &lv_draw_sw_blend_image_to_rgb565, + .color_format = LV_COLOR_FORMAT_RGB565, + .canary_pixels = CANARY_PIXELS_RGB565, + .memory_alignment_offset = 0, + .src_data_type_size = sizeof(uint16_t), + .dest_data_type_size = sizeof(uint16_t), + .operation_type = OPERATION_FILL, + }; + + ESP_LOGI(TAG_LV_IMAGE_FUNC, "running test for RGB565 color format"); + functionality_test_matrix(&test_matrix, &test_case); +} + +TEST_CASE("LV Image functionality RGB888 blend to RGB888", "[image][functionality][RGB888]") +{ + test_matrix_lv_image_params_t test_matrix = default_test_matrix_image_blend; + + func_test_case_lv_image_params_t test_case = { + .blend_api_func_px_size = &lv_draw_sw_blend_image_to_rgb888, // The blending API function takes additional parameter, pixel size + .color_format = LV_COLOR_FORMAT_RGB888, + .canary_pixels = CANARY_PIXELS_RGB888, + .memory_alignment_offset = 32 - (CANARY_PIXELS_RGB888 * 3), // Closes 16-byte boundary (32) - RGB888 canary pixels + .src_data_type_size = sizeof(uint8_t) * 3, + .dest_data_type_size = sizeof(uint8_t) * 3, + .operation_type = OPERATION_FILL, + }; + + ESP_LOGI(TAG_LV_IMAGE_FUNC, "running test for RGB888 color format"); + functionality_test_matrix(&test_matrix, &test_case); +} + +// ------------------------------------------------ Static test functions ---------------------------------------------- + +static void functionality_test_matrix(test_matrix_lv_image_params_t *test_matrix, + func_test_case_lv_image_params_t *test_case) +{ + // Step destination array width + for (int dest_w = test_matrix->min_w; dest_w <= test_matrix->max_w; dest_w++) { + + // Step destination array height + for (int dest_h = test_matrix->min_h; dest_h <= test_matrix->max_h; dest_h++) { + + // Step source array stride + for (int src_stride = dest_w; src_stride <= dest_w * 2; src_stride += test_matrix->src_stride_step) { + + // Step destination array stride + for (int dest_stride = dest_w; dest_stride <= dest_w * 2; dest_stride += test_matrix->dest_stride_step) { + + // Step source array unalignment + for (int src_unalign_byte = test_matrix->src_min_unalign_byte; src_unalign_byte <= test_matrix->src_max_unalign_byte; + src_unalign_byte += test_matrix->src_unalign_step) { + + // Step destination array unalignment + for (int dest_unalign_byte = test_matrix->dest_min_unalign_byte; + dest_unalign_byte <= test_matrix->dest_max_unalign_byte; dest_unalign_byte += test_matrix->dest_unalign_step) { + + // Call functionality test + UPDATE_TEST_CASE(test_case, dest_w, dest_h, src_stride, dest_stride, src_unalign_byte, dest_unalign_byte); + lv_image_functionality(test_case); + test_matrix->test_combinations_count++; + } + } + } + } + } + } + ESP_LOGI(TAG_LV_IMAGE_FUNC, "test combinations: %d\n", test_matrix->test_combinations_count); +} + +static void lv_image_functionality(func_test_case_lv_image_params_t *test_case) +{ + fill_test_bufs(test_case); + + _lv_draw_sw_blend_image_dsc_t dsc_asm = { + .dest_buf = test_case->buf.p_dest_asm, + .dest_w = test_case->dest_w, + .dest_h = test_case->dest_h, + .dest_stride = test_case->dest_stride * test_case->dest_data_type_size, // dest_stride * sizeof(data_type) + .mask_buf = NULL, + .mask_stride = 0, + .src_buf = test_case->buf.p_src, + .src_stride = test_case->src_stride * test_case->src_data_type_size, // src_stride * sizeof(data_type) + .src_color_format = test_case->color_format, + .opa = LV_OPA_MAX, + .blend_mode = LV_BLEND_MODE_NORMAL, + .use_asm = true, + }; + + // Init structure for LVGL blend API, to call the ANSI API + _lv_draw_sw_blend_image_dsc_t dsc_ansi = dsc_asm; + dsc_ansi.dest_buf = test_case->buf.p_dest_ansi; + dsc_ansi.use_asm = false; + + if (test_case->blend_api_func != NULL) { + test_case->blend_api_func(&dsc_asm); // Call the LVGL API with Assembly code + test_case->blend_api_func(&dsc_ansi); // Call the LVGL API with ANSI code + } else if (test_case->blend_api_func_px_size != NULL) { + test_case->blend_api_func_px_size(&dsc_asm, 3); // Call the LVGL API with Assembly code with set pixel size + test_case->blend_api_func_px_size(&dsc_ansi, 3); // Call the LVGL API with ANSI code with set pixel size + } else { + TEST_ASSERT_MESSAGE(false, "Not supported: Both API pointers can't be NULL"); + } + + // Shift array pointers by (Canary pixels amount * data type length) back + test_case->buf.p_dest_asm -= test_case->canary_pixels * test_case->dest_data_type_size; + test_case->buf.p_dest_ansi -= test_case->canary_pixels * test_case->dest_data_type_size; + + // Evaluate the results + sprintf(test_msg_buf, + "Test case: dest_w = %d, dest_h = %d, dest_stride = %d, src_stride = %d, dest_unalign_byte = %d, src_unalign_byte = %d\n", + test_case->dest_w, test_case->dest_h, test_case->dest_stride, test_case->src_stride, test_case->dest_unalign_byte, + test_case->src_unalign_byte); +#if DBG_PRINT_OUTPUT + printf("%s\n", test_msg_buf); +#endif + switch (test_case->color_format) { + case LV_COLOR_FORMAT_RGB565: + test_eval_image_16bit_data(test_case); + break; + case LV_COLOR_FORMAT_RGB888: + test_eval_image_24bit_data(test_case); + break; + default: + TEST_ASSERT_MESSAGE(false, "LV Color format not found"); + break; + } + + // Free memory allocated for test buffers + free(test_case->buf.p_dest_asm_alloc); + free(test_case->buf.p_dest_ansi_alloc); + free(test_case->buf.p_src_alloc); +} + +static void fill_test_bufs(func_test_case_lv_image_params_t *test_case) +{ + const size_t src_data_type_size = + test_case->src_data_type_size; // sizeof() of used data type in the source buffer + const size_t dest_data_type_size = + test_case->dest_data_type_size; // sizeof() of used data type in the destination buffer + const size_t src_buf_len = + test_case->src_buf_len; // Total source buffer length, data part of the source buffer including matrix padding (no Canary pixels are used for source buffer) + const size_t total_dest_buf_len = + test_case->total_dest_buf_len; // Total destination buffer length, data part of the destination buffer including the Canary pixels and matrix padding + const size_t active_dest_buf_len = + test_case->active_dest_buf_len; // Length of the data part of the destination buffer including matrix padding + const size_t canary_pixels = test_case->canary_pixels; // Canary pixels, according to the data type + const unsigned int src_unalign_byte = test_case->src_unalign_byte; // Unalignment bytes for source buffer + const unsigned int dest_unalign_byte = test_case->dest_unalign_byte; // Unalignment bytes for destination buffer + const unsigned int memory_offset = test_case->memory_alignment_offset; // Memory alignment offset for 16-byte boundary + + // Allocate destination arrays and source array for Assembly and ANSI LVGL Blend API + void *src_mem_common = memalign(16, (src_buf_len * src_data_type_size) + src_unalign_byte); + void *dest_mem_asm = memalign(16, (total_dest_buf_len * dest_data_type_size) + dest_unalign_byte + memory_offset); + void *dest_mem_ansi = memalign(16, (total_dest_buf_len * dest_data_type_size) + dest_unalign_byte + memory_offset); + TEST_ASSERT_NOT_NULL_MESSAGE(src_mem_common, "Lack of memory"); + TEST_ASSERT_NOT_NULL_MESSAGE(dest_mem_asm, "Lack of memory"); + TEST_ASSERT_NOT_NULL_MESSAGE(dest_mem_ansi, "Lack of memory"); + + // Save a pointer to the beginning of the allocated memory which will be used to free() + test_case->buf.p_src_alloc = src_mem_common; + test_case->buf.p_dest_asm_alloc = dest_mem_asm; + test_case->buf.p_dest_ansi_alloc = dest_mem_ansi; + + // Apply destination and source array unalignment + uint8_t *src_buf_common = (uint8_t *)src_mem_common + src_unalign_byte; + uint8_t *dest_buf_asm = (uint8_t *)dest_mem_asm + dest_unalign_byte + memory_offset; + uint8_t *dest_buf_ansi = (uint8_t *)dest_mem_ansi + dest_unalign_byte + memory_offset; + + // Set the whole buffer to 0, including the Canary pixels part + memset(src_buf_common, 0, src_buf_len * src_data_type_size); + memset(dest_buf_asm, 0, total_dest_buf_len * src_data_type_size); + memset(dest_buf_ansi, 0, total_dest_buf_len * src_data_type_size); + + switch (test_case->operation_type) { + case OPERATION_FILL: + // Fill the actual part of the destination buffers with known values, + // Values must be same, because of the stride + + if (test_case->color_format == LV_COLOR_FORMAT_RGB565) { + uint16_t *dest_buf_asm_uint16 = (uint16_t *)dest_buf_asm; + uint16_t *dest_buf_ansi_uint16 = (uint16_t *)dest_buf_ansi; + uint16_t *src_buf_uint16 = (uint16_t *)src_buf_common; + + // Fill destination buffers + for (int i = 0; i < active_dest_buf_len; i++) { + dest_buf_asm_uint16[canary_pixels + i] = i + ((i & 1) ? 0x6699 : 0x9966); + dest_buf_ansi_uint16[canary_pixels + i] = dest_buf_asm_uint16[canary_pixels + i]; + } + + // Fill source buffer + for (int i = 0; i < src_buf_len; i++) { + src_buf_uint16[i] = i + ((i & 1) ? 0x55AA : 0xAA55); + } + } + + if (test_case->color_format == LV_COLOR_FORMAT_RGB888) { + uint8_t *dest_buf_asm_uint8 = dest_buf_asm; + uint8_t *dest_buf_ansi_uint8 = dest_buf_ansi; + uint8_t *src_buf_uint8 = src_buf_common; + + // Fill destination buffers + for (int i = 0; i < active_dest_buf_len * 3; i++) { + dest_buf_asm_uint8[(canary_pixels * 3) + i] = i + ((i & 1) ? 0x66 : 0x99); + dest_buf_ansi_uint8[(canary_pixels * 3) + i] = dest_buf_asm_uint8[(canary_pixels * 3) + i]; + } + + // Fill source buffer + for (int i = 0; i < src_buf_len * 3; i++) { + src_buf_uint8[i] = i + ((i & 1) ? 0x55 : 0xAA); + } + } + + break; + default: + TEST_ASSERT_MESSAGE(false, "LV Operation not found"); + break; + } + + // Shift array pointers by (Canary pixels amount * data type length) forward + dest_buf_asm += canary_pixels * dest_data_type_size; + dest_buf_ansi += canary_pixels * dest_data_type_size; + + // Save a pointer to the working part of the memory, where the test data are stored + test_case->buf.p_src = (void *)src_buf_common; + test_case->buf.p_dest_asm = (void *)dest_buf_asm; + test_case->buf.p_dest_ansi = (void *)dest_buf_ansi; + +#if DBG_PRINT_OUTPUT + printf("Destination buffers fill:\n"); + for (uint32_t i = 0; i < test_case->active_dest_buf_len; i++) { + printf("dest_buf[%"PRIi32"] %s ansi = %8"PRIx16" \t asm = %8"PRIx16" \n", i, ((i < 10) ? (" ") : ("")), + ((uint16_t *)test_case->buf.p_dest_ansi)[i], ((uint16_t *)test_case->buf.p_dest_asm)[i]); + } + printf("\n"); + + printf("Source buffer fill:\n"); + for (uint32_t i = 0; i < test_case->src_buf_len; i++) { + printf("src_buf[%"PRIi32"] %s = %8"PRIx16" \n", i, ((i < 10) ? (" ") : ("")), ((uint16_t *)test_case->buf.p_src)[i]); + } + printf("\n"); +#endif + +} + +static void test_eval_image_16bit_data(func_test_case_lv_image_params_t *test_case) +{ + // Print results, 16bit data +#if DBG_PRINT_OUTPUT + printf("\nEval\nDestination buffers fill:\n"); + for (uint32_t i = 0; i < test_case->total_dest_buf_len; i++) { + printf("dest_buf[%"PRIi32"] %s ansi = %8"PRIx16" \t asm = %8"PRIx16" %s \n", i, ((i < 10) ? (" ") : ("")), + ((uint16_t *)test_case->buf.p_dest_ansi)[i], ((uint16_t *)test_case->buf.p_dest_asm)[i], + (((uint16_t *)test_case->buf.p_dest_ansi)[i] == ((uint16_t *)test_case->buf.p_dest_asm)[i]) ? ("OK") : ("FAIL")); + } + printf("\n"); + + printf("Source buffer fill:\n"); + for (uint32_t i = 0; i < test_case->src_buf_len; i++) { + printf("src_buf[%"PRIi32"] %s = %8"PRIx16" \n", i, ((i < 10) ? (" ") : ("")), ((uint16_t *)test_case->buf.p_src)[i]); + } + printf("\n"); +#endif + + // Canary pixels area must stay 0 + const size_t canary_pixels = test_case->canary_pixels; + TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, (uint16_t *)test_case->buf.p_dest_ansi, canary_pixels, test_msg_buf); + TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, (uint16_t *)test_case->buf.p_dest_asm, canary_pixels, test_msg_buf); + + // dest_buf_asm and dest_buf_ansi must be equal + TEST_ASSERT_EQUAL_UINT16_ARRAY_MESSAGE((uint16_t *)test_case->buf.p_dest_ansi + canary_pixels, + (uint16_t *)test_case->buf.p_dest_asm + canary_pixels, test_case->active_dest_buf_len, test_msg_buf); + + // Data part of the destination buffer and source buffer (not considering matrix padding) must be equal + uint16_t *dest_row_begin = (uint16_t *)test_case->buf.p_dest_asm + canary_pixels; + uint16_t *src_row_begin = (uint16_t *)test_case->buf.p_src; + for (int row = 0; row < test_case->dest_h; row++) { + TEST_ASSERT_EQUAL_UINT16_ARRAY_MESSAGE(dest_row_begin, src_row_begin, test_case->dest_w, test_msg_buf); + dest_row_begin += test_case->dest_stride; // Move pointer of the destination buffer to the next row + src_row_begin += test_case->src_stride; // Move pointer of the source buffer to the next row + } + + // Canary pixels area must stay 0 + TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, + (uint16_t *)test_case->buf.p_dest_ansi + (test_case->total_dest_buf_len - canary_pixels), canary_pixels, test_msg_buf); + TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(0, + (uint16_t *)test_case->buf.p_dest_asm + (test_case->total_dest_buf_len - canary_pixels), canary_pixels, test_msg_buf); +} + +static void test_eval_image_24bit_data(func_test_case_lv_image_params_t *test_case) +{ + +// Print results, 24bit data +#if DBG_PRINT_OUTPUT + + printf("\nEval\nDestination buffers fill:\n"); + size_t dest_data_type_size = test_case->dest_data_type_size; + for (uint32_t i = 0; i < test_case->total_dest_buf_len; i++) { + uint32_t ansi_value = ((uint8_t *)test_case->buf.p_dest_ansi)[i * dest_data_type_size] + | (((uint8_t *)test_case->buf.p_dest_ansi)[i * dest_data_type_size + 1] << 8) + | (((uint8_t *)test_case->buf.p_dest_ansi)[i * dest_data_type_size + 2] << 16); + uint32_t asm_value = ((uint8_t *)test_case->buf.p_dest_asm)[i * dest_data_type_size] + | (((uint8_t *)test_case->buf.p_dest_asm)[i * dest_data_type_size + 1] << 8) + | (((uint8_t *)test_case->buf.p_dest_asm)[i * dest_data_type_size + 2] << 16); + printf("dest_buf[%"PRIi32"] %s ansi = %8"PRIx32" \t asm = %8"PRIx32" \n", i, ((i < 10) ? (" ") : ("")), ansi_value, + asm_value); + } + printf("\n"); + + printf("Source buffer fill:\n"); + printf("src_buf_len = %d\n", test_case->src_buf_len); + size_t src_data_type_size = test_case->src_data_type_size; + for (uint32_t i = 0; i < test_case->src_buf_len; i++) { + uint32_t value = ((uint8_t *)test_case->buf.p_src)[i * src_data_type_size] + | (((uint8_t *)test_case->buf.p_src)[i * src_data_type_size + 1] << 8) + | (((uint8_t *)test_case->buf.p_src)[i * src_data_type_size + 2] << 16); + printf("dest_buf[%"PRIi32"] %s = %8"PRIx32" \n", i, ((i < 10) ? (" ") : ("")), value); + } + printf("\n"); +#endif + + // Canary pixels area must stay 0 + const size_t canary_pixels = test_case->canary_pixels; + TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, (uint8_t *)test_case->buf.p_dest_ansi, canary_pixels * 3, test_msg_buf); + TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, (uint8_t *)test_case->buf.p_dest_asm, canary_pixels * 3, test_msg_buf); + + // dest_buf_asm and dest_buf_ansi must be equal + TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE((uint8_t *)test_case->buf.p_dest_ansi + (canary_pixels * 3), + (uint8_t *)test_case->buf.p_dest_asm + (canary_pixels * 3), test_case->active_dest_buf_len, test_msg_buf); + + // Data part of the destination buffer and source buffer (not considering matrix padding) must be equal + uint8_t *dest_row_begin = (uint8_t *)test_case->buf.p_dest_asm + (canary_pixels * 3); + uint8_t *src_row_begin = (uint8_t *)test_case->buf.p_src; + for (int row = 0; row < test_case->dest_h; row++) { + TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(dest_row_begin, src_row_begin, test_case->dest_w * 3, test_msg_buf); + dest_row_begin += (test_case->dest_stride * 3); // Move pointer of the destination buffer to the next row + src_row_begin += (test_case->src_stride * 3); // Move pointer of the source buffer to the next row + } + + // Canary pixels area must stay 0 + TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, + (uint8_t *)test_case->buf.p_dest_ansi + ((test_case->total_dest_buf_len * 3) - (canary_pixels * 3)), canary_pixels * 3, + test_msg_buf); + TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(0, + (uint8_t *)test_case->buf.p_dest_asm + ((test_case->total_dest_buf_len * 3) - (canary_pixels * 3)), canary_pixels * 3, + test_msg_buf); +} diff --git a/components/espressif__esp_lvgl_port/test_apps/simd/sdkconfig.defaults b/components/espressif__esp_lvgl_port/test_apps/simd/sdkconfig.defaults new file mode 100644 index 00000000..384a6053 --- /dev/null +++ b/components/espressif__esp_lvgl_port/test_apps/simd/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_ESP_TASK_WDT=n +CONFIG_OPTIMIZATION_LEVEL_RELEASE=y +CONFIG_COMPILER_OPTIMIZATION_PERF=y \ No newline at end of file