feat(rgb_lcd): support draw bitmap hook

This commit is contained in:
Chen Jichang
2026-08-24 11:44:30 +08:00
parent 560f558982
commit c997b4b026
18 changed files with 979 additions and 122 deletions
+31 -5
View File
@@ -542,7 +542,7 @@ esp_err_t esp_lcd_dpi_panel_enable_dma2d(esp_lcd_panel_handle_t panel)
esp_lcd_panel_hooks_t hooks = {
.draw_bitmap_hook = dpi_panel_draw_bitmap_dma2d_hook,
};
ESP_GOTO_ON_ERROR(esp_lcd_dpi_panel_register_hooks(panel, &hooks, dpi_panel->user_ctx), err, TAG, "register DMA2D draw bitmap hook failed");
ESP_GOTO_ON_ERROR(esp_lcd_dpi_panel_register_hooks(panel, &hooks, NULL), err, TAG, "register DMA2D draw bitmap hook failed");
return ESP_OK;
@@ -563,14 +563,19 @@ esp_err_t esp_lcd_dpi_panel_disable_dma2d(esp_lcd_panel_handle_t panel)
// Check if built-in DMA2D draw bitmap hook is registered
ESP_RETURN_ON_FALSE(dpi_panel->fbcpy_handle, ESP_ERR_INVALID_STATE, TAG, "draw bitmap DMA2D hook not registered");
// Clear the hook first so new draws stop entering the DMA2D path before uninstall.
esp_lcd_panel_hooks_t hooks = {
.draw_bitmap_hook = NULL,
};
ESP_RETURN_ON_ERROR(esp_lcd_dpi_panel_register_hooks(panel, &hooks, NULL), TAG, "unregister DMA2D draw bitmap hook failed");
if (dpi_panel->fbcpy_handle) {
ESP_RETURN_ON_ERROR(esp_async_color_convert_uninstall(dpi_panel->fbcpy_handle), TAG, "uninstall DMA2D failed");
dpi_panel->fbcpy_handle = NULL;
esp_err_t ret = esp_async_color_convert_uninstall(dpi_panel->fbcpy_handle);
if (ret != ESP_OK) {
// Restore the hook so draw_bitmap_hook and fbcpy_handle stay consistent on failure
hooks.draw_bitmap_hook = dpi_panel_draw_bitmap_dma2d_hook;
esp_lcd_dpi_panel_register_hooks(panel, &hooks, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "uninstall DMA2D failed");
}
dpi_panel->fbcpy_handle = NULL;
dpi_panel->on_hook_end = NULL;
return ESP_OK;
@@ -600,14 +605,35 @@ static esp_err_t dpi_panel_draw_bitmap_2d(esp_lcd_panel_t *panel, int x_start, i
size_t fb_size = dpi_panel->fb_size;
size_t bits_per_pixel = dpi_panel->bits_per_pixel;
// clip to boundaries
int h_res = dpi_panel->h_pixels;
int v_res = dpi_panel->v_pixels;
// save the original coordinates before clipping
int unclipped_x_start = x_start;
int unclipped_y_start = y_start;
int unclipped_x_end = x_end;
int unclipped_y_end = y_end;
// clip to boundaries
x_start = MAX(x_start, 0);
x_end = MIN(x_end, h_res);
y_start = MAX(y_start, 0);
y_end = MIN(y_end, v_res);
// adjust the source coordinates to the clipped region
src_x_start += x_start - unclipped_x_start;
src_y_start += y_start - unclipped_y_start;
src_x_end -= unclipped_x_end - x_end;
src_y_end -= unclipped_y_end - y_end;
if (x_start >= x_end || y_start >= y_end || src_x_start >= src_x_end || src_y_start >= src_y_end) {
// no valid region to draw, skip
if (dpi_panel->on_color_trans_done) {
dpi_panel->on_color_trans_done(&dpi_panel->base, NULL, dpi_panel->user_ctx);
}
return ESP_OK;
}
bool do_copy = false;
uint8_t draw_buf_fb_index = 0;
// check if the user draw buffer resides in any frame buffer's memory range
@@ -221,52 +221,6 @@ typedef struct {
*/
esp_err_t esp_lcd_dpi_panel_register_event_callbacks(esp_lcd_panel_handle_t dpi_panel, const esp_lcd_dpi_panel_event_callbacks_t *cbs, void *user_ctx);
/**
* @brief Type of draw bitmap hook data
*/
typedef struct {
void *dst_data; /*!< Destination buffer (usually frame buffer) */
int dst_x_size; /*!< Destination bitmap width */
int dst_y_size; /*!< Destination bitmap height */
int dst_x_start; /*!< Destination start x coordinate */
int dst_y_start; /*!< Destination start y coordinate */
int dst_x_end; /*!< Destination end x coordinate (exclusive) */
int dst_y_end; /*!< Destination end y coordinate (exclusive) */
const void *src_data; /*!< Source bitmap data */
int src_x_size; /*!< Source bitmap width */
int src_y_size; /*!< Source bitmap height */
int src_x_start; /*!< Source start x coordinate */
int src_y_start; /*!< Source start y coordinate */
int src_x_end; /*!< Source end x coordinate (exclusive) */
int src_y_end; /*!< Source end y coordinate (exclusive) */
int bits_per_pixel; /*!< Bits per pixel */
bool (*on_hook_end)(esp_lcd_panel_handle_t panel); /*!< Callback to be invoked when the hook completes its operation */
} esp_lcd_draw_bitmap_hook_data_t;
/**
* @brief draw bitmap hook function type for custom pixel processing operations
*
* This hook allows users to implement custom operations like scaling, rotation,
* color space conversion, etc. using hardware accelerators like PPA or DMA2D.
*
* @note The hook should ensure the synchronization of draw operations on its own.
*
* @param[in] panel LCD panel handle
* @param[in] hook_data Hook data
* @param[in] hook_ctx Hook context
* @return
* - ESP_OK on success
* - Other error codes on failure
*/
typedef esp_err_t (*esp_lcd_panel_draw_bitmap_hook_t)(esp_lcd_panel_handle_t panel, const esp_lcd_draw_bitmap_hook_data_t *hook_data, void* hook_ctx);
/**
* @brief Type of LCD panel hooks
*/
typedef struct {
esp_lcd_panel_draw_bitmap_hook_t draw_bitmap_hook; /*!< Draw bitmap hook function */
} esp_lcd_panel_hooks_t;
/**
* @brief Register panel hooks to the DPI panel
*
@@ -286,7 +240,7 @@ esp_err_t esp_lcd_dpi_panel_register_hooks(esp_lcd_panel_handle_t dpi_panel, con
/**
* @brief Enable DMA2D for DPI panel
*
* @note The function will register a built-in DMA2D draw bitmap hook to perform draw bitmap operations using DMA2D.
* @note The function will register a built-in DMA2D draw bitmap hook to perform bitmap copy using DMA2D.
*
* @param[in] dpi_panel LCD DPI panel handle, which is returned from esp_lcd_new_panel_dpi()
* @return
+54 -1
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -7,6 +7,7 @@
#include <stdbool.h>
#include "esp_assert.h"
#include "esp_err.h"
#include "hal/lcd_types.h"
#include "hal/gpio_types.h"
#include "hal/color_types.h"
@@ -72,6 +73,58 @@ typedef struct {
lcd_yuv_conv_std_t conv_std; /*!< YUV conversion standard: BT601, BT709 */
} esp_lcd_color_conv_yuv_config_t;
/**
* @brief Type of draw bitmap hook data
*/
typedef struct {
void *dst_data; /*!< Destination buffer (usually frame buffer) */
int dst_x_size; /*!< Destination bitmap width */
int dst_y_size; /*!< Destination bitmap height */
int dst_x_start; /*!< Destination start x coordinate */
int dst_y_start; /*!< Destination start y coordinate */
int dst_x_end; /*!< Destination end x coordinate (exclusive) */
int dst_y_end; /*!< Destination end y coordinate (exclusive) */
const void *src_data; /*!< Source bitmap data */
int src_x_size; /*!< Source bitmap width */
int src_y_size; /*!< Source bitmap height */
int src_x_start; /*!< Source start x coordinate */
int src_y_start; /*!< Source start y coordinate */
int src_x_end; /*!< Source end x coordinate (exclusive) */
int src_y_end; /*!< Source end y coordinate (exclusive) */
int bits_per_pixel; /*!< Bits per pixel */
bool (*on_hook_end)(esp_lcd_panel_handle_t panel); /*!< Callback to be invoked by an asynchronous hook after the custom draw
operation completes. This notifies the panel driver to finish the draw
transaction. If a color transfer done callback has been registered, it
also invokes that callback */
} esp_lcd_draw_bitmap_hook_data_t;
/**
* @brief draw bitmap hook function type for custom pixel processing operations
*
* This hook allows users to implement custom operations like scaling, rotation,
* color space conversion, etc. using hardware accelerators like PPA or DMA2D.
*
* @note For asynchronous operations, the hook should call hook_data->on_hook_end() after the operation is complete.
* The panel driver does not wait for a previous draw to finish; the hook must handle synchronization itself.
* The simplest approach is to serialize draws. To queue multiple transactions (e.g. via PPA), keep per-transaction
* hook_data, keep source buffers valid until completion, and handle overlapping destinations carefully.
*
* @param[in] panel LCD panel handle
* @param[in] hook_data Hook data
* @param[in] hook_ctx Hook context
* @return
* - ESP_OK on success
* - Other error codes on failure
*/
typedef esp_err_t (*esp_lcd_panel_draw_bitmap_hook_t)(esp_lcd_panel_handle_t panel, const esp_lcd_draw_bitmap_hook_data_t *hook_data, void* hook_ctx);
/**
* @brief Type of LCD panel hooks
*/
typedef struct {
esp_lcd_panel_draw_bitmap_hook_t draw_bitmap_hook; /*!< Draw bitmap hook function */
} esp_lcd_panel_hooks_t;
#ifdef __cplusplus
}
#endif
+28
View File
@@ -4,6 +4,15 @@ entries:
if LCD_DSI_ISR_HANDLER_IN_IRAM = y:
esp_lcd_panel_dpi: mipi_dsi_dma_trans_done_cb (noflash)
esp_lcd_panel_dpi: mipi_dsi_bridge_isr_handler (noflash)
esp_lcd_panel_dpi: dpi_panel_draw_bitmap_hook_end (noflash)
if SOC_DMA2D_SUPPORTED = y:
esp_lcd_panel_dpi: async_fbcpy_done_cb (noflash)
[mapping:esp_lcd_async_color_convert]
archive: libesp_driver_dma.a
entries:
if (LCD_DSI_ISR_HANDLER_IN_IRAM = y || LCD_RGB_ISR_IRAM_SAFE = y) && SOC_DMA2D_SUPPORTED = y:
async_color_convert_dma2d: async_color_convert_done_cb (noflash)
[mapping:esp_lcd_dsi_dma]
archive: libesp_driver_dma.a
@@ -21,7 +30,26 @@ entries:
if LCD_RGB_ISR_IRAM_SAFE = y:
gdma: gdma_reset (noflash)
gdma: gdma_start (noflash)
gdma: gdma_request_link_switch_event (noflash)
gdma_link: gdma_link_get_head_addr (noflash)
gdma_link: gdma_link_concat (noflash)
[mapping:esp_lcd_rgb_dma_hal]
archive: libesp_hal_dma.a
entries:
if LCD_RGB_ISR_IRAM_SAFE = y:
gdma_hal_top: gdma_hal_request_link_switch_event (noflash)
if SOC_AXI_GDMA_SUPPORTED = y:
gdma_hal_axi: gdma_axi_hal_request_link_switch_event (noflash)
[mapping:esp_lcd_rgb]
archive: libesp_lcd.a
entries:
if LCD_RGB_ISR_IRAM_SAFE = y:
esp_lcd_panel_rgb: rgb_panel_update_dma_link (noflash)
esp_lcd_panel_rgb: rgb_panel_draw_bitmap_hook_end (noflash)
if SOC_DMA2D_SUPPORTED = y:
esp_lcd_panel_rgb: async_fbcpy_done_cb (noflash)
[mapping:esp_lcd_rgb_hal_common]
archive: libhal.a
+236 -30
View File
@@ -48,6 +48,7 @@
#include "hal/color_hal.h"
#include "rgb_lcd_rotation_sw.h"
#include "esp_private/sleep_retention.h"
#include "esp_async_color_convert.h"
#if SOC_HAS(AXI_GDMA)
#include "hal/axi_dma_ll.h"
@@ -89,6 +90,8 @@ static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel);
static esp_err_t rgb_panel_reset(esp_lcd_panel_t *panel);
static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel);
static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
static esp_err_t rgb_panel_draw_bitmap_2d(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data,
size_t src_x_size, size_t src_y_size, int src_x_start, int src_y_start, int src_x_end, int src_y_end);
static esp_err_t rgb_panel_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
static esp_err_t rgb_panel_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t rgb_panel_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
@@ -171,6 +174,11 @@ struct esp_rgb_panel_t {
uint32_t bb_behind_cache: 1; // Whether the bounce buffer is behind the cache
uint32_t user_fb: 1; // Whether the frame buffer is provided by user
} flags;
// hook fields
esp_lcd_panel_draw_bitmap_hook_t draw_bitmap_hook; // Draw bitmap hook function
void* hook_ctx; // Hook context
bool (*on_hook_end)(esp_lcd_panel_handle_t panel); // Callback to be invoked when the draw bitmap hook completes its operation
async_color_convert_handle_t fbcpy_handle; // Async color convert handle used for same-format DMA2D frame buffer copy
};
static esp_err_t lcd_rgb_panel_alloc_frame_buffers(esp_rgb_panel_t *rgb_panel, const esp_lcd_rgb_panel_config_t *panel_config)
@@ -461,6 +469,7 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
rgb_panel->base.reset = rgb_panel_reset;
rgb_panel->base.init = rgb_panel_init;
rgb_panel->base.draw_bitmap = rgb_panel_draw_bitmap;
rgb_panel->base.draw_bitmap_2d = rgb_panel_draw_bitmap_2d;
rgb_panel->base.disp_on_off = rgb_panel_disp_on_off;
rgb_panel->base.invert_color = rgb_panel_invert_color;
rgb_panel->base.mirror = rgb_panel_mirror;
@@ -611,6 +620,10 @@ static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
int panel_id = rgb_panel->panel_id;
// check if the panel is using DMA2D draw bitmap hook
if (rgb_panel->fbcpy_handle) {
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_STATE, TAG, "please call `esp_lcd_rgb_panel_disable_dma2d()` before deleting the panel");
}
ESP_RETURN_ON_ERROR(lcd_rgb_panel_destroy(rgb_panel), TAG, "destroy rgb panel(%d) failed", panel_id);
ESP_LOGD(TAG, "del rgb panel(%d)", panel_id);
return ESP_OK;
@@ -680,17 +693,73 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
}
static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
{
size_t src_x_size = x_end - x_start;
size_t src_y_size = y_end - y_start;
size_t src_x_start = 0;
size_t src_y_start = 0;
size_t src_x_end = src_x_size;
size_t src_y_end = src_y_size;
ESP_RETURN_ON_ERROR(rgb_panel_draw_bitmap_2d(panel, x_start, y_start, x_end, y_end, color_data, src_x_size, src_y_size, src_x_start, src_y_start, src_x_end, src_y_end),
TAG, "draw bitmap failed");
return ESP_OK;
}
static void rgb_panel_update_dma_link(esp_rgb_panel_t *rgb_panel)
{
if (!rgb_panel->bb_size && rgb_panel->flags.stream_mode) {
for (int i = 0; i < rgb_panel->num_fbs; i++) {
// Note, because of DMA prefetch, there's possibility that the old frame buffer might be sent out again
// it's hard to know the time when the new frame buffer starts
gdma_link_concat(rgb_panel->dma_fb_links[i], -1, rgb_panel->dma_fb_links[rgb_panel->cur_fb_index], 0);
}
#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
gdma_request_link_switch_event(rgb_panel->dma_chan);
#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
}
}
static bool rgb_panel_draw_bitmap_hook_end(esp_lcd_panel_t *panel)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
rgb_panel_update_dma_link(rgb_panel);
if (rgb_panel->on_color_trans_done) {
return rgb_panel->on_color_trans_done(panel, NULL, rgb_panel->user_ctx);
}
return false;
}
#if SOC_HAS(DMA2D)
static bool async_fbcpy_done_cb(async_color_convert_handle_t conv_hdl, async_color_convert_event_data_t *event, void *cb_args)
{
bool need_yield = false;
esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)cb_args;
(void)conv_hdl;
(void)event;
if (rgb_panel->on_hook_end) {
if (rgb_panel->on_hook_end(&rgb_panel->base)) {
need_yield = true;
}
}
return need_yield;
}
#endif // SOC_HAS(DMA2D)
static esp_err_t rgb_panel_draw_bitmap_2d(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data,
size_t src_x_size, size_t src_y_size, int src_x_start, int src_y_start, int src_x_end, int src_y_end)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
ESP_RETURN_ON_FALSE(rgb_panel->num_fbs > 0, ESP_ERR_NOT_SUPPORTED, TAG, "no frame buffer installed");
esp_lcd_rgb_panel_draw_buf_complete_cb_t cb = rgb_panel->on_color_trans_done;
uint8_t cur_fb_index = rgb_panel->cur_fb_index;
uint8_t *frame_buffer = rgb_panel->fbs[cur_fb_index];
uint8_t *draw_buffer = (uint8_t *)color_data;
size_t fb_size = rgb_panel->fb_size;
int h_res = rgb_panel->timings.h_res;
int v_res = rgb_panel->timings.v_res;
int bytes_per_pixel = rgb_panel->fb_bits_per_pixel / 8;
uint32_t bytes_per_line = bytes_per_pixel * h_res;
// adjust the flush window by adding extra gap
x_start += rgb_panel->x_gap;
@@ -698,6 +767,12 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
x_end += rgb_panel->x_gap;
y_end += rgb_panel->y_gap;
// save the original coordinates before clipping
int unclipped_x_start = x_start;
int unclipped_y_start = y_start;
int unclipped_x_end = x_end;
int unclipped_y_end = y_end;
// clip to boundaries
if (rgb_panel->rotate_mask & ROTATE_MASK_SWAP_XY) {
x_start = MAX(x_start, 0);
@@ -718,6 +793,20 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
return ESP_OK;
}
// adjust the source coordinates to the clipped region
src_x_start += x_start - unclipped_x_start;
src_y_start += y_start - unclipped_y_start;
src_x_end -= unclipped_x_end - x_end;
src_y_end -= unclipped_y_end - y_end;
if (x_start >= x_end || y_start >= y_end || src_x_start >= src_x_end || src_y_start >= src_y_end) {
// no valid region to draw, skip
if (cb) {
cb(&rgb_panel->base, NULL, rgb_panel->user_ctx);
}
return ESP_OK;
}
// check if we want to copy the draw buffer to the internal frame buffer
bool draw_buf_copy_to_fb = true;
uint8_t draw_buf_fb_index = 0;
@@ -729,17 +818,44 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
}
}
if (draw_buf_copy_to_fb) {
if (rgb_panel->draw_bitmap_hook && draw_buf_copy_to_fb && !rgb_panel->rotate_mask) { // copy using draw bitmap hook
ESP_LOGV(TAG, "copy draw buffer by draw bitmap hook");
// Note, whether the previous draw operation is finished should be ensured by the hook.
// For the built-in DMA2D hook, cache maintenance of the source and destination
// buffers is handled inside the async color convert driver.
esp_lcd_draw_bitmap_hook_data_t hook_data = {
.dst_data = frame_buffer,
.dst_x_size = h_res,
.dst_y_size = v_res,
.dst_x_start = x_start,
.dst_y_start = y_start,
.dst_x_end = x_end,
.dst_y_end = y_end,
.src_data = draw_buffer,
.src_x_size = src_x_size,
.src_y_size = src_y_size,
.src_x_start = src_x_start,
.src_y_start = src_y_start,
.src_x_end = src_x_end,
.src_y_end = src_y_end,
.bits_per_pixel = rgb_panel->fb_bits_per_pixel,
.on_hook_end = rgb_panel_draw_bitmap_hook_end,
};
ESP_RETURN_ON_ERROR(rgb_panel->draw_bitmap_hook(panel, &hook_data, rgb_panel->hook_ctx), TAG, "draw_bitmap_hook failed");
return ESP_OK;
} else if (draw_buf_copy_to_fb) { // copy by CPU
// sync the draw buffer with the frame buffer by CPU copy
ESP_LOGV(TAG, "copy draw buffer to frame buffer by CPU");
uint8_t *fb = rgb_panel->fbs[rgb_panel->cur_fb_index];
size_t bytes_to_flush = v_res * bytes_per_line;
uint8_t *flush_ptr = fb;
const uint8_t *from = (const uint8_t *)color_data;
uint8_t *fb = frame_buffer;
const uint8_t *from_base = draw_buffer;
uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel;
size_t offset = y_start * copy_bytes_per_line + x_start * bytes_per_pixel;
uint8_t *to = fb;
uint32_t bytes_per_line = bytes_per_pixel * h_res;
uint32_t src_bytes_per_line = bytes_per_pixel * src_x_size;
size_t bytes_to_flush = 0;
uint8_t *flush_ptr = NULL;
if (1 == bytes_per_pixel) {
COPY_PIXEL_CODE_BLOCK(8)
} else if (2 == bytes_per_pixel) {
@@ -748,22 +864,21 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
COPY_PIXEL_CODE_BLOCK(24)
}
// do memory sync only when the frame buffer is mounted to the DMA link list and behind the cache
if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache) {
if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache && flush_ptr) {
esp_cache_msync(flush_ptr, bytes_to_flush, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
}
// after the draw buffer finished copying, notify the user to recycle the draw buffer
if (cb) {
cb(&rgb_panel->base, NULL, rgb_panel->user_ctx);
}
} else {
ESP_LOGV(TAG, "draw buffer is part of the frame buffer");
// the new frame buffer index is changed
} else { // no copy, just do cache memory write back
ESP_LOGV(TAG, "draw buffer is in frame buffer memory range, do cache write back only");
// only write back the LCD lines that updated by the draw buffer
rgb_panel->cur_fb_index = draw_buf_fb_index;
// when this function is called, the frame buffer already reflects the draw buffer changes
// if the frame buffer is also mounted to the DMA, we need to do the sync between them
if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache) {
uint8_t *cache_sync_start = rgb_panel->fbs[draw_buf_fb_index] + (y_start * h_res) * bytes_per_pixel;
size_t cache_sync_size = (y_end - y_start) * bytes_per_line;
uint8_t *cache_sync_start = rgb_panel->fbs[draw_buf_fb_index] + (y_start * h_res) * bytes_per_pixel;
size_t cache_sync_size = (y_end - y_start) * h_res * bytes_per_pixel;
// the buffer to be flushed is still within the frame buffer, so even an unaligned address is OK
if (rgb_panel->flags.fb_behind_cache) {
esp_cache_msync(cache_sync_start, cache_sync_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
}
// after the draw buffer finished copying, notify the user to recycle the draw buffer
@@ -772,18 +887,7 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
}
}
if (!rgb_panel->bb_size) {
if (rgb_panel->flags.stream_mode) {
for (int i = 0; i < rgb_panel->num_fbs; i++) {
// Note, because of DMA prefetch, there's possibility that the old frame buffer might be sent out again
// it's hard to know the time when the new frame buffer starts
gdma_link_concat(rgb_panel->dma_fb_links[i], -1, rgb_panel->dma_fb_links[rgb_panel->cur_fb_index], 0);
}
#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
ESP_RETURN_ON_ERROR(gdma_request_link_switch_event(rgb_panel->dma_chan), TAG, "request link switch event failed");
#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
}
}
rgb_panel_update_dma_link(rgb_panel);
return ESP_OK;
}
@@ -1409,6 +1513,108 @@ IRAM_ATTR static void rgb_lcd_default_isr_handler(void *args)
}
}
esp_err_t esp_lcd_rgb_panel_register_hooks(esp_lcd_panel_handle_t panel, const esp_lcd_panel_hooks_t *hooks, void *hook_ctx)
{
ESP_RETURN_ON_FALSE(panel && hooks, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
rgb_panel->draw_bitmap_hook = hooks->draw_bitmap_hook;
rgb_panel->hook_ctx = hook_ctx;
return ESP_OK;
}
#if SOC_HAS(DMA2D)
static esp_err_t rgb_panel_draw_bitmap_dma2d_hook(esp_lcd_panel_t *panel, const esp_lcd_draw_bitmap_hook_data_t *hook_data, void* hook_ctx)
{
ESP_LOGV(TAG, "copy draw buffer by DMA2D");
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
(void)hook_ctx;
async_color_convert_request_t fbcpy_trans_config = {
.src_buffer = hook_data->src_data,
.dst_buffer = hook_data->dst_data,
.src_stride = hook_data->src_x_size,
.src_height = hook_data->src_y_size,
.dst_stride = hook_data->dst_x_size,
.dst_height = hook_data->dst_y_size,
.src_x = hook_data->src_x_start,
.src_y = hook_data->src_y_start,
.dst_x = hook_data->dst_x_start,
.dst_y = hook_data->dst_y_start,
.copy_width = hook_data->src_x_end - hook_data->src_x_start,
.copy_height = hook_data->src_y_end - hook_data->src_y_start,
// For this DMA2D hook we only do window copy from draw buffer to frame buffer.
// Source and destination color formats are intentionally set to the same value to disable CSC.
.src_color_format = rgb_panel->in_color_format,
.dst_color_format = rgb_panel->in_color_format,
};
// The async color convert backend owns source/destination cache sync for the
// DMA2D copy path, so the LCD driver should not perform extra cache sync here.
// Save the completion callback and invoke it when the async frame buffer copy finishes.
rgb_panel->on_hook_end = hook_data->on_hook_end;
ESP_RETURN_ON_ERROR(esp_async_color_convert(rgb_panel->fbcpy_handle, &fbcpy_trans_config, async_fbcpy_done_cb, rgb_panel), TAG, "async frame buffer copy failed");
return ESP_OK;
}
esp_err_t esp_lcd_rgb_panel_enable_dma2d(esp_lcd_panel_handle_t panel)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel");
esp_err_t ret = ESP_OK;
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
// Check if built-in DMA2D draw bitmap hook is registered
ESP_RETURN_ON_FALSE(!rgb_panel->fbcpy_handle, ESP_ERR_INVALID_STATE, TAG, "draw bitmap DMA2D hook is already registered");
// Initialize the async color convert backend used by the built-in DMA2D copy hook.
// Use its default backlog to queue multiple frame buffer copy requests.
async_color_convert_config_t fbcpy_config = {};
ESP_RETURN_ON_ERROR(esp_async_color_convert_install_dma2d(&fbcpy_config, &rgb_panel->fbcpy_handle), TAG, "install async frame buffer copy backend failed");
// Register the DMA2D draw bitmap hook
esp_lcd_panel_hooks_t hooks = {
.draw_bitmap_hook = rgb_panel_draw_bitmap_dma2d_hook,
};
ESP_GOTO_ON_ERROR(esp_lcd_rgb_panel_register_hooks(panel, &hooks, NULL), err, TAG, "register DMA2D draw bitmap hook failed");
return ESP_OK;
err:
if (rgb_panel->fbcpy_handle) {
esp_async_color_convert_uninstall(rgb_panel->fbcpy_handle);
rgb_panel->fbcpy_handle = NULL;
}
rgb_panel->on_hook_end = NULL;
return ret;
}
esp_err_t esp_lcd_rgb_panel_disable_dma2d(esp_lcd_panel_handle_t panel)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
// Check if built-in DMA2D draw bitmap hook is registered
ESP_RETURN_ON_FALSE(rgb_panel->fbcpy_handle, ESP_ERR_INVALID_STATE, TAG, "draw bitmap DMA2D hook not registered");
// Clear the hook first so new draws stop entering the DMA2D path before uninstall.
esp_lcd_panel_hooks_t hooks = {
.draw_bitmap_hook = NULL,
};
ESP_RETURN_ON_ERROR(esp_lcd_rgb_panel_register_hooks(panel, &hooks, NULL), TAG, "unregister DMA2D draw bitmap hook failed");
esp_err_t ret = esp_async_color_convert_uninstall(rgb_panel->fbcpy_handle);
if (ret != ESP_OK) {
// Restore the hook so draw_bitmap_hook and fbcpy_handle stay consistent on failure
hooks.draw_bitmap_hook = rgb_panel_draw_bitmap_dma2d_hook;
esp_lcd_rgb_panel_register_hooks(panel, &hooks, NULL);
ESP_RETURN_ON_ERROR(ret, TAG, "uninstall DMA2D failed");
}
rgb_panel->fbcpy_handle = NULL;
rgb_panel->on_hook_end = NULL;
return ESP_OK;
}
#endif // SOC_HAS(DMA2D)
#if CONFIG_LCD_ENABLE_DEBUG_LOG
__attribute__((constructor))
static void rgb_lcd_override_default_log_level(void)
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -282,6 +282,52 @@ void *esp_lcd_rgb_alloc_draw_buffer(esp_lcd_panel_handle_t panel, size_t size, u
*/
esp_err_t esp_lcd_rgb_panel_set_yuv_conversion(esp_lcd_panel_handle_t panel, const esp_lcd_color_conv_yuv_config_t *config);
/**
* @brief Register panel hooks to the RGB panel
*
* @note You can register panel hooks to implement custom operations like scaling, rotation, color space conversion, etc.
* with hardware accelerators like PPA or DMA2D.
* The hook will be overridden when this function is called multiple times.
* @note If software rotation is enabled by mirror or swap_xy, the RGB panel driver bypasses the draw bitmap hook and
* falls back to the CPU copy path to apply the rotation transform.
*
* @param[in] panel LCD RGB panel handle, which is returned from esp_lcd_new_rgb_panel()
* @param[in] hooks Panel hooks
* @param[in] hook_ctx Hook context
* @return
* - ESP_OK: Register hooks successfully
* - Other error codes on failure
*/
esp_err_t esp_lcd_rgb_panel_register_hooks(esp_lcd_panel_handle_t panel, const esp_lcd_panel_hooks_t *hooks, void *hook_ctx);
#if SOC_DMA2D_SUPPORTED
/**
* @brief Enable DMA2D for RGB panel
*
* @note The function will register a built-in DMA2D draw bitmap hook to perform bitmap copy using DMA2D.
* @note If software rotation is enabled by mirror or swap_xy, the RGB panel driver bypasses the built-in DMA2D hook and
* falls back to the CPU copy path, because the built-in DMA2D hook does not apply rotation transforms.
*
* @param[in] panel LCD RGB panel handle, which is returned from esp_lcd_new_rgb_panel()
* @return
* - ESP_OK: Enable DMA2D successfully
* - Other error codes on failure
*/
esp_err_t esp_lcd_rgb_panel_enable_dma2d(esp_lcd_panel_handle_t panel);
/**
* @brief Disable DMA2D for RGB panel
*
* @note The function will unregister the built-in DMA2D draw bitmap hook.
*
* @param[in] panel LCD RGB panel handle, which is returned from esp_lcd_new_rgb_panel()
* @return
* - ESP_OK: Disable DMA2D successfully
* - Other error codes on failure
*/
esp_err_t esp_lcd_rgb_panel_disable_dma2d(esp_lcd_panel_handle_t panel);
#endif // SOC_DMA2D_SUPPORTED
#endif // SOC_LCD_RGB_SUPPORTED
#ifdef __cplusplus
+49 -29
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -44,71 +44,85 @@ static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from)
*to++ = *from++;
}
// Macro for draw_bitmap_2d that supports source image stride and offset
// Requires: from_base (source buffer base), src_x_start, src_y_start, src_bytes_per_line
#define COPY_PIXEL_CODE_BLOCK(_bpp) \
switch (rgb_panel->rotate_mask) \
{ \
case 0: \
{ \
uint8_t *to = fb + (y_start * h_res + x_start) * bytes_per_pixel; \
const uint8_t *from = from_base + src_y_start * src_bytes_per_line + src_x_start * bytes_per_pixel; \
for (int y = y_start; y < y_end; y++) \
{ \
memcpy(to, from, copy_bytes_per_line); \
to += bytes_per_line; \
from += copy_bytes_per_line; \
from += src_bytes_per_line; \
} \
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
flush_ptr = fb + y_start * bytes_per_line; \
} \
break; \
case ROTATE_MASK_MIRROR_X: \
for (int y = y_start; y < y_end; y++) \
{ \
uint32_t index = (y * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \
for (size_t x = x_start; x < x_end; x++) \
const uint8_t *from = from_base + src_y_start * src_bytes_per_line + src_x_start * bytes_per_pixel; \
for (int y = y_start; y < y_end; y++) \
{ \
copy_pixel_##_bpp##bpp(to + index, from); \
index -= bytes_per_pixel; \
from += bytes_per_pixel; \
uint32_t index = (y * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \
for (size_t x = x_start; x < x_end; x++) \
{ \
copy_pixel_##_bpp##bpp(fb + index, from); \
index -= bytes_per_pixel; \
from += bytes_per_pixel; \
} \
from += src_bytes_per_line - copy_bytes_per_line; \
} \
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
flush_ptr = fb + y_start * bytes_per_line; \
} \
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
flush_ptr = fb + y_start * bytes_per_line; \
break; \
case ROTATE_MASK_MIRROR_Y: \
{ \
uint8_t *to = fb + ((v_res - 1 - y_start) * h_res + x_start) * bytes_per_pixel; \
const uint8_t *from = from_base + src_y_start * src_bytes_per_line + src_x_start * bytes_per_pixel; \
for (int y = y_start; y < y_end; y++) \
{ \
memcpy(to, from, copy_bytes_per_line); \
memcpy(to, from, copy_bytes_per_line); \
to -= bytes_per_line; \
from += copy_bytes_per_line; \
from += src_bytes_per_line; \
} \
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
flush_ptr = fb + (v_res - y_end) * bytes_per_line; \
} \
break; \
case ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y: \
for (int y = y_start; y < y_end; y++) \
{ \
uint32_t index = ((v_res - 1 - y) * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \
for (size_t x = x_start; x < x_end; x++) \
const uint8_t *from = from_base + src_y_start * src_bytes_per_line + src_x_start * bytes_per_pixel; \
for (int y = y_start; y < y_end; y++) \
{ \
copy_pixel_##_bpp##bpp(to + index, from); \
index -= bytes_per_pixel; \
from += bytes_per_pixel; \
uint32_t index = ((v_res - 1 - y) * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \
for (size_t x = x_start; x < x_end; x++) \
{ \
copy_pixel_##_bpp##bpp(fb + index, from); \
index -= bytes_per_pixel; \
from += bytes_per_pixel; \
} \
from += src_bytes_per_line - copy_bytes_per_line; \
} \
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
flush_ptr = fb + (v_res - y_end) * bytes_per_line; \
} \
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
flush_ptr = fb + (v_res - y_end) * bytes_per_line; \
break; \
case ROTATE_MASK_SWAP_XY: \
for (int y = y_start; y < y_end; y++) \
{ \
for (int x = x_start; x < x_end; x++) \
{ \
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
uint32_t src_y = src_y_start + (y - y_start); \
uint32_t src_x = src_x_start + (x - x_start); \
uint32_t src_j = src_y * src_bytes_per_line + src_x * bytes_per_pixel; \
uint32_t i = (x * h_res + y) * bytes_per_pixel; \
copy_pixel_##_bpp##bpp(to + i, from + j); \
copy_pixel_##_bpp##bpp(fb + i, from_base + src_j); \
} \
} \
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
@@ -119,9 +133,11 @@ static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from)
{ \
for (int x = x_start; x < x_end; x++) \
{ \
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
uint32_t i = (x * h_res + h_res - 1 - y) * bytes_per_pixel; \
copy_pixel_##_bpp##bpp(to + i, from + j); \
uint32_t src_y = src_y_start + (y - y_start); \
uint32_t src_x = src_x_start + (x - x_start); \
uint32_t src_j = src_y * src_bytes_per_line + src_x * bytes_per_pixel; \
uint32_t i = (x * h_res + h_res - 1 - y) * bytes_per_pixel; \
copy_pixel_##_bpp##bpp(fb + i, from_base + src_j); \
} \
} \
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
@@ -132,9 +148,11 @@ static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from)
{ \
for (int x = x_start; x < x_end; x++) \
{ \
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
uint32_t src_y = src_y_start + (y - y_start); \
uint32_t src_x = src_x_start + (x - x_start); \
uint32_t src_j = src_y * src_bytes_per_line + src_x * bytes_per_pixel; \
uint32_t i = ((v_res - 1 - x) * h_res + y) * bytes_per_pixel; \
copy_pixel_##_bpp##bpp(to + i, from + j); \
copy_pixel_##_bpp##bpp(fb + i, from_base + src_j); \
} \
} \
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
@@ -145,9 +163,11 @@ static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from)
{ \
for (int x = x_start; x < x_end; x++) \
{ \
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
uint32_t src_y = src_y_start + (y - y_start); \
uint32_t src_x = src_x_start + (x - x_start); \
uint32_t src_j = src_y * src_bytes_per_line + src_x * bytes_per_pixel; \
uint32_t i = ((v_res - 1 - x) * h_res + h_res - 1 - y) * bytes_per_pixel; \
copy_pixel_##_bpp##bpp(to + i, from + j); \
copy_pixel_##_bpp##bpp(fb + i, from_base + src_j); \
} \
} \
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
@@ -292,7 +292,6 @@ typedef struct {
typedef struct {
uint32_t count;
SemaphoreHandle_t draw_sem;
} test_dpi_panel_color_trans_done_callback_ctx_t;
IRAM_ATTR static bool test_ppa_srm_trans_done_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *edata, void *user_ctx)
@@ -307,6 +306,12 @@ IRAM_ATTR static bool test_ppa_srm_trans_done_callback(ppa_client_handle_t ppa_c
}
}
BaseType_t task_woken = pdFALSE;
xSemaphoreGiveFromISR(hook_ctx->draw_sem, &task_woken);
if (task_woken == pdTRUE) {
need_yield = true;
}
return need_yield;
}
@@ -353,11 +358,9 @@ static esp_err_t test_draw_bitmap_hook_ppa(esp_lcd_panel_handle_t panel, const e
IRAM_ATTR static bool test_dpi_panel_color_trans_done_count_callback(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
BaseType_t task_woken = pdFALSE;
test_dpi_panel_color_trans_done_callback_ctx_t *color_trans_done_ctx = (test_dpi_panel_color_trans_done_callback_ctx_t *)user_ctx;
color_trans_done_ctx->count++;
xSemaphoreGiveFromISR(color_trans_done_ctx->draw_sem, &task_woken);
return task_woken == pdTRUE;
return false;
}
TEST_CASE("MIPI DSI use PPA (EK79007)", "[mipi_dsi]")
@@ -437,7 +440,6 @@ TEST_CASE("MIPI DSI use PPA (EK79007)", "[mipi_dsi]")
};
test_dpi_panel_color_trans_done_callback_ctx_t color_trans_done_ctx = {
.draw_sem = draw_sem,
.count = 0,
};
TEST_ESP_OK(esp_lcd_dpi_panel_register_event_callbacks(mipi_dpi_panel, &cbs, &color_trans_done_ctx));
@@ -463,6 +465,7 @@ TEST_CASE("MIPI DSI use PPA (EK79007)", "[mipi_dsi]")
img, 200, 200, 0, 0, 200, 200);
vTaskDelay(pdMS_TO_TICKS(10));
}
xSemaphoreTake(draw_sem, portMAX_DELAY);
TEST_ASSERT_EQUAL_INT(100, color_trans_done_ctx.count);
hooks.draw_bitmap_hook = NULL;
@@ -472,7 +475,7 @@ TEST_CASE("MIPI DSI use PPA (EK79007)", "[mipi_dsi]")
TEST_ESP_OK(esp_lcd_panel_del(mipi_dpi_panel));
TEST_ESP_OK(esp_lcd_panel_io_del(mipi_dbi_io));
TEST_ESP_OK(esp_lcd_del_dsi_bus(mipi_dsi_bus));
vSemaphoreDelete(draw_sem);
vSemaphoreDeleteWithCaps(draw_sem);
free(img);
test_bsp_disable_dsi_phy_power();
@@ -5,5 +5,5 @@ set(srcs "test_app_main.c"
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
idf_component_register(SRCS ${srcs}
PRIV_REQUIRES esp_lcd unity esp_timer spi_flash
PRIV_REQUIRES esp_lcd unity esp_timer spi_flash esp_driver_ppa efuse
WHOLE_ARCHIVE)
@@ -9,6 +9,7 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "soc/soc_caps.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_lcd_panel_ops.h"
#include "esp_timer.h"
@@ -16,6 +17,10 @@
#include "test_rgb_board.h"
#include "esp_private/spi_flash_os.h"
#include "esp_clk_tree.h"
#include "driver/ppa.h"
#include "esp_efuse.h"
#define ALIGN_DOWN(num, align) ((num) & ~((align) - 1))
#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
#define TEST_LCD_CALLBACK_ATTR IRAM_ATTR
@@ -381,3 +386,254 @@ TEST_CASE("lcd_rgb_panel_iram_safe", "[lcd]")
free(img);
}
#endif // CONFIG_LCD_RGB_ISR_IRAM_SAFE
TEST_CASE("lcd_rgb_panel_draw_bitmap_2d", "[lcd]")
{
// Allocate a larger source image (200x200) for testing partial copy
size_t src_img_size = 200 * 200 * sizeof(uint16_t);
uint8_t *src_img = malloc(src_img_size);
TEST_ASSERT_NOT_NULL(src_img);
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
printf("Draw bitmap 2D by CPU - copy partial region from source to destination\r\n");
for (int i = 0; i < 100; i++) {
int x_start = rand() % (TEST_LCD_H_RES - 100);
int y_start = rand() % (TEST_LCD_V_RES - 100);
// Fill source image with random pattern
uint8_t color_byte = rand() & 0xFF;
memset(src_img, color_byte, src_img_size / 2);
color_byte = rand() & 0xFF;
memset(src_img + src_img_size / 2, color_byte, src_img_size / 2);
// Copy a 100x100 region from source (starting at 50,50) to destination at (x_start, y_start)
// Source image is 200x200, we copy region from (50,50) to (150,150)
esp_lcd_panel_draw_bitmap_2d(panel_handle, x_start, y_start, x_start + 100, y_start + 100,
src_img, 200, 200, 50, 50, 150, 150);
vTaskDelay(pdMS_TO_TICKS(10));
}
vTaskDelay(pdMS_TO_TICKS(1000));
printf("delete RGB panel\r\n");
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
free(src_img);
}
#if SOC_HAS(DMA2D)
TEST_CASE("lcd_rgb_panel_dma2d_hook", "[lcd]")
{
// Allocate a larger source image (200x200) for testing partial copy
size_t src_img_size = 200 * 200 * sizeof(uint16_t);
size_t buffer_alignment = 1;
if (esp_efuse_is_flash_encryption_enabled()) {
buffer_alignment = SOC_MEMSPI_ENCRYPTION_ALIGNMENT;
}
uint8_t *src_img = heap_caps_aligned_calloc(buffer_alignment, 1, src_img_size, MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM);
TEST_ASSERT_NOT_NULL(src_img);
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
printf("Draw bitmap 2D by CPU first\r\n");
for (int i = 0; i < 50; i++) {
int x_start = rand() % (TEST_LCD_H_RES - 100);
int y_start = rand() % (TEST_LCD_V_RES - 100);
uint8_t color_byte = rand() & 0xFF;
memset(src_img, color_byte, src_img_size / 2);
color_byte = rand() & 0xFF;
memset(src_img + src_img_size / 2, color_byte, src_img_size / 2);
esp_lcd_panel_draw_bitmap_2d(panel_handle, x_start, y_start, x_start + 100, y_start + 100,
src_img, 200, 200, 50, 50, 150, 150);
vTaskDelay(pdMS_TO_TICKS(10));
}
vTaskDelay(pdMS_TO_TICKS(1000));
size_t test_block_size = 100;
size_t start_alignment = 1;
size_t src_x_start = 50;
size_t src_y_start = 50;
if (esp_efuse_is_flash_encryption_enabled()) {
test_block_size = ALIGN_DOWN(test_block_size, SOC_MEMSPI_ENCRYPTION_ALIGNMENT);
start_alignment = SOC_MEMSPI_ENCRYPTION_ALIGNMENT;
src_x_start = ALIGN_DOWN(src_x_start, SOC_MEMSPI_ENCRYPTION_ALIGNMENT);
src_y_start = ALIGN_DOWN(src_y_start, SOC_MEMSPI_ENCRYPTION_ALIGNMENT);
}
printf("Enable DMA2D draw bitmap hook\r\n");
TEST_ESP_OK(esp_lcd_rgb_panel_enable_dma2d(panel_handle));
printf("Draw bitmap 2D by DMA2D\r\n");
for (int i = 0; i < 100; i++) {
int x_start = ALIGN_DOWN(rand() % (TEST_LCD_H_RES - test_block_size), start_alignment);
int y_start = ALIGN_DOWN(rand() % (TEST_LCD_V_RES - test_block_size), start_alignment);
uint8_t color_byte = rand() & 0xFF;
memset(src_img, color_byte, src_img_size / 2);
color_byte = rand() & 0xFF;
memset(src_img + src_img_size / 2, color_byte, src_img_size / 2);
esp_lcd_panel_draw_bitmap_2d(panel_handle, x_start, y_start, x_start + test_block_size, y_start + test_block_size,
src_img, 200, 200, src_x_start, src_y_start, src_x_start + test_block_size, src_y_start + test_block_size);
vTaskDelay(pdMS_TO_TICKS(10));
}
vTaskDelay(pdMS_TO_TICKS(1000));
printf("Disable DMA2D draw bitmap hook\r\n");
TEST_ESP_OK(esp_lcd_rgb_panel_disable_dma2d(panel_handle));
vTaskDelay(pdMS_TO_TICKS(1000));
printf("delete RGB panel\r\n");
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
free(src_img);
}
#endif // SOC_HAS(DMA2D)
#if SOC_HAS(PPA)
typedef struct {
ppa_client_handle_t ppa_srm_handle;
esp_lcd_draw_bitmap_hook_data_t hook_data;
SemaphoreHandle_t draw_sem;
esp_lcd_panel_handle_t panel;
} test_rgb_panel_draw_bitmap_hook_ctx_t;
typedef struct {
uint32_t count;
} test_rgb_panel_color_trans_done_callback_ctx_t;
TEST_LCD_CALLBACK_ATTR static bool test_ppa_srm_trans_done_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *edata, void *user_ctx)
{
bool need_yield = false;
test_rgb_panel_draw_bitmap_hook_ctx_t *hook_ctx = (test_rgb_panel_draw_bitmap_hook_ctx_t *)user_ctx;
esp_lcd_draw_bitmap_hook_data_t *hook_data = &hook_ctx->hook_data;
if (hook_data->on_hook_end) {
if (hook_data->on_hook_end(hook_ctx->panel)) {
need_yield = true;
}
}
BaseType_t task_woken = pdFALSE;
xSemaphoreGiveFromISR(hook_ctx->draw_sem, &task_woken);
if (task_woken == pdTRUE) {
need_yield = true;
}
return need_yield;
}
static esp_err_t test_draw_bitmap_hook_ppa(esp_lcd_panel_handle_t panel, const esp_lcd_draw_bitmap_hook_data_t *hook_data, void *user_ctx)
{
test_rgb_panel_draw_bitmap_hook_ctx_t *hook_ctx = (test_rgb_panel_draw_bitmap_hook_ctx_t *)user_ctx;
ppa_client_handle_t ppa_srm_handle = hook_ctx->ppa_srm_handle;
xSemaphoreTake(hook_ctx->draw_sem, portMAX_DELAY);
memcpy(&hook_ctx->hook_data, hook_data, sizeof(esp_lcd_draw_bitmap_hook_data_t));
ppa_srm_oper_config_t srm_config = {
.in.buffer = hook_data->src_data,
.in.pic_w = hook_data->src_x_size,
.in.pic_h = hook_data->src_y_size,
.in.block_w = hook_data->src_x_end - hook_data->src_x_start,
.in.block_h = hook_data->src_y_end - hook_data->src_y_start,
.in.block_offset_x = hook_data->src_x_start,
.in.block_offset_y = hook_data->src_y_start,
.in.srm_cm = PPA_SRM_COLOR_MODE_RGB565,
.out.buffer = hook_data->dst_data,
.out.buffer_size = hook_data->dst_x_size * hook_data->dst_y_size * hook_data->bits_per_pixel / 8,
.out.pic_w = hook_data->dst_x_size,
.out.pic_h = hook_data->dst_y_size,
.out.block_offset_x = hook_data->dst_x_start,
.out.block_offset_y = hook_data->dst_y_start,
.out.srm_cm = PPA_SRM_COLOR_MODE_RGB565,
.rotation_angle = PPA_SRM_ROTATION_ANGLE_90,
.scale_x = 0.5,
.scale_y = 0.5,
.rgb_swap = 0,
.byte_swap = 0,
.mode = PPA_TRANS_MODE_NON_BLOCKING,
.user_data = hook_ctx,
};
ppa_event_callbacks_t ppa_srm_event_callbacks = {
.on_trans_done = test_ppa_srm_trans_done_callback,
};
TEST_ESP_OK(ppa_client_register_event_callbacks(ppa_srm_handle, &ppa_srm_event_callbacks));
TEST_ESP_OK(ppa_do_scale_rotate_mirror(ppa_srm_handle, &srm_config));
return ESP_OK;
}
TEST_LCD_CALLBACK_ATTR static bool test_rgb_panel_color_trans_done_count_callback(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx)
{
test_rgb_panel_color_trans_done_callback_ctx_t *color_trans_done_ctx = (test_rgb_panel_color_trans_done_callback_ctx_t *)user_ctx;
color_trans_done_ctx->count++;
return false;
}
TEST_CASE("lcd_rgb_panel_ppa_hook", "[lcd]")
{
if (esp_efuse_is_flash_encryption_enabled()) {
TEST_PASS_MESSAGE("PPA SRM is not compatible with encrypted memory, skip this test");
}
// Allocate a larger source image (200x200) for testing
size_t src_img_size = 200 * 200 * sizeof(uint16_t);
uint8_t *src_img = malloc(src_img_size);
TEST_ASSERT_NOT_NULL(src_img);
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
SemaphoreHandle_t draw_sem = xSemaphoreCreateBinaryWithCaps(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
TEST_ASSERT_NOT_NULL(draw_sem);
xSemaphoreGive(draw_sem);
// use PPA to scale and rotate the image in draw bitmap hook
ppa_client_handle_t ppa_srm_handle = NULL;
ppa_client_config_t ppa_srm_config = {
.oper_type = PPA_OPERATION_SRM,
.max_pending_trans_num = 1,
};
TEST_ESP_OK(ppa_register_client(&ppa_srm_config, &ppa_srm_handle));
esp_lcd_rgb_panel_event_callbacks_t cbs = {
.on_color_trans_done = test_rgb_panel_color_trans_done_count_callback,
};
test_rgb_panel_color_trans_done_callback_ctx_t color_trans_done_ctx = {
.count = 0,
};
TEST_ESP_OK(esp_lcd_rgb_panel_register_event_callbacks(panel_handle, &cbs, &color_trans_done_ctx));
printf("Add PPA draw bitmap hook\r\n");
esp_lcd_panel_hooks_t hooks = {
.draw_bitmap_hook = test_draw_bitmap_hook_ppa,
};
test_rgb_panel_draw_bitmap_hook_ctx_t hook_ctx = {
.draw_sem = draw_sem,
.ppa_srm_handle = ppa_srm_handle,
.panel = panel_handle,
};
TEST_ESP_OK(esp_lcd_rgb_panel_register_hooks(panel_handle, &hooks, &hook_ctx));
for (int i = 0; i < 100; i++) {
int x_start = rand() % (TEST_LCD_H_RES - 100);
int y_start = rand() % (TEST_LCD_V_RES - 100);
uint8_t color_byte = rand() & 0xFF;
memset(src_img, color_byte, src_img_size / 2);
color_byte = rand() & 0xFF;
memset(src_img + src_img_size / 2, color_byte, src_img_size / 2);
esp_lcd_panel_draw_bitmap_2d(panel_handle, x_start, y_start, x_start + 50, y_start + 50,
src_img, 200, 200, 0, 0, 200, 200);
vTaskDelay(pdMS_TO_TICKS(10));
}
xSemaphoreTake(draw_sem, portMAX_DELAY);
TEST_ASSERT_EQUAL_INT(100, color_trans_done_ctx.count);
hooks.draw_bitmap_hook = NULL;
TEST_ESP_OK(esp_lcd_rgb_panel_register_hooks(panel_handle, &hooks, NULL));
TEST_ESP_OK(ppa_unregister_client(ppa_srm_handle));
printf("delete RGB panel\r\n");
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
vSemaphoreDeleteWithCaps(draw_sem);
free(src_img);
}
#endif // SOC_HAS(PPA)