diff --git a/components/esp_lvgl_port/CMakeLists.txt b/components/esp_lvgl_port/CMakeLists.txt index af2915c2f..6d4b45f69 100644 --- a/components/esp_lvgl_port/CMakeLists.txt +++ b/components/esp_lvgl_port/CMakeLists.txt @@ -113,6 +113,21 @@ if((lvgl_ver VERSION_GREATER_EQUAL "9.1.0") AND (lvgl_ver VERSION_LESS "9.2.0")) endif() endif() +# Include hardware acceleration code (PPA and DMA2D) for rendering, and only for esp32p4 +if(lvgl_ver VERSION_GREATER_EQUAL "9.1.0") + if(CONFIG_IDF_TARGET_ESP32P4) + message(VERBOSE "Using hardware acceleration for esp32p4") + # 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") + + file(GLOB_RECURSE PPA_SRCS ${PORT_PATH}/ppa/*) + list(APPEND ADD_SRCS ${PPA_SRCS}) + set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u lv_malloc_core") + set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u esp_ppa_fill_for_lvgl") + endif() +endif() + # Here we create the real lvgl_port_lib add_library(lvgl_port_lib STATIC ${PORT_PATH}/esp_lvgl_port.c @@ -130,5 +145,9 @@ target_link_libraries(lvgl_port_lib PRIVATE ${ADD_LIBS} ) +if(CONFIG_IDF_TARGET_ESP32P4) + target_link_libraries(lvgl_port_lib PRIVATE idf::esp_driver_ppa idf::esp_mm) +endif() + # Finally, link the lvgl_port_lib its esp-idf interface library target_link_libraries(${COMPONENT_LIB} INTERFACE lvgl_port_lib) diff --git a/components/esp_lvgl_port/idf_component.yml b/components/esp_lvgl_port/idf_component.yml index d9cf83d6f..1c5f2eea3 100644 --- a/components/esp_lvgl_port/idf_component.yml +++ b/components/esp_lvgl_port/idf_component.yml @@ -1,4 +1,4 @@ -version: "2.6.0" +version: "2.6.1" description: ESP LVGL port url: https://github.com/espressif/esp-bsp/tree/master/components/esp_lvgl_port dependencies: diff --git a/components/esp_lvgl_port/include/bsp_ppa.h b/components/esp_lvgl_port/include/bsp_ppa.h new file mode 100644 index 000000000..559ad19f8 --- /dev/null +++ b/components/esp_lvgl_port/include/bsp_ppa.h @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Release the memory used for ppa blend with fixed color in fg + */ +void esp_ppa_mem_release(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_lvgl_port/include/esp_lvgl_port_lv_blend_ppa.h b/components/esp_lvgl_port/include/esp_lvgl_port_lv_blend_ppa.h new file mode 100644 index 000000000..2b98e98bc --- /dev/null +++ b/components/esp_lvgl_port/include/esp_lvgl_port_lv_blend_ppa.h @@ -0,0 +1,410 @@ +/* + * SPDX-FileCopyrightText: 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_ppa.h included, but CONFIG_LV_DRAW_SW_ASM_CUSTOM not set. PPA rendering not used" +#else + +#include "lv_version.h" + +/********************* + * DEFINES + *********************/ + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB565 +#define LV_DRAW_SW_COLOR_BLEND_TO_RGB565(dsc) \ + _lv_color_blend_to_rgb565_esp2d(dsc) +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB565_WITH_OPA +#define LV_DRAW_SW_COLOR_BLEND_TO_RGB565_WITH_OPA(dsc) \ + _lv_color_blend_to_rgb565_with_opa_esp2d(dsc) +#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_esp2d(dsc) +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_WITH_OPA +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB565_WITH_OPA(dsc) \ + _lv_rgb565_blend_normal_to_rgb565_with_opa_esp2d(dsc) +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565 +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565(dsc, src_px_size) \ + _lv_rgb888_blend_normal_to_rgb565_esp2d(dsc, src_px_size) +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_WITH_OPA +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB565_WITH_OPA(dsc, src_px_size) \ + _lv_rgb888_blend_normal_to_rgb565_with_opa_esp2d(dsc, src_px_size) +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565 +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565(dsc) \ + _lv_argb8888_blend_normal_to_rgb565_esp2d(dsc) +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_WITH_OPA +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB565_WITH_OPA(dsc) \ + _lv_argb8888_blend_normal_to_rgb565_with_opa_esp2d(dsc) +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB888 +#define LV_DRAW_SW_COLOR_BLEND_TO_RGB888(dsc, dst_px_size) \ + _lv_color_blend_to_rgb888_esp2d(dsc, dst_px_size) +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB888_WITH_OPA +#define LV_DRAW_SW_COLOR_BLEND_TO_RGB888_WITH_OPA(dsc, dst_px_size) \ + _lv_color_blend_to_rgb888_with_opa_esp2d(dsc, dst_px_size) +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888 +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888(dsc, dst_px_size) \ + _lv_rgb565_blend_normal_to_rgb888_esp2d(dsc, dst_px_size) +#endif + +#ifndef LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_WITH_OPA +#define LV_DRAW_SW_RGB565_BLEND_NORMAL_TO_RGB888_WITH_OPA(dsc, dst_px_size) \ + _lv_rgb565_blend_normal_to_rgb888_with_opa_esp2d(dsc, dst_px_size) +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888 +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888(dsc, dst_px_size, src_px_size) \ + _lv_rgb888_blend_normal_to_rgb888_esp2d(dsc, dst_px_size, src_px_size) +#endif + +#ifndef LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_WITH_OPA +#define LV_DRAW_SW_RGB888_BLEND_NORMAL_TO_RGB888_WITH_OPA(dsc, dst_px_size, src_px_size) \ + _lv_rgb888_blend_normal_to_rgb888_with_opa_esp2d(dsc, dst_px_size, src_px_size) +#endif + +#ifndef LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888 +#define LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888(dsc) \ + _lv_color_blend_to_argb8888_esp2d(dsc) +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888 +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888(dsc, dst_px_size) \ + _lv_argb8888_blend_normal_to_rgb888_esp2d(dsc, dst_px_size) +#endif + +#ifndef LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_WITH_OPA +#define LV_DRAW_SW_ARGB8888_BLEND_NORMAL_TO_RGB888_WITH_OPA(dsc, dst_px_size) \ + _lv_argb8888_blend_normal_to_rgb888_with_opa_esp2d(dsc, dst_px_size) +#endif + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + uint32_t opa; + void *dest_buf; + uint32_t dest_w; + uint32_t dest_h; + uint32_t dest_stride; + const void *src_buf; + uint32_t src_stride; + const lv_opa_t *mask_buf; + uint32_t mask_stride; +} asm_dsc_t; + +#if ((LVGL_VERSION_MAJOR == 9) && (LVGL_VERSION_MINOR == 1)) + +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; +} bsp_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; +} bsp_blend_image_dsc_t; + +#elif ((LVGL_VERSION_MAJOR == 9) && (LVGL_VERSION_MINOR == 2)) + +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; + lv_area_t relative_area; +} bsp_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; + lv_area_t relative_area; /**< The blend area relative to the layer's buffer area. */ + lv_area_t src_area; /**< The original src area. */ +} bsp_blend_image_dsc_t; + +#endif + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +// ppa for lvgl +extern int esp_ppa_fill_for_lvgl(asm_dsc_t *dsc, uint32_t dest_px_size); + +// Simple fill: ppa fill +static inline lv_result_t _lv_color_blend_to_rgb565_esp2d(void *dsc) +{ + bsp_blend_fill_dsc_t *fill_dsc = (bsp_blend_fill_dsc_t *) dsc; + asm_dsc_t asm_dsc = { + .dest_buf = fill_dsc->dest_buf, + .dest_w = fill_dsc->dest_w, + .dest_h = fill_dsc->dest_h, + .dest_stride = fill_dsc->dest_stride, + .src_buf = &fill_dsc->color, + .opa = fill_dsc->opa, + }; + return esp_ppa_fill_for_lvgl(&asm_dsc, 2); +} + +static inline lv_result_t _lv_color_blend_to_rgb888_esp2d(void *dsc, uint32_t dst_px_size) +{ + bsp_blend_fill_dsc_t *fill_dsc = (bsp_blend_fill_dsc_t *) dsc; + asm_dsc_t asm_dsc = { + .dest_buf = fill_dsc->dest_buf, + .dest_w = fill_dsc->dest_w, + .dest_h = fill_dsc->dest_h, + .dest_stride = fill_dsc->dest_stride, + .src_buf = &fill_dsc->color, + .opa = fill_dsc->opa, + }; + return esp_ppa_fill_for_lvgl(&asm_dsc, dst_px_size); +} + +static inline lv_result_t _lv_color_blend_to_argb8888_esp2d(void *dsc) +{ + bsp_blend_fill_dsc_t *fill_dsc = (bsp_blend_fill_dsc_t *) dsc; + asm_dsc_t asm_dsc = { + .dest_buf = fill_dsc->dest_buf, + .dest_w = fill_dsc->dest_w, + .dest_h = fill_dsc->dest_h, + .dest_stride = fill_dsc->dest_stride, + .src_buf = &fill_dsc->color, + .opa = fill_dsc->opa, + }; + return esp_ppa_fill_for_lvgl(&asm_dsc, 4); +} + +extern int esp_ppa_fill_opa_for_lvgl(asm_dsc_t *dsc, uint32_t dest_px_size); + +// Blend with opa +static inline lv_result_t _lv_color_blend_to_rgb565_with_opa_esp2d(void *dsc) +{ + bsp_blend_fill_dsc_t *fill_dsc = (bsp_blend_fill_dsc_t *) dsc; + asm_dsc_t asm_dsc = { + .dest_buf = fill_dsc->dest_buf, + .dest_w = fill_dsc->dest_w, + .dest_h = fill_dsc->dest_h, + .dest_stride = fill_dsc->dest_stride, + .src_buf = &fill_dsc->color, + .opa = fill_dsc->opa, + }; + return esp_ppa_fill_opa_for_lvgl(&asm_dsc, 2); +} + +static inline lv_result_t _lv_color_blend_to_rgb888_with_opa_esp2d(void *dsc, + uint32_t dst_px_size) +{ + bsp_blend_fill_dsc_t *fill_dsc = (bsp_blend_fill_dsc_t *) dsc; + asm_dsc_t asm_dsc = { + .dest_buf = fill_dsc->dest_buf, + .dest_w = fill_dsc->dest_w, + .dest_h = fill_dsc->dest_h, + .dest_stride = fill_dsc->dest_stride, + .src_buf = &fill_dsc->color, + .opa = fill_dsc->opa, + }; + return esp_ppa_fill_opa_for_lvgl(&asm_dsc, dst_px_size); +} + +extern int esp_dma2d_convert_for_lvgl(asm_dsc_t *dsc, const uint8_t dest_px_size, uint32_t src_px_size); + +// Color convert (including dam2d memcpy) +static inline lv_result_t _lv_rgb565_blend_normal_to_rgb565_esp2d(void *dsc) +{ + bsp_blend_image_dsc_t *blend_dsc = (bsp_blend_image_dsc_t *) dsc; + asm_dsc_t asm_dsc = { + .dest_buf = blend_dsc->dest_buf, + .dest_w = blend_dsc->dest_w, + .dest_h = blend_dsc->dest_h, + .dest_stride = blend_dsc->dest_stride, + .src_buf = blend_dsc->src_buf, + .src_stride = blend_dsc->src_stride, + .opa = blend_dsc->opa, + }; + return esp_dma2d_convert_for_lvgl(&asm_dsc, 2, 2); +} + +static inline lv_result_t _lv_rgb565_blend_normal_to_rgb888_esp2d(void *dsc, + uint32_t dst_px_size) +{ + bsp_blend_image_dsc_t *blend_dsc = (bsp_blend_image_dsc_t *) dsc; + asm_dsc_t asm_dsc = { + .dest_buf = blend_dsc->dest_buf, + .dest_w = blend_dsc->dest_w, + .dest_h = blend_dsc->dest_h, + .dest_stride = blend_dsc->dest_stride, + .src_buf = blend_dsc->src_buf, + .src_stride = blend_dsc->src_stride, + .opa = blend_dsc->opa, + }; + return esp_dma2d_convert_for_lvgl(&asm_dsc, dst_px_size, 2); +} + +static inline lv_result_t _lv_rgb888_blend_normal_to_rgb565_esp2d(void *dsc, + uint32_t src_px_size) +{ + bsp_blend_image_dsc_t *blend_dsc = (bsp_blend_image_dsc_t *) dsc; + asm_dsc_t asm_dsc = { + .dest_buf = blend_dsc->dest_buf, + .dest_w = blend_dsc->dest_w, + .dest_h = blend_dsc->dest_h, + .dest_stride = blend_dsc->dest_stride, + .src_buf = blend_dsc->src_buf, + .src_stride = blend_dsc->src_stride, + .opa = blend_dsc->opa, + }; + return esp_dma2d_convert_for_lvgl(&asm_dsc, src_px_size, 2); +} + +static inline lv_result_t _lv_rgb888_blend_normal_to_rgb888_esp2d(void *dsc, + uint32_t dst_px_size, + uint32_t src_px_size) +{ + bsp_blend_image_dsc_t *blend_dsc = (bsp_blend_image_dsc_t *) dsc; + asm_dsc_t asm_dsc = { + .dest_buf = blend_dsc->dest_buf, + .dest_w = blend_dsc->dest_w, + .dest_h = blend_dsc->dest_h, + .dest_stride = blend_dsc->dest_stride, + .src_buf = blend_dsc->src_buf, + .src_stride = blend_dsc->src_stride, + .opa = blend_dsc->opa, + }; + return esp_dma2d_convert_for_lvgl(&asm_dsc, dst_px_size, src_px_size); +} + +extern int esp_ppa_blend_area_for_lvgl(asm_dsc_t *dsc, uint32_t dest_px_size, uint8_t src_px_size); + +// Blend normal +static inline lv_result_t _lv_argb8888_blend_normal_to_rgb565_esp2d(void *dsc) +{ + bsp_blend_image_dsc_t *blend_dsc = (bsp_blend_image_dsc_t *) dsc; + asm_dsc_t asm_dsc = { + .dest_buf = blend_dsc->dest_buf, + .dest_w = blend_dsc->dest_w, + .dest_h = blend_dsc->dest_h, + .dest_stride = blend_dsc->dest_stride, + .src_buf = blend_dsc->src_buf, + .src_stride = blend_dsc->src_stride, + .opa = blend_dsc->opa, + }; + return esp_ppa_blend_area_for_lvgl(&asm_dsc, 2, 4); +} + +static inline lv_result_t _lv_argb8888_blend_normal_to_rgb888_esp2d(void *dsc, + uint32_t dst_px_size) +{ + bsp_blend_image_dsc_t *blend_dsc = (bsp_blend_image_dsc_t *) dsc; + asm_dsc_t asm_dsc = { + .dest_buf = blend_dsc->dest_buf, + .dest_w = blend_dsc->dest_w, + .dest_h = blend_dsc->dest_h, + .dest_stride = blend_dsc->dest_stride, + .src_buf = blend_dsc->src_buf, + .src_stride = blend_dsc->src_stride, + .opa = blend_dsc->opa, + }; + return esp_ppa_blend_area_for_lvgl(&asm_dsc, dst_px_size, 4); +} + + +static inline lv_result_t _lv_rgb565_blend_normal_to_rgb565_with_opa_esp2d(void *dsc) +{ + return LV_RESULT_INVALID; +} + +static inline lv_result_t _lv_rgb888_blend_normal_to_rgb565_with_opa_esp2d(void *dsc, + uint32_t src_px_size) +{ + return LV_RESULT_INVALID; +} + +static inline lv_result_t _lv_argb8888_blend_normal_to_rgb565_with_opa_esp2d(void *dsc) +{ + return LV_RESULT_INVALID; +} + +static inline lv_result_t _lv_rgb565_blend_normal_to_rgb888_with_opa_esp2d(void *dsc, + uint32_t dst_px_size) +{ + return LV_RESULT_INVALID; +} + +static inline lv_result_t _lv_rgb888_blend_normal_to_rgb888_with_opa_esp2d(void *dsc, + uint32_t dst_px_size, uint32_t src_px_size) +{ + return LV_RESULT_INVALID; +} + +static inline lv_result_t _lv_argb8888_blend_normal_to_rgb888_with_opa_esp2d(void *dsc, + uint32_t dst_px_size) +{ + return LV_RESULT_INVALID; +} + +#endif // CONFIG_LV_DRAW_SW_ASM_CUSTOM + +#ifdef __cplusplus +} /*extern "C"*/ +#endif diff --git a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port.c b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port.c index 5551a2ef7..da3d72475 100644 --- a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port.c +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port.c @@ -19,6 +19,7 @@ #include "esp_lvgl_port.h" #include "esp_lvgl_port_priv.h" #include "lvgl.h" +#include "bsp_ppa.h" static const char *TAG = "LVGL"; @@ -301,6 +302,10 @@ static void lvgl_port_task_deinit(void) /* Deinitialize LVGL */ lv_deinit(); #endif + +#if CONFIG_IDF_TARGET_ESP32P4 + esp_ppa_mem_release(); +#endif } static void lvgl_port_tick_increment(void *arg) diff --git a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c index a5a07d7f3..d833f0293 100644 --- a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c @@ -72,6 +72,9 @@ typedef struct { } flags; } lvgl_port_display_ctx_t; +static lv_color_t *lcd_buf1 = NULL; +static lv_color_t *lcd_buf2 = NULL; + /******************************************************************************* * Function definitions *******************************************************************************/ @@ -95,6 +98,13 @@ static void lvgl_port_display_invalidate_callback(lv_event_t *e); * Public API functions *******************************************************************************/ +// Used for ppa picture start address +void bsp_get_lvgl_buffer(void **buffer1, void **buffer2) +{ + *buffer1 = lcd_buf1; + *buffer2 = lcd_buf2; +} + lv_display_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg) { lvgl_port_lock(0); @@ -312,7 +322,7 @@ static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp buff_caps |= MALLOC_CAP_DEFAULT; } - /* Use RGB internal buffers for avoid tearing effect */ + /* Use LCD 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; @@ -325,6 +335,8 @@ static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp 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; + lcd_buf1 = buf1; + lcd_buf2 = buf2; } 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 */ @@ -683,7 +695,7 @@ static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, u 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); + xSemaphoreTake(disp_ctx->trans_sem, pdMS_TO_TICKS(100)); } } else { esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map); diff --git a/components/esp_lvgl_port/src/lvgl9/ppa/esp_dma2d_utils.c b/components/esp_lvgl_port/src/lvgl9/ppa/esp_dma2d_utils.c new file mode 100644 index 000000000..e43c7e279 --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/ppa/esp_dma2d_utils.c @@ -0,0 +1,161 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_dma2d_utils.h" +#include "esp_private/dma2d.h" +#include "soc/dma2d_channel.h" +#include "esp_heap_caps.h" +#include "esp_memory_utils.h" +#include "esp_check.h" +#include "esp_err.h" +#include "esp_attr.h" +#include "esp_log.h" + +__attribute__((unused)) static const char *TAG = "dma2d_m2m"; + +#if CONFIG_DMA2D_OPERATION_FUNC_IN_IRAM || CONFIG_DMA2D_ISR_IRAM_SAFE +#define DMA2D_M2M_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#define DMA2D_M2M_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT +#endif + +#if CONFIG_DMA2D_ISR_IRAM_SAFE +#define DMA2D_M2M_ATTR IRAM_ATTR +#else +#define DMA2D_M2M_ATTR +#endif + +typedef dma2d_m2m_trans_config_t dma2d_m2m_trans_desc_t; + +typedef struct { + dma2d_m2m_trans_desc_t m2m_trans_desc; + dma2d_trans_config_t dma_chan_desc; + uint32_t dma_trans_placeholder_head; /* Head of the memory for storing the 2D-DMA transaction elm */ +} dma2d_m2m_transaction_t; + +static dma2d_pool_handle_t dma2d_pool_handle; + +esp_err_t dma2d_m2m_init(void) +{ + dma2d_pool_config_t dma2d_pool_config = { + .pool_id = 0 + }; + return dma2d_acquire_pool(&dma2d_pool_config, &dma2d_pool_handle); +} + +esp_err_t dma2d_m2m_deinit(void) +{ + return dma2d_release_pool(dma2d_pool_handle); +} + +static bool DMA2D_M2M_ATTR dma2d_m2m_transaction_done_cb(dma2d_channel_handle_t dma2d_chan, dma2d_event_data_t *event_data, void *user_data) +{ + bool need_yield = false; + dma2d_m2m_transaction_t *trans_config = (dma2d_m2m_transaction_t *)user_data; + dma2d_m2m_trans_desc_t *m2m_trans_desc = &trans_config->m2m_trans_desc; + + if (m2m_trans_desc->trans_eof_cb) { + need_yield |= m2m_trans_desc->trans_eof_cb(m2m_trans_desc->user_data); + } + + free(trans_config); + + return need_yield; +} + +static bool DMA2D_M2M_ATTR dma2d_m2m_transaction_on_picked(uint32_t channel_num, const dma2d_trans_channel_info_t *dma2d_chans, void *user_config) +{ + assert(channel_num == 2 && dma2d_chans && user_config); + dma2d_m2m_transaction_t *trans_config = (dma2d_m2m_transaction_t *)user_config; + dma2d_m2m_trans_desc_t *m2m_trans_desc = &trans_config->m2m_trans_desc; + + // Get the required 2D-DMA channel handles + uint32_t dma_tx_chan_idx = 0; + uint32_t dma_rx_chan_idx = 1; + if (dma2d_chans[0].dir == DMA2D_CHANNEL_DIRECTION_RX) { + dma_tx_chan_idx = 1; + dma_rx_chan_idx = 0; + } + dma2d_channel_handle_t dma_tx_chan = dma2d_chans[dma_tx_chan_idx].chan; + dma2d_channel_handle_t dma_rx_chan = dma2d_chans[dma_rx_chan_idx].chan; + + dma2d_trigger_t trig_periph = { + .periph = DMA2D_TRIG_PERIPH_M2M, + .periph_sel_id = SOC_DMA2D_TRIG_PERIPH_M2M_TX, + }; + dma2d_connect(dma_tx_chan, &trig_periph); + trig_periph.periph_sel_id = SOC_DMA2D_TRIG_PERIPH_M2M_RX; + dma2d_connect(dma_rx_chan, &trig_periph); + + if (m2m_trans_desc->transfer_ability_config) { + dma2d_set_transfer_ability(dma_tx_chan, m2m_trans_desc->transfer_ability_config); + dma2d_set_transfer_ability(dma_rx_chan, m2m_trans_desc->transfer_ability_config); + } + + if (m2m_trans_desc->tx_strategy_config) { + dma2d_apply_strategy(dma_tx_chan, m2m_trans_desc->tx_strategy_config); + } + if (m2m_trans_desc->rx_strategy_config) { + dma2d_apply_strategy(dma_rx_chan, m2m_trans_desc->rx_strategy_config); + } + + if (m2m_trans_desc->tx_csc_config) { + dma2d_configure_color_space_conversion(dma_tx_chan, m2m_trans_desc->tx_csc_config); + } + if (m2m_trans_desc->rx_csc_config) { + dma2d_configure_color_space_conversion(dma_rx_chan, m2m_trans_desc->rx_csc_config); + } + + dma2d_rx_event_callbacks_t dma_cbs = { + .on_recv_eof = dma2d_m2m_transaction_done_cb, + }; + dma2d_register_rx_event_callbacks(dma_rx_chan, &dma_cbs, (void *)trans_config); + + dma2d_set_desc_addr(dma_tx_chan, m2m_trans_desc->tx_desc_base_addr); + dma2d_set_desc_addr(dma_rx_chan, m2m_trans_desc->rx_desc_base_addr); + + dma2d_start(dma_tx_chan); + dma2d_start(dma_rx_chan); + + // No need to yield + return false; +} + +esp_err_t dma2d_m2m(const dma2d_m2m_trans_config_t *trans_config) +{ +#if CONFIG_DMA2D_ISR_IRAM_SAFE + if (trans_config->trans_eof_cb) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(trans_config->trans_eof_cb), + ESP_ERR_INVALID_ARG, TAG, "trans_eof_cb not in IRAM"); + } + if (trans_config->user_data) { + ESP_RETURN_ON_FALSE(esp_ptr_internal(trans_config->user_data), + ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); + } +#endif + dma2d_m2m_transaction_t *dma2d_m2m_trans = (dma2d_m2m_transaction_t *)heap_caps_calloc(1, sizeof(dma2d_m2m_transaction_t) + SIZEOF_DMA2D_TRANS_T, DMA2D_M2M_MEM_ALLOC_CAPS); + assert(dma2d_m2m_trans); + + dma2d_m2m_trans->dma_chan_desc.tx_channel_num = 1; + dma2d_m2m_trans->dma_chan_desc.rx_channel_num = 1; + dma2d_m2m_trans->dma_chan_desc.channel_flags = DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING; + dma2d_m2m_trans->dma_chan_desc.channel_flags |= (trans_config->tx_csc_config == NULL) ? 0 : DMA2D_CHANNEL_FUNCTION_FLAG_TX_CSC; + dma2d_m2m_trans->dma_chan_desc.channel_flags |= (trans_config->rx_csc_config == NULL) ? 0 : DMA2D_CHANNEL_FUNCTION_FLAG_RX_CSC; + dma2d_m2m_trans->dma_chan_desc.specified_tx_channel_mask = 0; + dma2d_m2m_trans->dma_chan_desc.specified_rx_channel_mask = 0; + + memcpy(&dma2d_m2m_trans->m2m_trans_desc, trans_config, sizeof(dma2d_m2m_trans_config_t)); + + dma2d_m2m_trans->dma_chan_desc.user_config = (void *)dma2d_m2m_trans; + dma2d_m2m_trans->dma_chan_desc.on_job_picked = dma2d_m2m_transaction_on_picked; + + esp_err_t ret = dma2d_enqueue(dma2d_pool_handle, &dma2d_m2m_trans->dma_chan_desc, (dma2d_trans_t *)&dma2d_m2m_trans->dma_trans_placeholder_head); + if (ret != ESP_OK) { + free(dma2d_m2m_trans); + } + return ret; +} diff --git a/components/esp_lvgl_port/src/lvgl9/ppa/esp_dma2d_utils.h b/components/esp_lvgl_port/src/lvgl9/ppa/esp_dma2d_utils.h new file mode 100644 index 000000000..25822359c --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/ppa/esp_dma2d_utils.h @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_private/dma2d.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize the 2D-DMA module to perform memcopy operations + */ +esp_err_t dma2d_m2m_init(void); + +/** + * @brief De-initialize the 2D-DMA module + */ +esp_err_t dma2d_m2m_deinit(void); + +/** + * @brief Callback function when a memcopy operation is done + * + * @param user_data User registered data from `dma2d_m2m_trans_config_t` + * + * @return Whether a task switch is needed after the callback function returns, + * this is usually due to the callback wakes up some high priority task. + */ +typedef bool (*dma2d_m2m_trans_eof_callback_t)(void *user_data); + +/** + * @brief A collection of configuration items for perferming a memcopy operation with 2D-DMA + */ +typedef struct { + intptr_t tx_desc_base_addr; /*!< 2D-DMA TX descriptor address */ + intptr_t rx_desc_base_addr; /*!< 2D-DMA RX descriptor address */ + dma2d_m2m_trans_eof_callback_t trans_eof_cb; /*!< Callback function to be called when the memcopy operation is done */ + void *user_data; /*!< User registered data to be passed into `trans_eof_cb` callback */ + dma2d_transfer_ability_t *transfer_ability_config; /*!< Pointer to a collection of 2D-DMA transfer ability configuration */ + dma2d_strategy_config_t *tx_strategy_config; /*!< Pointer to a collection of 2D-DMA TX strategy configuration */ + dma2d_strategy_config_t *rx_strategy_config; /*!< Pointer to a collection of 2D-DMA RX strategy configuration */ + dma2d_csc_config_t *tx_csc_config; /*!< Pointer to a collection of 2D-DMA TX color space conversion configuration */ + dma2d_csc_config_t *rx_csc_config; /*!< Pointer to a collection of 2D-DMA RX color space conversion configuration */ +} dma2d_m2m_trans_config_t; + +/** + * @brief Do a memcopy operation with 2D-DMA module + * + * @param trans_config Pointer to a collection of configurations for the memcopy operation + * @return + * - ESP_OK: Enqueue the transaction to 2D-DMA pool successfully + * - ESP_ERR_INVALID_ARG: Enqueue the transaction to 2D-DMA pool failed because of invalid argument + */ +esp_err_t dma2d_m2m(const dma2d_m2m_trans_config_t *trans_config); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_lvgl_port/src/lvgl9/ppa/esp_lvgl_port_dma2d.c b/components/esp_lvgl_port/src/lvgl9/ppa/esp_lvgl_port_dma2d.c new file mode 100644 index 000000000..2371a83f7 --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/ppa/esp_lvgl_port_dma2d.c @@ -0,0 +1,175 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_dma2d_utils.h" +#include "esp_cache.h" +#include "esp_timer.h" +#include "esp_lvgl_port_dma2d.h" + +static char *TAG = "convert"; + +#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) +#define ALIGN_DOWN(num, align) ((num) & (~((align) - 1))) + +#define DMA2D_LOG_CYCLE (21) + +static size_t get_cache_line_size(const void *addr) +{ + esp_err_t ret = ESP_FAIL; + size_t cache_line_size = 0; + uint32_t heap_caps = esp_ptr_external_ram(addr) ? MALLOC_CAP_SPIRAM : MALLOC_CAP_INTERNAL; + ret = esp_cache_get_alignment(heap_caps, &cache_line_size); + return ret == ESP_OK ? cache_line_size : 0; +} + +static int make_addr_and_size_aligned(uint8_t **addr, uint32_t *size) +{ + uint8_t *buf = *addr; + uint16_t cache_line = get_cache_line_size(buf); + uint8_t *buf_alignd = (uint8_t *) ALIGN_DOWN((uint32_t)buf, cache_line); + uint32_t len = *size; + len += (buf - buf_alignd); + uint32_t size_align = ALIGN_UP(len, cache_line); + + *addr = buf_alignd; + *size = size_align; + return ESP_OK; +} + +static bool IRAM_ATTR dma2d_m2m_suc_eof_event_cb(void *user_data) +{ + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + SemaphoreHandle_t sem = (SemaphoreHandle_t)user_data; + xSemaphoreGiveFromISR(sem, &xHigherPriorityTaskWoken); + return (xHigherPriorityTaskWoken == pdTRUE); +} + +static void dma2d_link_dscr_init(uint32_t *head, uint32_t *next, void *buf_ptr, + uint32_t ha, uint32_t va, uint32_t hb, uint32_t vb, + uint32_t eof, uint32_t en_2d, uint32_t pbyte, uint32_t mod, + uint32_t bias_x, uint32_t bias_y) +{ + dma2d_descriptor_t *dma2d = (dma2d_descriptor_t *)head; + memset(dma2d, 0, sizeof(dma2d_descriptor_t)); + dma2d->owner = DMA2D_DESCRIPTOR_BUFFER_OWNER_DMA; + dma2d->suc_eof = eof; + dma2d->dma2d_en = en_2d; + dma2d->err_eof = 0; + dma2d->hb_length = hb; + dma2d->vb_size = vb; + dma2d->pbyte = pbyte; + dma2d->ha_length = ha; + dma2d->va_size = va; + dma2d->mode = mod; + dma2d->y = bias_y; + dma2d->x = bias_x; + dma2d->buffer = buf_ptr; + dma2d->next = (dma2d_descriptor_t *)next; +} + +esp_err_t dma2d_color_convert(dma2d_pic_config_t *src, dma2d_pic_config_t *dest) +{ + if (src->pic_buf == NULL || dest->pic_buf == NULL) { + return ESP_FAIL; + } + + if (src->format.pixel_format == COLOR_PIXEL_RGB666 || dest->format.pixel_format == COLOR_PIXEL_RGB666) { + return ESP_FAIL; + } + + int64_t start = esp_timer_get_time(); + dma2d_m2m_trans_config_t m2m_trans_config = {0}; + + if (dma2d_m2m_init() != ESP_OK) { + return ESP_FAIL; + } + + int dsc_size = 64; + size_t cache_line_size = 0; + esp_cache_get_alignment(MALLOC_CAP_SPIRAM, &cache_line_size); + dma2d_descriptor_t *tx_dsc = (dma2d_descriptor_t *)heap_caps_aligned_calloc(cache_line_size, 1, dsc_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_CACHE_ALIGNED); + dma2d_descriptor_t *rx_dsc = (dma2d_descriptor_t *)heap_caps_aligned_calloc(cache_line_size, 1, dsc_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_CACHE_ALIGNED); + if (tx_dsc == NULL || rx_dsc == NULL) { + return ESP_FAIL; + } + + dma2d_csc_config_t dma2d_tx_csc = {0}; + uint16_t src_pbyte = src->format.pixel_format == COLOR_PIXEL_RGB888 ? 3 : 2; + uint16_t dest_pbyte = dest->format.pixel_format == COLOR_PIXEL_RGB888 ? 3 : 2; + + if (src->format.pixel_format == COLOR_PIXEL_RGB888 && dest->format.pixel_format == COLOR_PIXEL_RGB565) { + dma2d_tx_csc.tx_csc_option = DMA2D_CSC_TX_RGB888_TO_RGB565; + } else if (src->format.pixel_format == COLOR_PIXEL_RGB565 && dest->format.pixel_format == COLOR_PIXEL_RGB888) { + dma2d_tx_csc.tx_csc_option = DMA2D_CSC_TX_RGB565_TO_RGB888; + } + + uint8_t *src_alignd = src->pic_buf + src->offset_y * src_pbyte * src->pic_w; + uint32_t src_size_alignd = src->pic_w * src->block_h * src_pbyte; + make_addr_and_size_aligned(&src_alignd, &src_size_alignd); + + uint8_t *dest_alignd = dest->pic_buf + dest->offset_y * dest_pbyte * dest->pic_w; + uint32_t dest_size_alignd = dest->pic_w * dest->block_h * dest_pbyte; + make_addr_and_size_aligned(&dest_alignd, &dest_size_alignd); + + // Writeback TX buffers and Invalidate RX buffers + esp_cache_msync((void *)src_alignd, src_size_alignd, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + esp_cache_msync((void *)dest_alignd, dest_size_alignd, ESP_CACHE_MSYNC_FLAG_INVALIDATE); + + SemaphoreHandle_t sem = xSemaphoreCreateCounting(1, 0); + + dma2d_transfer_ability_t transfer_ability_config = { + .data_burst_length = DMA2D_DATA_BURST_LENGTH_128, + .desc_burst_en = true, + .mb_size = DMA2D_MACRO_BLOCK_SIZE_NONE, + }; + + // DMA description preparation + dma2d_link_dscr_init((uint32_t *)tx_dsc, NULL, (void *)src->pic_buf, + src->pic_w, src->pic_h, + src->block_w, src->block_h, + 1, 1, dma2d_desc_pixel_format_to_pbyte_value(src->format), + DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE, src->offset_x, src->offset_y); + dma2d_link_dscr_init((uint32_t *)rx_dsc, NULL, (void *)dest->pic_buf, + dest->pic_w, dest->pic_h, + dest->block_w, dest->block_h, + 0, 1, dma2d_desc_pixel_format_to_pbyte_value(dest->format), + DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE, dest->offset_x, dest->offset_y); + // Writeback the DMA descriptors + esp_cache_msync((void *)tx_dsc, dsc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED); + esp_cache_msync((void *)rx_dsc, dsc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED); + + // Construct dma2d_m2m_trans_config_t structure + m2m_trans_config.tx_desc_base_addr = (intptr_t)tx_dsc; + m2m_trans_config.rx_desc_base_addr = (intptr_t)rx_dsc; + m2m_trans_config.trans_eof_cb = dma2d_m2m_suc_eof_event_cb; + m2m_trans_config.user_data = (void *)sem; + m2m_trans_config.transfer_ability_config = &transfer_ability_config; + m2m_trans_config.tx_csc_config = &dma2d_tx_csc; + + dma2d_m2m(&m2m_trans_config); + xSemaphoreTake(sem, portMAX_DELAY); + + free(tx_dsc); + free(rx_dsc); + vSemaphoreDelete(sem); + dma2d_m2m_deinit(); + + int duration = esp_timer_get_time() - start; + uint32_t data_size = dest->block_w * dest->block_h * (src_pbyte + dest_pbyte); + float speed = data_size * 0.95367 / duration; + static uint32_t count = 0; + if (count++ % DMA2D_LOG_CYCLE == 0) { + ESP_LOGD(TAG, "dma2d %s: %d ms, (x:%ld, y:%ld)[w:%ld, h:%ld][spx:%d, dpx:%d], %.1f MB/s", src_pbyte == dest_pbyte ? "memcpy" : "convert", + duration / 1000, dest->offset_x, dest->offset_y, dest->block_w, dest->block_h, src_pbyte, dest_pbyte, speed); + } + return ESP_OK; +} diff --git a/components/esp_lvgl_port/src/lvgl9/ppa/esp_lvgl_port_dma2d.h b/components/esp_lvgl_port/src/lvgl9/ppa/esp_lvgl_port_dma2d.h new file mode 100644 index 000000000..e7082f231 --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/ppa/esp_lvgl_port_dma2d.h @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_private/esp_cache_private.h" +#include "esp_memory_utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief A collection of configuration items for perferming a memcpy or color convert operation with 2D-DMA + */ +typedef struct { + color_space_pixel_format_t format; + uint8_t *pic_buf; + uint32_t pic_w; + uint32_t pic_h; + uint32_t offset_x; + uint32_t offset_y; + uint32_t block_w; + uint32_t block_h; +} dma2d_pic_config_t; + +/** + * @brief Perform memcpy or color convert operations using 2D-DMA module + */ +esp_err_t dma2d_color_convert(dma2d_pic_config_t *src, dma2d_pic_config_t *dest); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_lvgl_port/src/lvgl9/ppa/esp_lvgl_port_ppa.c b/components/esp_lvgl_port/src/lvgl9/ppa/esp_lvgl_port_ppa.c new file mode 100644 index 000000000..bf9fc730c --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/ppa/esp_lvgl_port_ppa.c @@ -0,0 +1,529 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "driver/ppa.h" +#include "esp_heap_caps.h" +#include "esp_timer.h" +#include "esp_log.h" +#include "lvgl.h" +#include "esp_memory_utils.h" +#include "esp_private/esp_cache_private.h" +#include "esp_lvgl_port_dma2d.h" + +static const char *TAG = "ppa_lvgl"; + +#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) +#define ALIGN_DOWN(num, align) ((num) & (~((align) - 1))) +#define PPA_LOG_CYCLE (21) + +// Need define in others file +extern void bsp_get_lvgl_buffer(void **buffer1, void **buffer2); + +// Need to free when lvgl deinit +static uint8_t *fg_pic_buf = NULL; + +typedef struct { + uint32_t opa; + void *dest_buf; + uint32_t dest_w; + uint32_t dest_h; + uint32_t dest_stride; + const void *src_buf; + uint32_t src_stride; + const lv_opa_t *mask_buf; + uint32_t mask_stride; +} asm_dsc_t; + +static size_t get_cache_line_size(const void *addr) +{ + esp_err_t ret = ESP_FAIL; + size_t cache_line_size = 0; + uint32_t heap_caps = esp_ptr_external_ram(addr) ? MALLOC_CAP_SPIRAM : MALLOC_CAP_INTERNAL; + ret = esp_cache_get_alignment(heap_caps, &cache_line_size); + return ret == ESP_OK ? cache_line_size : 0; +} + +static lv_result_t bsp_get_lvgl_info(uint32_t *pic_w, uint32_t *pic_h, uint32_t *stride) +{ + lv_display_t *disp = lv_display_get_default(); + if (disp == NULL) { + return LV_RESULT_INVALID; + } + *pic_w = lv_display_get_horizontal_resolution(disp); + *pic_h = lv_display_get_vertical_resolution(disp); + lv_color_format_t cf = lv_display_get_color_format(disp); + *stride = lv_draw_buf_width_to_stride(*pic_w, cf); + return LV_RESULT_OK; +} + +static lv_result_t find_start_addr_and_offset(const uint8_t *src_buf, uint8_t **pic_addr, uint32_t *offsetx, uint32_t *offsety) +{ + uint8_t *buf1, *buf2, *addr; + uint32_t pic_w, pic_h, stride; + if (bsp_get_lvgl_info(&pic_w, &pic_h, &stride) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + uint32_t pic_size = stride * pic_h; + bsp_get_lvgl_buffer((void **)&buf1, (void **)&buf2); + if (buf1 == NULL || buf2 == NULL) { + return LV_RESULT_INVALID; + } + uint32_t cache_line_size = get_cache_line_size(src_buf); + if (src_buf >= buf1 && src_buf < buf1 + pic_size) { + addr = buf1; + } else if (src_buf >= buf2 && src_buf < buf2 + pic_size) { + addr = buf2; + } else if ((uint32_t)src_buf == ALIGN_UP((uint32_t)src_buf, cache_line_size)) { // If fg buffer is aligned, this is ok + *pic_addr = (uint8_t *)src_buf; + *offsetx = 0; + *offsety = 0; + return LV_RESULT_OK; + } else { + return LV_RESULT_INVALID; + } + cache_line_size = get_cache_line_size(addr); + if ((uint32_t)addr != ALIGN_UP((uint32_t)addr, cache_line_size)) { + return LV_RESULT_INVALID; + } + + int pixel_size = stride / pic_w; + uint32_t pixel_num = (src_buf - addr) / pixel_size; + *pic_addr = addr; + *offsetx = pixel_num % pic_w; + *offsety = pixel_num / pic_w; + return LV_RESULT_OK; +} + +static lv_result_t _check_arg(uint32_t dest_w, uint32_t dest_h, const uint8_t *mask_buf) +{ + int color_format = lv_display_get_color_format(NULL); + if (!((color_format == LV_COLOR_FORMAT_RGB565) || (color_format == LV_COLOR_FORMAT_RGB888)) ) { + return LV_RESULT_INVALID; + } + + uint32_t width = lv_display_get_horizontal_resolution(NULL); + if (dest_w * dest_h < width * 20 || mask_buf != NULL) { + return LV_RESULT_INVALID; + } + return LV_RESULT_OK; +} + +static lv_result_t ppa_blend_get_color_mode_by_px_size(int px_size, uint32_t *mode) +{ + ppa_blend_color_mode_t color_mode = PPA_BLEND_COLOR_MODE_RGB565; + if (px_size == 2) { + color_mode = PPA_BLEND_COLOR_MODE_RGB565; + } else if (px_size == 3) { + color_mode = PPA_BLEND_COLOR_MODE_RGB888; + } else if (px_size == 4) { + color_mode = PPA_BLEND_COLOR_MODE_ARGB8888; + } else { + return LV_RESULT_INVALID; + } + *mode = color_mode; + return LV_RESULT_OK; +} + +static lv_result_t ppa_get_default_color_mode(uint32_t *color_mode) +{ + lv_color_format_t cf = lv_display_get_color_format(NULL); + if (cf == LV_COLOR_FORMAT_RGB565) { + *color_mode = PPA_FILL_COLOR_MODE_RGB565; + } else if (cf == LV_COLOR_FORMAT_RGB888) { + *color_mode = PPA_FILL_COLOR_MODE_RGB888; + } else if (cf == LV_COLOR_FORMAT_ARGB8888) { + *color_mode = PPA_FILL_COLOR_MODE_ARGB8888; + } else { + return LV_RESULT_INVALID; + } + return LV_RESULT_OK; +} + +int esp_dma2d_convert_for_lvgl(asm_dsc_t *dsc, const uint8_t dest_px_size, uint32_t src_px_size) +{ + if (_check_arg(dsc->dest_w, dsc->dest_h, dsc->mask_buf) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + if (dest_px_size < 2 || dest_px_size > 3 || src_px_size < 2 || src_px_size > 3) { + return LV_RESULT_INVALID; + } + + dma2d_pic_config_t src = { + .format = { + .color_space = COLOR_SPACE_RGB, + .pixel_format = src_px_size == 2 ? COLOR_PIXEL_RGB565 : COLOR_PIXEL_RGB888, + }, + .pic_buf = (uint8_t *) dsc->src_buf, + .pic_w = dsc->src_stride / src_px_size, + .pic_h = dsc->dest_h, + .offset_x = 0, + .offset_y = 0, + .block_w = dsc->dest_w, + .block_h = dsc->dest_h, + }; + + dma2d_pic_config_t dest = { + .format = { + .color_space = COLOR_SPACE_RGB, + .pixel_format = dest_px_size == 2 ? COLOR_PIXEL_RGB565 : COLOR_PIXEL_RGB888, + }, + .block_w = dsc->dest_w, + .block_h = dsc->dest_h, + }; + + uint32_t stride; + if (bsp_get_lvgl_info(&dest.pic_w, &dest.pic_h, &stride) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + if (find_start_addr_and_offset(dsc->dest_buf, &dest.pic_buf, &dest.offset_x, &dest.offset_y) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + if (dma2d_color_convert(&src, &dest) == ESP_FAIL) { + return LV_RESULT_INVALID; + } + + return LV_RESULT_OK; +} + +int esp_ppa_fill_opa_for_lvgl(asm_dsc_t *dsc, uint32_t dest_px_size) +{ + if (_check_arg(dsc->dest_w, dsc->dest_h, dsc->mask_buf) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + int64_t start = esp_timer_get_time(); + + uint32_t dest_w = dsc->dest_w; + uint32_t dest_h = dsc->dest_h; + uint32_t pic_w, pic_h, stride; + if (bsp_get_lvgl_info(&pic_w, &pic_h, &stride) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + uint8_t *bg_pic = NULL; + uint32_t bg_offx = 0; + uint32_t bg_offy = 0; + uint32_t bg_color_mode = PPA_BLEND_COLOR_MODE_RGB888; + if (ppa_get_default_color_mode(&bg_color_mode) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + static uint32_t fg_max_size = 0; + uint32_t out_buf_size_align = dest_w * dest_h; // fg color format is A8 + size_t cache_line_size = 0; + esp_cache_get_alignment(MALLOC_CAP_SPIRAM, &cache_line_size); + out_buf_size_align = ALIGN_UP(out_buf_size_align, cache_line_size); + if (fg_max_size < out_buf_size_align) { + fg_max_size = out_buf_size_align; + if (fg_pic_buf != NULL) { + free(fg_pic_buf); + } + fg_pic_buf = heap_caps_aligned_calloc(cache_line_size, fg_max_size, sizeof(uint8_t), MALLOC_CAP_SPIRAM); + } + + ppa_blend_color_mode_t fg_color_mode = PPA_BLEND_COLOR_MODE_A8; + ppa_alpha_update_mode_t fg_updata_mode = PPA_ALPHA_FIX_VALUE; + + lv_color_t lv_color = {0}; + memcpy(&lv_color, dsc->src_buf, sizeof(lv_color_t)); + color_pixel_rgb888_data_t fg_rgb_data = { + .r = lv_color.red, + .g = lv_color.green, + .b = lv_color.blue, + }; + + if (find_start_addr_and_offset(dsc->dest_buf, &bg_pic, &bg_offx, &bg_offy) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + ppa_client_handle_t ppa_client_handle; + ppa_client_config_t ppa_client_config = { + .oper_type = PPA_OPERATION_BLEND, + .max_pending_trans_num = 1, + }; + esp_err_t err = ppa_register_client(&ppa_client_config, &ppa_client_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ppa register client err: %d\n", err); + goto _err; + } + + uint32_t outbuf_size = ALIGN_UP(stride * pic_h, cache_line_size); + + ppa_blend_oper_config_t oper_config = { + .in_bg.buffer = bg_pic, + .in_bg.pic_w = pic_w, + .in_bg.pic_h = pic_h, + .in_bg.block_w = dest_w, + .in_bg.block_h = dest_h, + .in_bg.block_offset_x = bg_offx, + .in_bg.block_offset_y = bg_offy, + .in_bg.blend_cm = bg_color_mode, + .bg_rgb_swap = 0, + .bg_byte_swap = 0, + .bg_alpha_update_mode = PPA_ALPHA_NO_CHANGE, + + .in_fg.buffer = fg_pic_buf, + .in_fg.pic_w = dest_w, + .in_fg.pic_h = dest_h, + .in_fg.block_w = dest_w, + .in_fg.block_h = dest_h, + .in_fg.block_offset_x = 0, + .in_fg.block_offset_y = 0, + .in_fg.blend_cm = fg_color_mode, + .fg_rgb_swap = 0, + .fg_byte_swap = 0, + .fg_alpha_update_mode = fg_updata_mode, + .fg_alpha_fix_val = dsc->opa, + .fg_fix_rgb_val = fg_rgb_data, + + .out.buffer = bg_pic, + .out.buffer_size = outbuf_size, + .out.pic_w = pic_w, + .out.pic_h = pic_h, + .out.block_offset_x = bg_offx, + .out.block_offset_y = bg_offy, + .out.blend_cm = bg_color_mode, + + .mode = PPA_TRANS_MODE_BLOCKING, + }; + err = ppa_do_blend(ppa_client_handle, &oper_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ppa do blend err: %d\n", err); + goto _err; + } + err = ppa_unregister_client(ppa_client_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ppa unregister client err: %d\n", err); + goto _err; + } + int duration = esp_timer_get_time() - start; + uint32_t data_size = dest_w * dest_h * (dest_px_size + 1); + float speed = data_size * 0.95367 / duration; + static uint32_t count = 0; + if (count++ % PPA_LOG_CYCLE == 0) { + ESP_LOGD(TAG, "%s, %d ms, (x:%ld, y:%ld)[w:%ld, h:%ld][spx:%d, dpx:%ld], %.1f MB/s", + __FUNCTION__, duration / 1000, bg_offx, bg_offy, dest_w, dest_h, 1, dest_px_size, speed); + } + + return LV_RESULT_OK; +_err: + return LV_RESULT_INVALID; +} + +int esp_ppa_blend_area_for_lvgl(asm_dsc_t *dsc, uint32_t dest_px_size, uint8_t src_px_size) +{ + if (_check_arg(dsc->dest_w, dsc->dest_h, dsc->mask_buf) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + uint32_t pic_w, pic_h, stride; + if (bsp_get_lvgl_info(&pic_w, &pic_h, &stride) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + int64_t start = esp_timer_get_time(); + + uint8_t *bg_pic = NULL; + uint32_t bg_offx = 0; + uint32_t bg_offy = 0; + uint32_t bg_color_mode = PPA_BLEND_COLOR_MODE_RGB565; + if (ppa_get_default_color_mode(&bg_color_mode) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + uint8_t *fg_pic = NULL; + uint32_t fg_offx = 0; + uint32_t fg_offy = 0; + uint32_t fg_color_mode = PPA_BLEND_COLOR_MODE_RGB565; + if (ppa_blend_get_color_mode_by_px_size(src_px_size, &fg_color_mode) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + ppa_alpha_update_mode_t fg_updata_mode = PPA_ALPHA_NO_CHANGE; + + uint32_t dest_w = dsc->dest_w; + uint32_t dest_h = dsc->dest_h; + + if (find_start_addr_and_offset(dsc->dest_buf, &bg_pic, &bg_offx, &bg_offy) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + uint16_t cache_line_size = get_cache_line_size(bg_pic); + + if (find_start_addr_and_offset(dsc->src_buf, &fg_pic, &fg_offx, &fg_offy) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + ppa_client_handle_t ppa_client_handle; + ppa_client_config_t ppa_client_config = { + .oper_type = PPA_OPERATION_BLEND, + .max_pending_trans_num = 1, + }; + esp_err_t err = ppa_register_client(&ppa_client_config, &ppa_client_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ppa register client err: %d\n", err); + goto _err; + } + + uint32_t outbuf_size = ALIGN_UP(stride * pic_h, cache_line_size); + + ppa_blend_oper_config_t oper_config = { + .in_bg.buffer = bg_pic, + .in_bg.pic_w = pic_w, + .in_bg.pic_h = pic_h, + .in_bg.block_w = dest_w, + .in_bg.block_h = dest_h, + .in_bg.block_offset_x = bg_offx, + .in_bg.block_offset_y = bg_offy, + .in_bg.blend_cm = bg_color_mode, + .bg_rgb_swap = 0, + .bg_byte_swap = 0, + .bg_alpha_update_mode = PPA_ALPHA_NO_CHANGE, + + .in_fg.buffer = fg_pic, + .in_fg.pic_w = dest_w, + .in_fg.pic_h = dest_h, + .in_fg.block_w = dest_w, + .in_fg.block_h = dest_h, + .in_fg.block_offset_x = 0, + .in_fg.block_offset_y = 0, + .in_fg.blend_cm = PPA_BLEND_COLOR_MODE_ARGB8888, + .fg_rgb_swap = 0, + .fg_byte_swap = 0, + .fg_alpha_update_mode = fg_updata_mode, + + .out.buffer = bg_pic, + .out.buffer_size = outbuf_size, + .out.pic_w = pic_w, + .out.pic_h = pic_h, + .out.block_offset_x = bg_offx, + .out.block_offset_y = bg_offy, + .out.blend_cm = bg_color_mode, + + .mode = PPA_TRANS_MODE_BLOCKING, + }; + err = ppa_do_blend(ppa_client_handle, &oper_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ppa do blend err: %d\n", err); + goto _err; + } + err = ppa_unregister_client(ppa_client_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ppa unregister client err: %d\n", err); + goto _err; + } + int duration = esp_timer_get_time() - start; + uint32_t data_size = dest_w * dest_h * (dest_px_size * 2 + src_px_size); + float speed = data_size * 0.95367 / duration; + static uint32_t count = 0; + if (count++ % PPA_LOG_CYCLE == 0) { + ESP_LOGD(TAG, "%s, %d ms, (x:%ld, y:%ld)[w:%ld, h:%ld][spx:%d, dpx:%ld], %.1f MB/s", + __FUNCTION__, duration / 1000, bg_offx, bg_offy, dest_w, dest_h, src_px_size, dest_px_size, speed); + } + + return LV_RESULT_OK; +_err: + return LV_RESULT_INVALID; +} + +static inline uint32_t convert_color_to_uint32(lv_color_t color, uint8_t opa) +{ + return (uint32_t)((uint32_t)opa << 24) + ((uint32_t)color.red << 16) + ((uint32_t)color.green << 8) + (color.blue); +} + +int esp_ppa_fill_for_lvgl(asm_dsc_t *dsc, uint32_t dest_px_size) +{ + if (_check_arg(dsc->dest_w, dsc->dest_h, dsc->mask_buf) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + uint32_t pic_w, pic_h, stride; + if (bsp_get_lvgl_info(&pic_w, &pic_h, &stride) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + uint8_t *pic_addr = NULL; + uint32_t offsetx = 0; + uint32_t offsety = 0; + uint32_t dest_w = dsc->dest_w; + uint32_t dest_h = dsc->dest_h; + + if (find_start_addr_and_offset(dsc->dest_buf, &pic_addr, &offsetx, &offsety) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + int64_t start = esp_timer_get_time(); + + uint32_t color_mode = PPA_FILL_COLOR_MODE_RGB565; + if (ppa_get_default_color_mode(&color_mode) == LV_RESULT_INVALID) { + return LV_RESULT_INVALID; + } + + lv_color_t lv_color = {0}; + memcpy(&lv_color, dsc->src_buf, sizeof(lv_color_t)); + uint32_t color = convert_color_to_uint32(lv_color, dsc->opa); + + ppa_client_handle_t ppa_client_handle = NULL; + + int cache_line_size = get_cache_line_size(pic_addr); + uint32_t out_buf_size_align = ALIGN_UP(stride * pic_h, cache_line_size); + ppa_client_config_t ppa_client_config = { + .oper_type = PPA_OPERATION_FILL, + .max_pending_trans_num = 1, + }; + esp_err_t err = ppa_register_client(&ppa_client_config, &ppa_client_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ppa register client err: %d\n", err); + } + + ppa_fill_oper_config_t fill_trans_config = { + .fill_block_w = dest_w, + .fill_block_h = dest_h, + .fill_argb_color = { + .val = color, + }, + .mode = PPA_TRANS_MODE_BLOCKING, + + .out = { + .buffer = pic_addr, + .buffer_size = out_buf_size_align, + .pic_w = pic_w, + .pic_h = pic_h, + .block_offset_x = offsetx, + .block_offset_y = offsety, + .fill_cm = color_mode, + } + }; + err = ppa_do_fill(ppa_client_handle, &fill_trans_config); + int duration = esp_timer_get_time() - start; + err = ppa_unregister_client(ppa_client_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ppa unregister client err: %d\n", err); + } + uint32_t data_size = dsc->dest_w * dsc->dest_h * dest_px_size; + float speed = data_size * 0.95367 / duration; + static uint32_t count = 0; + if (count++ % PPA_LOG_CYCLE == 0) { + ESP_LOGD(TAG, "%s, %d ms, (x:%ld, y:%ld)[w:%ld, h:%ld][spx:%d, dpx:%ld], %ld KB, %.1f MB/s", + __FUNCTION__, duration / 1000, offsetx, offsety, dest_w, dest_h, 0, dest_px_size, data_size / 1024, speed); + } + if (err != ESP_OK) { + return LV_RESULT_INVALID; + } + return LV_RESULT_OK; +} + +void esp_ppa_mem_release(void) +{ + if (fg_pic_buf) { + free(fg_pic_buf); + fg_pic_buf = NULL; + } +} diff --git a/components/esp_lvgl_port/src/lvgl9/ppa/esp_malloc.c b/components/esp_lvgl_port/src/lvgl9/ppa/esp_malloc.c new file mode 100644 index 000000000..accdb084e --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/ppa/esp_malloc.c @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_heap_caps.h" +#include "lvgl.h" + +#if LV_USE_STDLIB_MALLOC == LV_STDLIB_CUSTOM +void lv_mem_init(void) +{ + return; /*Nothing to init*/ +} + +void lv_mem_deinit(void) +{ + return; /*Nothing to deinit*/ +} + +lv_mem_pool_t lv_mem_add_pool(void *mem, size_t bytes) +{ + /*Not supported*/ + LV_UNUSED(mem); + LV_UNUSED(bytes); + return NULL; +} + +void lv_mem_remove_pool(lv_mem_pool_t pool) +{ + /*Not supported*/ + LV_UNUSED(pool); + return; +} + +void *lv_malloc_core(size_t size) +{ + if (size > 128 * 1024) { + return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_CACHE_ALIGNED); + } + return malloc(size); +} + +void *lv_realloc_core(void *p, size_t new_size) +{ + if (new_size > 128 * 1024) { + free(p); + return heap_caps_malloc(new_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_CACHE_ALIGNED); + } + return realloc(p, new_size); +} + +void lv_free_core(void *p) +{ + free(p); +} + +void lv_mem_monitor_core(lv_mem_monitor_t *mon_p) +{ + /*Not supported*/ + LV_UNUSED(mon_p); +} + +lv_result_t lv_mem_test_core(void) +{ + /*Not supported*/ + return LV_RESULT_OK; +} +#endif // LV_STDLIB_CUSTOM diff --git a/examples/display_lvgl_benchmark/sdkconfig.defaults.esp32p4 b/examples/display_lvgl_benchmark/sdkconfig.defaults.esp32p4 new file mode 100644 index 000000000..68757a287 --- /dev/null +++ b/examples/display_lvgl_benchmark/sdkconfig.defaults.esp32p4 @@ -0,0 +1,59 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# + +# +# Cache config +# +CONFIG_CACHE_L2_CACHE_256KB=y +CONFIG_CACHE_L2_CACHE_LINE_128B=y + +# +# ESP PSRAM +# +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_HEX=y +CONFIG_SPIRAM_SPEED_200M=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_IDF_EXPERIMENTAL_FEATURES=y + +# +# Rendering Configuration +# +# Set custom ASM render and provide a header file with function prototypes +CONFIG_LV_DRAW_SW_ASM_CUSTOM=y +CONFIG_LV_DRAW_SW_ASM_CUSTOM_INCLUDE="esp_lvgl_port_lv_blend_ppa.h" + +# +# Memory Settings +# +CONFIG_LV_USE_CUSTOM_MALLOC=y +CONFIG_LV_USE_CLIB_SPRINTF=y +CONFIG_LV_USE_CLIB_STRING=y + +# +# Display +# +CONFIG_BSP_LCD_DPI_BUFFER_NUMS=2 +CONFIG_BSP_DISPLAY_LVGL_AVOID_TEAR=y +CONFIG_BSP_DISPLAY_LVGL_DIRECT_MODE=y +CONFIG_BSP_LCD_COLOR_FORMAT_RGB888=n +CONFIG_LV_COLOR_DEPTH_24=n +CONFIG_BSP_LCD_TYPE_1024_600=y + +# Enable logging +CONFIG_LV_USE_LOG=y +CONFIG_LV_LOG_PRINTF=y + +## LVGL8 ## +CONFIG_LV_MEM_SIZE_KILOBYTES=48 +CONFIG_LV_USE_PERF_MONITOR=y + +## LVGL9 ## +CONFIG_LV_CONF_SKIP=y +CONFIG_LV_DEF_REFR_PERIOD=10 + +# Performance monitor +CONFIG_LV_USE_OBSERVER=y +CONFIG_LV_USE_SYSMON=y +CONFIG_LV_USE_PERF_MONITOR=y