mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
feat(rgb_lcd): support draw bitmap hook
This commit is contained in:
@@ -528,7 +528,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;
|
||||
|
||||
@@ -549,14 +549,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;
|
||||
@@ -586,14 +591,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -49,6 +49,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"
|
||||
@@ -90,6 +91,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);
|
||||
@@ -167,6 +170,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)
|
||||
@@ -446,6 +454,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;
|
||||
@@ -589,6 +598,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;
|
||||
@@ -658,17 +671,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;
|
||||
@@ -676,6 +745,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);
|
||||
@@ -696,6 +771,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;
|
||||
@@ -707,17 +796,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) {
|
||||
@@ -726,22 +842,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
|
||||
@@ -750,18 +865,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;
|
||||
}
|
||||
|
||||
@@ -1325,6 +1429,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
|
||||
|
||||
@@ -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_random.h"
|
||||
@@ -17,6 +18,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
|
||||
@@ -382,3 +387,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)
|
||||
|
||||
@@ -86,7 +86,7 @@ MIPI DSI Interfaced LCD
|
||||
|
||||
#. Configure draw bitmap hook function (optional)
|
||||
|
||||
If you want to use DMA2D to implement draw bitmap, the driver has already implemented the DMA2D draw bitmap hook function, you only need to call :func:`esp_lcd_dpi_panel_enable_dma2d` to enable it.
|
||||
If you want to accelerate 2D bitmap copy with DMA2D, the driver already provides a built-in DMA2D bitmap copy hook. You only need to call :func:`esp_lcd_dpi_panel_enable_dma2d` to enable it.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
@@ -105,6 +105,58 @@ MIPI DSI Interfaced LCD
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_hooks(mipi_dpi_panel, &hooks, &user_ctx));
|
||||
|
||||
If the custom hook is asynchronous — for example, the hook starts a PPA transfer and returns immediately while the hardware continues processing pixels in the background — call :cpp:member:`esp_lcd_draw_bitmap_hook_data_t::on_hook_end` only after the hardware operation has actually finished. ``on_hook_end`` is implemented and filled into ``hook_data`` by the DPI panel driver; you do not need to write it yourself, only call it when the asynchronous operation completes to notify the driver that the draw transaction is finished. If a color transfer done callback has been registered, it is invoked at that time as well.
|
||||
|
||||
The panel driver does not wait for a previous draw to finish; synchronization is the custom hook's responsibility. The simplest approach is to serialize draws (do not start a new one before the previous one completes), as shown in the example below. If you want to submit multiple draws concurrently using an accelerator transaction queue such as PPA's, keep a separate ``hook_data`` copy per transaction, keep the source buffer valid until hardware completion, and handle overlapping destination regions carefully.
|
||||
|
||||
The following snippet shows an asynchronous custom hook: it starts PPA and returns immediately, then calls ``on_hook_end`` from the PPA completion callback. The example serializes draws with a semaphore so only one draw is in flight at a time:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
typedef struct {
|
||||
esp_lcd_panel_handle_t panel;
|
||||
esp_lcd_draw_bitmap_hook_data_t hook_data;
|
||||
SemaphoreHandle_t draw_sem;
|
||||
// ... other fields, e.g. ppa_client_handle_t
|
||||
} draw_bitmap_hook_ctx_t;
|
||||
|
||||
static bool ppa_trans_done_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx;
|
||||
bool need_yield = false;
|
||||
|
||||
// on_hook_end is provided by the DPI panel driver; just call it when done
|
||||
if (ctx->hook_data.on_hook_end) {
|
||||
if (ctx->hook_data.on_hook_end(ctx->panel)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
xSemaphoreGiveFromISR(ctx->draw_sem, &task_woken);
|
||||
if (task_woken == pdTRUE) {
|
||||
need_yield = true;
|
||||
}
|
||||
return need_yield;
|
||||
}
|
||||
|
||||
static esp_err_t custom_draw_bitmap_hook(esp_lcd_panel_handle_t panel,
|
||||
const esp_lcd_draw_bitmap_hook_data_t *hook_data,
|
||||
void *user_ctx)
|
||||
{
|
||||
draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx;
|
||||
|
||||
// Simplest sync: wait until the previous draw finishes
|
||||
xSemaphoreTake(ctx->draw_sem, portMAX_DELAY);
|
||||
|
||||
// Save hook_data so the completion callback can call on_hook_end later
|
||||
ctx->hook_data = *hook_data;
|
||||
|
||||
// Start an asynchronous PPA transfer, then return immediately
|
||||
// ppa_do_scale_rotate_mirror(...);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
Power Supply for MIPI DPHY
|
||||
--------------------------
|
||||
|
||||
|
||||
@@ -257,6 +257,76 @@ This mode is similar to :ref:`bounce_buffer_with_single_psram_frame_buffer`, but
|
||||
In a well-designed embedded application, situations where the DMA cannot deliver data as fast as the LCD consumes it should be avoided. However, such scenarios can theoretically occur. In the {IDF_TARGET_NAME} hardware, this results in the LCD outputting dummy bytes while the DMA waits for data. If the DMA were to run in a continuous stream, it could cause a desynchronization between the LCD address from which the DMA reads data and the address from which the LCD peripheral outputs data, leading to a **permanently** shifted image.
|
||||
To prevent this, you can either enable the :ref:`CONFIG_LCD_RGB_RESTART_IN_VSYNC` option, allowing the driver to automatically restart the DMA during the VBlank interrupt, or call :cpp:func:`esp_lcd_rgb_panel_restart` to manually restart the DMA. Note that :cpp:func:`esp_lcd_rgb_panel_restart` does not restart the DMA immediately; instead, the DMA will be restarted at the next VSYNC event.
|
||||
|
||||
Draw Bitmap Hook Function
|
||||
-------------------------
|
||||
|
||||
If you want to accelerate 2D bitmap copy with DMA2D, the driver already provides a built-in DMA2D bitmap copy hook. You only need to call :cpp:func:`esp_lcd_rgb_panel_enable_dma2d` to enable it.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_rgb_panel_enable_dma2d(panel_handle));
|
||||
|
||||
If you need more advanced applications, you can add a custom hook for draw bitmap, such as using PPA to implement rotation, scaling, etc.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_panel_hooks_t hooks = {
|
||||
.draw_bitmap_hook = custom_draw_bitmap_hook,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_hooks(panel_handle, &hooks, &user_ctx));
|
||||
|
||||
If the custom hook is asynchronous — for example, the hook starts a PPA transfer and returns immediately while the hardware continues processing pixels in the background — call :cpp:member:`esp_lcd_draw_bitmap_hook_data_t::on_hook_end` only after the hardware operation has actually finished. ``on_hook_end`` is implemented and filled into ``hook_data`` by the RGB panel driver; you do not need to write it yourself, only call it when the asynchronous operation completes to notify the driver that the draw transaction is finished. If a color transfer done callback has been registered, it is invoked at that time as well.
|
||||
|
||||
The panel driver does not wait for a previous draw to finish; synchronization is the custom hook's responsibility. The simplest approach is to serialize draws (do not start a new one before the previous one completes), as shown in the example below. If you want to submit multiple draws concurrently using an accelerator transaction queue such as PPA's, keep a separate ``hook_data`` copy per transaction, keep the source buffer valid until hardware completion, and handle overlapping destination regions carefully.
|
||||
|
||||
The following snippet shows an asynchronous custom hook: it starts PPA and returns immediately, then calls ``on_hook_end`` from the PPA completion callback. The example serializes draws with a semaphore so only one draw is in flight at a time:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
typedef struct {
|
||||
esp_lcd_panel_handle_t panel;
|
||||
esp_lcd_draw_bitmap_hook_data_t hook_data;
|
||||
SemaphoreHandle_t draw_sem;
|
||||
// ... other fields, e.g. ppa_client_handle_t
|
||||
} draw_bitmap_hook_ctx_t;
|
||||
|
||||
static bool ppa_trans_done_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx;
|
||||
bool need_yield = false;
|
||||
|
||||
// on_hook_end is provided by the RGB panel driver; just call it when done
|
||||
if (ctx->hook_data.on_hook_end) {
|
||||
if (ctx->hook_data.on_hook_end(ctx->panel)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
xSemaphoreGiveFromISR(ctx->draw_sem, &task_woken);
|
||||
if (task_woken == pdTRUE) {
|
||||
need_yield = true;
|
||||
}
|
||||
return need_yield;
|
||||
}
|
||||
|
||||
static esp_err_t custom_draw_bitmap_hook(esp_lcd_panel_handle_t panel,
|
||||
const esp_lcd_draw_bitmap_hook_data_t *hook_data,
|
||||
void *user_ctx)
|
||||
{
|
||||
draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx;
|
||||
|
||||
// Simplest sync: wait until the previous draw finishes
|
||||
xSemaphoreTake(ctx->draw_sem, portMAX_DELAY);
|
||||
|
||||
// Save hook_data so the completion callback can call on_hook_end later
|
||||
ctx->hook_data = *hook_data;
|
||||
|
||||
// Start an asynchronous PPA transfer, then return immediately
|
||||
// ppa_do_scale_rotate_mirror(...);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ MIPI DSI 接口的 LCD
|
||||
|
||||
#. 配置绘制位图钩子函数(可选)
|
||||
|
||||
若想使用 DMA2D 实现绘制位图,驱动程序内部已实现 DMA2D 绘制位图的钩子函数,用户只需调用 :func:`esp_lcd_dpi_panel_enable_dma2d` 即可。
|
||||
若想使用 DMA2D 加速位图的复制,驱动程序内部已实现基于 DMA2D 的位图复制钩子函数,用户只需调用 :func:`esp_lcd_dpi_panel_enable_dma2d` 即可。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
@@ -105,6 +105,58 @@ MIPI DSI 接口的 LCD
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_hooks(mipi_dpi_panel, &hooks, &user_ctx));
|
||||
|
||||
如果自定义钩子是异步的——例如钩子函数启动 PPA 后立即返回,真正的像素处理仍由硬件在后台执行——则必须在硬件操作真正完成后再调用 :cpp:member:`esp_lcd_draw_bitmap_hook_data_t::on_hook_end`。该回调由 DPI 面板驱动实现并填入 ``hook_data``,用户无需自行编写,只需在异步操作完成时调用它,以通知驱动结束本次绘制事务;若已注册颜色传输完成回调,也会在此时被调用。
|
||||
|
||||
面板驱动本身不会等待上一笔绘制结束,同步由自定义钩子自行负责。最简单的做法是串行执行(上一笔完成前不启动下一笔),如下面示例所示。若希望利用 PPA 等加速器的事务队列并发提交多笔绘制,则需要为每笔事务单独保存 ``hook_data``、保证源 buffer 在硬件完成前有效,并处理好目标区域重叠等问题。
|
||||
|
||||
下面是一个异步自定义钩子的示意代码:钩子启动 PPA 后立即返回,并在 PPA 完成回调中调用 ``on_hook_end``。示例采用串行方式,用信号量保证同一时间只有一笔绘制在进行:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
typedef struct {
|
||||
esp_lcd_panel_handle_t panel;
|
||||
esp_lcd_draw_bitmap_hook_data_t hook_data;
|
||||
SemaphoreHandle_t draw_sem;
|
||||
// ... 其他字段,例如 ppa_client_handle_t
|
||||
} draw_bitmap_hook_ctx_t;
|
||||
|
||||
static bool ppa_trans_done_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx;
|
||||
bool need_yield = false;
|
||||
|
||||
// on_hook_end 由 DPI 面板驱动提供,操作完成后直接调用即可
|
||||
if (ctx->hook_data.on_hook_end) {
|
||||
if (ctx->hook_data.on_hook_end(ctx->panel)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
xSemaphoreGiveFromISR(ctx->draw_sem, &task_woken);
|
||||
if (task_woken == pdTRUE) {
|
||||
need_yield = true;
|
||||
}
|
||||
return need_yield;
|
||||
}
|
||||
|
||||
static esp_err_t custom_draw_bitmap_hook(esp_lcd_panel_handle_t panel,
|
||||
const esp_lcd_draw_bitmap_hook_data_t *hook_data,
|
||||
void *user_ctx)
|
||||
{
|
||||
draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx;
|
||||
|
||||
// 最简单的同步方式:等待上一笔完成后再启动本笔
|
||||
xSemaphoreTake(ctx->draw_sem, portMAX_DELAY);
|
||||
|
||||
// 保存 hook_data,供完成回调稍后调用 on_hook_end
|
||||
ctx->hook_data = *hook_data;
|
||||
|
||||
// 启动异步 PPA 传输后立即返回
|
||||
// ppa_do_scale_rotate_mirror(...);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
关于 MIPI DPHY 的供电
|
||||
---------------------
|
||||
|
||||
|
||||
@@ -257,6 +257,76 @@ bounce buffer 与 PSRAM frame buffer
|
||||
虽说在设计良好的嵌入式应用程序中, DMA 传递数据的速度不应该赶不上 LCD 读取数据的速度。但理论上,此种情况还是有可能出现的。在 {IDF_TARGET_NAME} 的硬件中,这种情况会导致 LCD 在 DMA 等待数据时单纯输出 dummy 字节。若以流式传输运行 DMA,则 DMA 会将读取到的数据传输到某个 LCD 地址,同时 LCD 也会将数据输出到某个 LCD 地址,但上述两个地址可能会不同步,导致图像 **永久** 偏移。
|
||||
为防止类似情况发生,可以启用 :ref:`CONFIG_LCD_RGB_RESTART_IN_VSYNC` 选项,以便驱动程序在 VBlank 中断时自动重启 DMA;或者也可以调用 :cpp:func:`esp_lcd_rgb_panel_restart`,手动重启 DMA。请注意,调用 :cpp:func:`esp_lcd_rgb_panel_restart` 不会立即重启 DMA,DMA 只会在下一个 VSYNC 事件中重启。
|
||||
|
||||
绘制位图钩子函数
|
||||
----------------
|
||||
|
||||
若想使用 DMA2D 加速位图的复制,驱动程序内部已实现基于 DMA2D 的位图复制钩子函数,用户只需调用 :cpp:func:`esp_lcd_rgb_panel_enable_dma2d` 即可。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_rgb_panel_enable_dma2d(panel_handle));
|
||||
|
||||
若需更高级的应用,用户可为绘制位图添加自定义钩子,例如通过 PPA 实现旋转、缩放等操作。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_panel_hooks_t hooks = {
|
||||
.draw_bitmap_hook = custom_draw_bitmap_hook,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_hooks(panel_handle, &hooks, &user_ctx));
|
||||
|
||||
如果自定义钩子是异步的——例如钩子函数启动 PPA 后立即返回,真正的像素处理仍由硬件在后台执行——则必须在硬件操作真正完成后再调用 :cpp:member:`esp_lcd_draw_bitmap_hook_data_t::on_hook_end`。该回调由 RGB 面板驱动实现并填入 ``hook_data``,用户无需自行编写,只需在异步操作完成时调用它,以通知驱动结束本次绘制事务;若已注册颜色传输完成回调,也会在此时被调用。
|
||||
|
||||
面板驱动本身不会等待上一笔绘制结束,同步由自定义钩子自行负责。最简单的做法是串行执行(上一笔完成前不启动下一笔),如下面示例所示。若希望利用 PPA 等加速器的事务队列并发提交多笔绘制,则需要为每笔事务单独保存 ``hook_data``、保证源 buffer 在硬件完成前有效,并处理好目标区域重叠等问题。
|
||||
|
||||
下面是一个异步自定义钩子的示意代码:钩子启动 PPA 后立即返回,并在 PPA 完成回调中调用 ``on_hook_end``。示例采用串行方式,用信号量保证同一时间只有一笔绘制在进行:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
typedef struct {
|
||||
esp_lcd_panel_handle_t panel;
|
||||
esp_lcd_draw_bitmap_hook_data_t hook_data;
|
||||
SemaphoreHandle_t draw_sem;
|
||||
// ... 其他字段,例如 ppa_client_handle_t
|
||||
} draw_bitmap_hook_ctx_t;
|
||||
|
||||
static bool ppa_trans_done_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx;
|
||||
bool need_yield = false;
|
||||
|
||||
// on_hook_end 由 RGB 面板驱动提供,操作完成后直接调用即可
|
||||
if (ctx->hook_data.on_hook_end) {
|
||||
if (ctx->hook_data.on_hook_end(ctx->panel)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
xSemaphoreGiveFromISR(ctx->draw_sem, &task_woken);
|
||||
if (task_woken == pdTRUE) {
|
||||
need_yield = true;
|
||||
}
|
||||
return need_yield;
|
||||
}
|
||||
|
||||
static esp_err_t custom_draw_bitmap_hook(esp_lcd_panel_handle_t panel,
|
||||
const esp_lcd_draw_bitmap_hook_data_t *hook_data,
|
||||
void *user_ctx)
|
||||
{
|
||||
draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx;
|
||||
|
||||
// 最简单的同步方式:等待上一笔完成后再启动本笔
|
||||
xSemaphoreTake(ctx->draw_sem, portMAX_DELAY);
|
||||
|
||||
// 保存 hook_data,供完成回调稍后调用 on_hook_end
|
||||
ctx->hook_data = *hook_data;
|
||||
|
||||
// 启动异步 PPA 传输后立即返回
|
||||
// ppa_do_scale_rotate_mirror(...);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
API 参考
|
||||
--------
|
||||
|
||||
|
||||
@@ -25,6 +25,14 @@ menu "Example Configuration"
|
||||
Allocate one draw buffer in LVGL.
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_USE_DMA2D_COPY_FRAME
|
||||
bool "Use DMA2D to copy draw buffer to frame buffer"
|
||||
default y
|
||||
depends on EXAMPLE_USE_SINGLE_FB && SOC_DMA2D_SUPPORTED
|
||||
help
|
||||
Enable this option, DMA2D will be used to copy the LVGL draw buffer to the target frame buffer.
|
||||
This can save some CPU time and improve the performance.
|
||||
|
||||
choice EXAMPLE_LCD_DATA_LINES
|
||||
prompt "RGB LCD Data Lines"
|
||||
default EXAMPLE_LCD_DATA_LINES_16
|
||||
|
||||
@@ -271,6 +271,12 @@ void app_main(void)
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
|
||||
|
||||
#if CONFIG_EXAMPLE_USE_DMA2D_COPY_FRAME
|
||||
// use DMA2D to copy draw buffer to frame buffer
|
||||
ESP_LOGI(TAG, "RGB panel added DMA2D draw bitmap hook");
|
||||
ESP_ERROR_CHECK(esp_lcd_rgb_panel_enable_dma2d(panel_handle));
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "Initialize RGB LCD panel");
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
|
||||
|
||||
Reference in New Issue
Block a user