@@ -113,10 +113,10 @@ void tab5_create_devices_v1(Device* i2c0) {
|
|||||||
.vsync_pulse_width = 4,
|
.vsync_pulse_width = 4,
|
||||||
.vsync_back_porch = 20,
|
.vsync_back_porch = 20,
|
||||||
.vsync_front_porch = 20,
|
.vsync_front_porch = 20,
|
||||||
.num_fbs = 1,
|
.num_fbs = 2,
|
||||||
.use_dma2d = false,
|
.use_dma2d = true,
|
||||||
.disable_lp = false,
|
.disable_lp = false,
|
||||||
.allow_tearing = true, // matches old lvgl_port_display_dsi_cfg_t.avoid_tearing = 0
|
.allow_tearing = true, // matches old lvgl_port_display_dsi_cfg_t.avoid_tearing = 0 (disabled = don't wait)
|
||||||
.init_sequence = display_init_bytes.data(),
|
.init_sequence = display_init_bytes.data(),
|
||||||
.init_sequence_length = (uint32_t)display_init_bytes.size(),
|
.init_sequence_length = (uint32_t)display_init_bytes.size(),
|
||||||
.backlight = backlight,
|
.backlight = backlight,
|
||||||
|
|||||||
@@ -92,10 +92,10 @@ void tab5_create_devices_v2(Device* i2c0) {
|
|||||||
.vsync_pulse_width = 2,
|
.vsync_pulse_width = 2,
|
||||||
.vsync_back_porch = 8,
|
.vsync_back_porch = 8,
|
||||||
.vsync_front_porch = 220,
|
.vsync_front_porch = 220,
|
||||||
.num_fbs = 1,
|
.num_fbs = 2,
|
||||||
.use_dma2d = false,
|
.use_dma2d = true,
|
||||||
.disable_lp = false,
|
.disable_lp = false,
|
||||||
.allow_tearing = true,
|
.allow_tearing = true, // matches old lvgl_port_display_dsi_cfg_t.avoid_tearing = 0 (disabled = don't wait)
|
||||||
.init_sequence = display_init_bytes.data(),
|
.init_sequence = display_init_bytes.data(),
|
||||||
.init_sequence_length = (uint32_t)display_init_bytes.size(),
|
.init_sequence_length = (uint32_t)display_init_bytes.size(),
|
||||||
.backlight = backlight,
|
.backlight = backlight,
|
||||||
|
|||||||
@@ -46,8 +46,7 @@ sdkconfig.CONFIG_ESP_HOSTED_ENABLE_PEER_DATA_TRANSFER=y
|
|||||||
sdkconfig.CONFIG_ESP_HOSTED_MAX_CUSTOM_MSG_HANDLERS=8
|
sdkconfig.CONFIG_ESP_HOSTED_MAX_CUSTOM_MSG_HANDLERS=8
|
||||||
# Performance: larger L2 cache reduces PSRAM stalls for draw/DPI buffers
|
# Performance: larger L2 cache reduces PSRAM stalls for draw/DPI buffers
|
||||||
sdkconfig.CONFIG_CACHE_L2_CACHE_256KB=y
|
sdkconfig.CONFIG_CACHE_L2_CACHE_256KB=y
|
||||||
# Performance: use P4's PPA (pixel processing accelerator for rotation)
|
# Performance: cache-line-aligned draw buffers, required for PPA/DMA2D coherency (see lvgl-module's lvgl_display_alloc_buffer())
|
||||||
sdkconfig.CONFIG_LVGL_PORT_ENABLE_PPA=y
|
|
||||||
sdkconfig.CONFIG_LV_DRAW_BUF_ALIGN=64
|
sdkconfig.CONFIG_LV_DRAW_BUF_ALIGN=64
|
||||||
sdkconfig.CONFIG_LV_DEF_REFR_PERIOD=15
|
sdkconfig.CONFIG_LV_DEF_REFR_PERIOD=15
|
||||||
# SC202CS (SC2356) MIPI CSI camera sensor + ISP pipeline (AE/AWB/demosaicing)
|
# SC202CS (SC2356) MIPI CSI camera sensor + ISP pipeline (AE/AWB/demosaicing)
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ struct Ili9881cInternal {
|
|||||||
// draw_bitmap() when color_data is one of frame_buffers and avoid_tearing is set - see the
|
// draw_bitmap() when color_data is one of frame_buffers and avoid_tearing is set - see the
|
||||||
// comment there for why.
|
// comment there for why.
|
||||||
SemaphoreHandle_t frame_complete_semaphore;
|
SemaphoreHandle_t frame_complete_semaphore;
|
||||||
|
// Signaled by on_color_trans_done once the panel driver's own copy/DMA2D transfer of a
|
||||||
|
// draw_bitmap() call's pixels into the target frame buffer completes. Unlike
|
||||||
|
// frame_complete_semaphore this is always waited on (regardless of allow_tearing or whether
|
||||||
|
// color_data is a real frame buffer) - see the comment on draw_bitmap() for why.
|
||||||
|
SemaphoreHandle_t color_trans_done_semaphore;
|
||||||
// Heap-allocated only when the devicetree supplies a custom init_sequence (see
|
// Heap-allocated only when the devicetree supplies a custom init_sequence (see
|
||||||
// parse_init_sequence()) - nullptr otherwise, since the vendor's built-in default sequence
|
// parse_init_sequence()) - nullptr otherwise, since the vendor's built-in default sequence
|
||||||
// needs no parsing. Its .data pointers alias directly into the devicetree's static const
|
// needs no parsing. Its .data pointers alias directly into the devicetree's static const
|
||||||
@@ -69,6 +74,30 @@ static bool on_refresh_done(esp_lcd_panel_handle_t, esp_lcd_dpi_panel_event_data
|
|||||||
return high_task_woken == pdTRUE;
|
return high_task_woken == pdTRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// esp_lcd_dpi_panel_draw_bitmap() copies (memcpy, or DMA2D when use_dma2d is set) the caller's
|
||||||
|
// pixels into the target frame buffer and returns immediately - the copy itself finishes
|
||||||
|
// asynchronously. Calling draw_bitmap() again before that copy completes hits the panel driver's
|
||||||
|
// own re-entrancy guard and logs "previous draw operation is not finished" (lcd.dsi). This is
|
||||||
|
// unconditional, unlike frame_complete_semaphore's tearing-avoidance wait: it protects the copy
|
||||||
|
// itself, not scan-out, so it must be waited on regardless of allow_tearing or which buffer
|
||||||
|
// color_data is.
|
||||||
|
//
|
||||||
|
// Unlike on_refresh_done (always ISR, from the VSYNC interrupt), esp_lcd_panel_dpi.c invokes this
|
||||||
|
// callback from two different contexts depending on the copy path: synchronously from the calling
|
||||||
|
// task for the CPU-memcpy and fb-direct (zero-copy) paths, but from an ISR (async_fbcpy_done_cb,
|
||||||
|
// the DMA2D completion interrupt) when use_dma2d is set. Giving a semaphore with the ISR-only API
|
||||||
|
// from task context is undefined behavior on FreeRTOS, so the context must be checked at runtime.
|
||||||
|
static bool on_color_trans_done(esp_lcd_panel_handle_t, esp_lcd_dpi_panel_event_data_t*, void* user_ctx) {
|
||||||
|
auto* internal = static_cast<Ili9881cInternal*>(user_ctx);
|
||||||
|
if (xPortInIsrContext()) {
|
||||||
|
BaseType_t high_task_woken = pdFALSE;
|
||||||
|
xSemaphoreGiveFromISR(internal->color_trans_done_semaphore, &high_task_woken);
|
||||||
|
return high_task_woken == pdTRUE;
|
||||||
|
}
|
||||||
|
xSemaphoreGive(internal->color_trans_done_semaphore);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static int pin_or_unused(const GpioPinSpec& pin) {
|
static int pin_or_unused(const GpioPinSpec& pin) {
|
||||||
return pin.gpio_controller == nullptr ? -1 : static_cast<int>(pin.pin);
|
return pin.gpio_controller == nullptr ? -1 : static_cast<int>(pin.pin);
|
||||||
}
|
}
|
||||||
@@ -310,10 +339,27 @@ static error_t start(Device* device) {
|
|||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal->color_trans_done_semaphore = xSemaphoreCreateBinary();
|
||||||
|
if (internal->color_trans_done_semaphore == nullptr) {
|
||||||
|
vSemaphoreDelete(internal->frame_complete_semaphore);
|
||||||
|
esp_lcd_panel_del(internal->panel_handle);
|
||||||
|
esp_lcd_panel_io_del(internal->io_handle);
|
||||||
|
esp_lcd_del_dsi_bus(internal->dsi_bus_handle);
|
||||||
|
esp_ldo_release_channel(internal->ldo_handle);
|
||||||
|
free(internal->parsed_init_cmds);
|
||||||
|
free(internal);
|
||||||
|
return ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
// The panel starts out idle (no draw_bitmap() call in flight), so the first draw_bitmap()
|
||||||
|
// must not block waiting for a completion event that will never come.
|
||||||
|
xSemaphoreGive(internal->color_trans_done_semaphore);
|
||||||
|
|
||||||
esp_lcd_dpi_panel_event_callbacks_t callbacks = {};
|
esp_lcd_dpi_panel_event_callbacks_t callbacks = {};
|
||||||
callbacks.on_refresh_done = on_refresh_done;
|
callbacks.on_refresh_done = on_refresh_done;
|
||||||
|
callbacks.on_color_trans_done = on_color_trans_done;
|
||||||
if (esp_lcd_dpi_panel_register_event_callbacks(internal->panel_handle, &callbacks, internal) != ESP_OK) {
|
if (esp_lcd_dpi_panel_register_event_callbacks(internal->panel_handle, &callbacks, internal) != ESP_OK) {
|
||||||
LOG_E(TAG, "Failed to register panel event callbacks");
|
LOG_E(TAG, "Failed to register panel event callbacks");
|
||||||
|
vSemaphoreDelete(internal->color_trans_done_semaphore);
|
||||||
vSemaphoreDelete(internal->frame_complete_semaphore);
|
vSemaphoreDelete(internal->frame_complete_semaphore);
|
||||||
esp_lcd_panel_del(internal->panel_handle);
|
esp_lcd_panel_del(internal->panel_handle);
|
||||||
esp_lcd_panel_io_del(internal->io_handle);
|
esp_lcd_panel_io_del(internal->io_handle);
|
||||||
@@ -364,6 +410,7 @@ static error_t stop(Device* device) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vSemaphoreDelete(internal->frame_complete_semaphore);
|
vSemaphoreDelete(internal->frame_complete_semaphore);
|
||||||
|
vSemaphoreDelete(internal->color_trans_done_semaphore);
|
||||||
free(internal->parsed_init_cmds);
|
free(internal->parsed_init_cmds);
|
||||||
free(internal);
|
free(internal);
|
||||||
device_set_driver_data(device, nullptr);
|
device_set_driver_data(device, nullptr);
|
||||||
@@ -403,15 +450,32 @@ static bool ili9881c_color_data_is_frame_buffer(const Ili9881cInternal* internal
|
|||||||
static error_t ili9881c_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
|
static error_t ili9881c_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
|
||||||
auto* internal = static_cast<Ili9881cInternal*>(device_get_driver_data(device));
|
auto* internal = static_cast<Ili9881cInternal*>(device_get_driver_data(device));
|
||||||
|
|
||||||
|
// Wait for the previous draw_bitmap()'s copy/DMA2D transfer to finish before issuing a new
|
||||||
|
// one - see on_color_trans_done's comment for why this is unconditional (unlike the
|
||||||
|
// tearing-avoidance wait below). The semaphore starts pre-given (see start()), so the first
|
||||||
|
// call doesn't block here.
|
||||||
|
xSemaphoreTake(internal->color_trans_done_semaphore, portMAX_DELAY);
|
||||||
|
|
||||||
bool wait_for_scanout = !GET_CONFIG(device)->allow_tearing && ili9881c_color_data_is_frame_buffer(internal, color_data);
|
bool wait_for_scanout = !GET_CONFIG(device)->allow_tearing && ili9881c_color_data_is_frame_buffer(internal, color_data);
|
||||||
if (wait_for_scanout) {
|
if (wait_for_scanout) {
|
||||||
xSemaphoreTake(internal->frame_complete_semaphore, 0); // clear any already-pending signal
|
xSemaphoreTake(internal->frame_complete_semaphore, 0); // clear any already-pending signal
|
||||||
}
|
}
|
||||||
|
|
||||||
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) {
|
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) {
|
||||||
|
xSemaphoreGive(internal->color_trans_done_semaphore);
|
||||||
return ERROR_RESOURCE;
|
return ERROR_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also wait for *this* call's own copy to finish before returning: color_data may be a
|
||||||
|
// caller-owned buffer (e.g. LVGL's draw buffer in owned-buffer/PARTIAL mode), and the
|
||||||
|
// CPU-memcpy/DMA2D copy paths both read directly from it asynchronously - draw_bitmap()
|
||||||
|
// returning early would let the caller start overwriting color_data (DisplayApi's contract
|
||||||
|
// treats draw_bitmap as synchronous - see lvgl_display_flush_cb()'s lv_display_flush_ready()
|
||||||
|
// call right after) while that read is still in flight. on_color_trans_done() re-gives the
|
||||||
|
// semaphore here, restoring the pre-given/idle state for the next call.
|
||||||
|
xSemaphoreTake(internal->color_trans_done_semaphore, portMAX_DELAY);
|
||||||
|
xSemaphoreGive(internal->color_trans_done_semaphore);
|
||||||
|
|
||||||
if (wait_for_scanout) {
|
if (wait_for_scanout) {
|
||||||
xSemaphoreTake(internal->frame_complete_semaphore, portMAX_DELAY);
|
xSemaphoreTake(internal->frame_complete_semaphore, portMAX_DELAY);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ struct St7123Internal {
|
|||||||
// draw_bitmap() when color_data is one of frame_buffers and avoid_tearing is set - see the
|
// draw_bitmap() when color_data is one of frame_buffers and avoid_tearing is set - see the
|
||||||
// comment there for why.
|
// comment there for why.
|
||||||
SemaphoreHandle_t frame_complete_semaphore;
|
SemaphoreHandle_t frame_complete_semaphore;
|
||||||
|
// Signaled by on_color_trans_done once the panel driver's own copy/DMA2D transfer of a
|
||||||
|
// draw_bitmap() call's pixels into the target frame buffer completes. Unlike
|
||||||
|
// frame_complete_semaphore this is always waited on (regardless of allow_tearing or whether
|
||||||
|
// color_data is a real frame buffer) - see the comment on draw_bitmap() for why.
|
||||||
|
SemaphoreHandle_t color_trans_done_semaphore;
|
||||||
// Heap-allocated only when the devicetree supplies a custom init_sequence (see
|
// Heap-allocated only when the devicetree supplies a custom init_sequence (see
|
||||||
// parse_init_sequence()) - nullptr otherwise, since the vendor's built-in default sequence
|
// parse_init_sequence()) - nullptr otherwise, since the vendor's built-in default sequence
|
||||||
// needs no parsing. Its .data pointers alias directly into the devicetree's static const
|
// needs no parsing. Its .data pointers alias directly into the devicetree's static const
|
||||||
@@ -69,6 +74,30 @@ static bool on_refresh_done(esp_lcd_panel_handle_t, esp_lcd_dpi_panel_event_data
|
|||||||
return high_task_woken == pdTRUE;
|
return high_task_woken == pdTRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// esp_lcd_dpi_panel_draw_bitmap() copies (memcpy, or DMA2D when use_dma2d is set) the caller's
|
||||||
|
// pixels into the target frame buffer and returns immediately - the copy itself finishes
|
||||||
|
// asynchronously. Calling draw_bitmap() again before that copy completes hits the panel driver's
|
||||||
|
// own re-entrancy guard and logs "previous draw operation is not finished" (lcd.dsi). This is
|
||||||
|
// unconditional, unlike frame_complete_semaphore's tearing-avoidance wait: it protects the copy
|
||||||
|
// itself, not scan-out, so it must be waited on regardless of allow_tearing or which buffer
|
||||||
|
// color_data is.
|
||||||
|
//
|
||||||
|
// Unlike on_refresh_done (always ISR, from the VSYNC interrupt), esp_lcd_panel_dpi.c invokes this
|
||||||
|
// callback from two different contexts depending on the copy path: synchronously from the calling
|
||||||
|
// task for the CPU-memcpy and fb-direct (zero-copy) paths, but from an ISR (async_fbcpy_done_cb,
|
||||||
|
// the DMA2D completion interrupt) when use_dma2d is set. Giving a semaphore with the ISR-only API
|
||||||
|
// from task context is undefined behavior on FreeRTOS, so the context must be checked at runtime.
|
||||||
|
static bool on_color_trans_done(esp_lcd_panel_handle_t, esp_lcd_dpi_panel_event_data_t*, void* user_ctx) {
|
||||||
|
auto* internal = static_cast<St7123Internal*>(user_ctx);
|
||||||
|
if (xPortInIsrContext()) {
|
||||||
|
BaseType_t high_task_woken = pdFALSE;
|
||||||
|
xSemaphoreGiveFromISR(internal->color_trans_done_semaphore, &high_task_woken);
|
||||||
|
return high_task_woken == pdTRUE;
|
||||||
|
}
|
||||||
|
xSemaphoreGive(internal->color_trans_done_semaphore);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static int pin_or_unused(const GpioPinSpec& pin) {
|
static int pin_or_unused(const GpioPinSpec& pin) {
|
||||||
return pin.gpio_controller == nullptr ? -1 : static_cast<int>(pin.pin);
|
return pin.gpio_controller == nullptr ? -1 : static_cast<int>(pin.pin);
|
||||||
}
|
}
|
||||||
@@ -309,10 +338,27 @@ static error_t start(Device* device) {
|
|||||||
return ERROR_OUT_OF_MEMORY;
|
return ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal->color_trans_done_semaphore = xSemaphoreCreateBinary();
|
||||||
|
if (internal->color_trans_done_semaphore == nullptr) {
|
||||||
|
vSemaphoreDelete(internal->frame_complete_semaphore);
|
||||||
|
esp_lcd_panel_del(internal->panel_handle);
|
||||||
|
esp_lcd_panel_io_del(internal->io_handle);
|
||||||
|
esp_lcd_del_dsi_bus(internal->dsi_bus_handle);
|
||||||
|
esp_ldo_release_channel(internal->ldo_handle);
|
||||||
|
free(internal->parsed_init_cmds);
|
||||||
|
free(internal);
|
||||||
|
return ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
// The panel starts out idle (no draw_bitmap() call in flight), so the first draw_bitmap()
|
||||||
|
// must not block waiting for a completion event that will never come.
|
||||||
|
xSemaphoreGive(internal->color_trans_done_semaphore);
|
||||||
|
|
||||||
esp_lcd_dpi_panel_event_callbacks_t callbacks = {};
|
esp_lcd_dpi_panel_event_callbacks_t callbacks = {};
|
||||||
callbacks.on_refresh_done = on_refresh_done;
|
callbacks.on_refresh_done = on_refresh_done;
|
||||||
|
callbacks.on_color_trans_done = on_color_trans_done;
|
||||||
if (esp_lcd_dpi_panel_register_event_callbacks(internal->panel_handle, &callbacks, internal) != ESP_OK) {
|
if (esp_lcd_dpi_panel_register_event_callbacks(internal->panel_handle, &callbacks, internal) != ESP_OK) {
|
||||||
LOG_E(TAG, "Failed to register panel event callbacks");
|
LOG_E(TAG, "Failed to register panel event callbacks");
|
||||||
|
vSemaphoreDelete(internal->color_trans_done_semaphore);
|
||||||
vSemaphoreDelete(internal->frame_complete_semaphore);
|
vSemaphoreDelete(internal->frame_complete_semaphore);
|
||||||
esp_lcd_panel_del(internal->panel_handle);
|
esp_lcd_panel_del(internal->panel_handle);
|
||||||
esp_lcd_panel_io_del(internal->io_handle);
|
esp_lcd_panel_io_del(internal->io_handle);
|
||||||
@@ -363,6 +409,7 @@ static error_t stop(Device* device) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vSemaphoreDelete(internal->frame_complete_semaphore);
|
vSemaphoreDelete(internal->frame_complete_semaphore);
|
||||||
|
vSemaphoreDelete(internal->color_trans_done_semaphore);
|
||||||
free(internal->parsed_init_cmds);
|
free(internal->parsed_init_cmds);
|
||||||
free(internal);
|
free(internal);
|
||||||
device_set_driver_data(device, nullptr);
|
device_set_driver_data(device, nullptr);
|
||||||
@@ -402,15 +449,32 @@ static bool st7123_color_data_is_frame_buffer(const St7123Internal* internal, co
|
|||||||
static error_t st7123_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
|
static error_t st7123_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
|
||||||
auto* internal = static_cast<St7123Internal*>(device_get_driver_data(device));
|
auto* internal = static_cast<St7123Internal*>(device_get_driver_data(device));
|
||||||
|
|
||||||
|
// Wait for the previous draw_bitmap()'s copy/DMA2D transfer to finish before issuing a new
|
||||||
|
// one - see on_color_trans_done's comment for why this is unconditional (unlike the
|
||||||
|
// tearing-avoidance wait below). The semaphore starts pre-given (see start()), so the first
|
||||||
|
// call doesn't block here.
|
||||||
|
xSemaphoreTake(internal->color_trans_done_semaphore, portMAX_DELAY);
|
||||||
|
|
||||||
bool wait_for_scanout = !GET_CONFIG(device)->allow_tearing && st7123_color_data_is_frame_buffer(internal, color_data);
|
bool wait_for_scanout = !GET_CONFIG(device)->allow_tearing && st7123_color_data_is_frame_buffer(internal, color_data);
|
||||||
if (wait_for_scanout) {
|
if (wait_for_scanout) {
|
||||||
xSemaphoreTake(internal->frame_complete_semaphore, 0); // clear any already-pending signal
|
xSemaphoreTake(internal->frame_complete_semaphore, 0); // clear any already-pending signal
|
||||||
}
|
}
|
||||||
|
|
||||||
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) {
|
if (esp_lcd_panel_draw_bitmap(internal->panel_handle, x_start, y_start, x_end, y_end, color_data) != ESP_OK) {
|
||||||
|
xSemaphoreGive(internal->color_trans_done_semaphore);
|
||||||
return ERROR_RESOURCE;
|
return ERROR_RESOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also wait for *this* call's own copy to finish before returning: color_data may be a
|
||||||
|
// caller-owned buffer (e.g. LVGL's draw buffer in owned-buffer/PARTIAL mode), and the
|
||||||
|
// CPU-memcpy/DMA2D copy paths both read directly from it asynchronously - draw_bitmap()
|
||||||
|
// returning early would let the caller start overwriting color_data (DisplayApi's contract
|
||||||
|
// treats draw_bitmap as synchronous - see lvgl_display_flush_cb()'s lv_display_flush_ready()
|
||||||
|
// call right after) while that read is still in flight. on_color_trans_done() re-gives the
|
||||||
|
// semaphore here, restoring the pre-given/idle state for the next call.
|
||||||
|
xSemaphoreTake(internal->color_trans_done_semaphore, portMAX_DELAY);
|
||||||
|
xSemaphoreGive(internal->color_trans_done_semaphore);
|
||||||
|
|
||||||
if (wait_for_scanout) {
|
if (wait_for_scanout) {
|
||||||
xSemaphoreTake(internal->frame_complete_semaphore, portMAX_DELAY);
|
xSemaphoreTake(internal->frame_complete_semaphore, portMAX_DELAY);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,10 @@ list(APPEND REQUIRES_LIST
|
|||||||
|
|
||||||
if (DEFINED ENV{ESP_IDF_VERSION})
|
if (DEFINED ENV{ESP_IDF_VERSION})
|
||||||
list(APPEND REQUIRES_LIST esp_lvgl_port)
|
list(APPEND REQUIRES_LIST esp_lvgl_port)
|
||||||
|
# required for lvgl_ppa
|
||||||
|
if (IDF_TARGET STREQUAL "esp32p4")
|
||||||
|
list(APPEND REQUIRES_LIST esp_driver_ppa)
|
||||||
|
endif ()
|
||||||
else ()
|
else ()
|
||||||
list(APPEND REQUIRES_LIST freertos_kernel)
|
list(APPEND REQUIRES_LIST freertos_kernel)
|
||||||
endif ()
|
endif ()
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lvgl.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// True on targets with a PPA (Pixel Processing Accelerator) unit that lvgl_ppa can use to
|
||||||
|
// hardware-rotate rotated flush tiles instead of lv_draw_sw_rotate(). False (always) on the
|
||||||
|
// simulator and on ESP32 targets without a PPA (see SOC_PPA_SUPPORTED).
|
||||||
|
bool lvgl_ppa_is_supported(void);
|
||||||
|
|
||||||
|
// True when color_format has a PPA color mode this module supports (RGB565/RGB888 - see
|
||||||
|
// lvgl_ppa_color_mode()). LV_COLOR_FORMAT_I1 and others fall back to lv_draw_sw_rotate().
|
||||||
|
bool lvgl_ppa_supports_color_format(lv_color_format_t color_format);
|
||||||
|
|
||||||
|
// Lazily creates (on first call) a PPA client and an output buffer at least out_buffer_size_bytes
|
||||||
|
// large, big enough for the largest tile this display will ever rotate. Returns NULL on failure -
|
||||||
|
// callers should fall back to lv_draw_sw_rotate() when that happens. Not thread-safe; callers must
|
||||||
|
// already hold the LVGL lock (true for all flush_cb callers).
|
||||||
|
void* lvgl_ppa_get_or_create(size_t out_buffer_size_bytes);
|
||||||
|
|
||||||
|
void lvgl_ppa_delete(void* ppa_handle);
|
||||||
|
|
||||||
|
// Rotates the tightly-packed w x h block at in_buff by rotation (LV_DISPLAY_ROTATION_90/180/270 -
|
||||||
|
// numerically identical to ppa_srm_rotation_angle_t's CCW convention, see the .c file) into the
|
||||||
|
// PPA's own output buffer and returns it. in_buff must have no row padding (PPA reads pic_w/pic_h
|
||||||
|
// in pixels, not a byte stride) - true for every lvgl_display_flush_cb() tile, which LVGL always
|
||||||
|
// renders compact. Returns NULL on failure - caller should fall back to lv_draw_sw_rotate().
|
||||||
|
void* lvgl_ppa_rotate(
|
||||||
|
void* ppa_handle,
|
||||||
|
const uint8_t* in_buff,
|
||||||
|
int32_t w,
|
||||||
|
int32_t h,
|
||||||
|
lv_display_rotation_t rotation,
|
||||||
|
lv_color_format_t color_format,
|
||||||
|
bool swap_bytes
|
||||||
|
);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
#include <tactility/lvgl_display.h>
|
#include <tactility/lvgl_display.h>
|
||||||
|
|
||||||
|
#include <tactility/lvgl_ppa.h>
|
||||||
|
|
||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
#include <tactility/driver.h>
|
#include <tactility/driver.h>
|
||||||
#include <tactility/drivers/display.h>
|
#include <tactility/drivers/display.h>
|
||||||
@@ -37,6 +39,13 @@ struct LvglDisplayCtx {
|
|||||||
// display_mirror(); rotate_buf holds the rotated pixels and is sized like buf1.
|
// display_mirror(); rotate_buf holds the rotated pixels and is sized like buf1.
|
||||||
bool sw_rotate;
|
bool sw_rotate;
|
||||||
void* rotate_buf;
|
void* rotate_buf;
|
||||||
|
// Lazily created on the first sw_rotate flush that needs it (see lvgl_display_rotate_tile()).
|
||||||
|
// Stays NULL - and every rotate falls back to rotate_buf/lv_draw_sw_rotate() - when the target
|
||||||
|
// has no PPA (lvgl_ppa_is_supported()), the color format has no PPA color mode
|
||||||
|
// (lvgl_ppa_supports_color_format()), or creating the PPA client/buffer failed once already.
|
||||||
|
void* ppa_handle;
|
||||||
|
bool ppa_unavailable;
|
||||||
|
bool ppa_eligible;
|
||||||
// Size of buf1/buf2 (each) - used by lvgl_display_fb_base() to range-check which real buffer
|
// Size of buf1/buf2 (each) - used by lvgl_display_fb_base() to range-check which real buffer
|
||||||
// (for the fb-direct case) a given color_map pointer falls into.
|
// (for the fb-direct case) a given color_map pointer falls into.
|
||||||
size_t buf_size_bytes;
|
size_t buf_size_bytes;
|
||||||
@@ -166,7 +175,37 @@ static void* lvgl_display_fb_base(struct LvglDisplayCtx* ctx, const uint8_t* col
|
|||||||
return ctx->buf1;
|
return ctx->buf1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void lvgl_display_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* color_map) {
|
// Tries to rotate the tightly-packed w x h block at in_buff via PPA, returning the PPA output
|
||||||
|
// buffer on success or NULL if this tile/format/target can't use it - in which case the caller
|
||||||
|
// must fall back to lv_draw_sw_rotate() into ctx->rotate_buf. Lazily creates the PPA client on the
|
||||||
|
// first eligible call, sized to ctx->buf_size_bytes (the largest tile or full-frame buffer this
|
||||||
|
// display will ever flush - see lvgl_display_add()); once creation fails once, ppa_unavailable
|
||||||
|
// latches so later tiles don't retry it. Never asks PPA to byte-swap: lvgl_display_flush_cb()
|
||||||
|
// applies ctx->byte_swap itself, once, at a single point regardless of which rotation path ran -
|
||||||
|
// simpler than tracking whether it was already done by PPA (PARTIAL mode) vs. already done
|
||||||
|
// per-tile before FULL mode's whole-frame rotate (see the two call sites).
|
||||||
|
static void* lvgl_display_try_ppa_rotate(struct LvglDisplayCtx* ctx, const uint8_t* in_buff, int32_t w, int32_t h,
|
||||||
|
lv_display_rotation_t rotation, lv_color_format_t color_format) {
|
||||||
|
if (!ctx->ppa_eligible || ctx->ppa_unavailable) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
// PPA reads pic_w/pic_h in pixels with no separate stride - only safe when the tile has no
|
||||||
|
// row padding beyond w * bytes-per-pixel (see lvgl_ppa.h).
|
||||||
|
uint32_t bpp = lv_color_format_get_size(color_format);
|
||||||
|
if (lv_draw_buf_width_to_stride(w, color_format) != (uint32_t)w * bpp) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (ctx->ppa_handle == NULL) {
|
||||||
|
ctx->ppa_handle = lvgl_ppa_get_or_create(ctx->buf_size_bytes);
|
||||||
|
if (ctx->ppa_handle == NULL) {
|
||||||
|
ctx->ppa_unavailable = true;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lvgl_ppa_rotate(ctx->ppa_handle, in_buff, w, h, rotation, color_format, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lvgl_display_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* color_map) {
|
||||||
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_display_get_driver_data(disp);
|
struct LvglDisplayCtx* ctx = (struct LvglDisplayCtx*)lv_display_get_driver_data(disp);
|
||||||
bool is_i1 = lv_display_get_color_format(disp) == LV_COLOR_FORMAT_I1;
|
bool is_i1 = lv_display_get_color_format(disp) == LV_COLOR_FORMAT_I1;
|
||||||
|
|
||||||
@@ -190,14 +229,20 @@ static void* lvgl_display_fb_base(struct LvglDisplayCtx* ctx, const uint8_t* col
|
|||||||
lv_color_format_t color_format = lv_display_get_color_format(disp);
|
lv_color_format_t color_format = lv_display_get_color_format(disp);
|
||||||
int32_t w = x2 - x1 + 1;
|
int32_t w = x2 - x1 + 1;
|
||||||
int32_t h = y2 - y1 + 1;
|
int32_t h = y2 - y1 + 1;
|
||||||
uint32_t w_stride = lv_draw_buf_width_to_stride(w, color_format);
|
|
||||||
uint32_t h_stride = lv_draw_buf_width_to_stride(h, color_format);
|
void* ppa_out = lvgl_display_try_ppa_rotate(ctx, color_map, w, h, rotation, color_format);
|
||||||
if (rotation == LV_DISPLAY_ROTATION_180) {
|
if (ppa_out != NULL) {
|
||||||
lv_draw_sw_rotate(color_map, ctx->rotate_buf, w, h, w_stride, w_stride, rotation, color_format);
|
color_map = (uint8_t*)ppa_out;
|
||||||
} else {
|
} else {
|
||||||
lv_draw_sw_rotate(color_map, ctx->rotate_buf, w, h, w_stride, h_stride, rotation, color_format);
|
uint32_t w_stride = lv_draw_buf_width_to_stride(w, color_format);
|
||||||
|
uint32_t h_stride = lv_draw_buf_width_to_stride(h, color_format);
|
||||||
|
if (rotation == LV_DISPLAY_ROTATION_180) {
|
||||||
|
lv_draw_sw_rotate(color_map, ctx->rotate_buf, w, h, w_stride, w_stride, rotation, color_format);
|
||||||
|
} else {
|
||||||
|
lv_draw_sw_rotate(color_map, ctx->rotate_buf, w, h, w_stride, h_stride, rotation, color_format);
|
||||||
|
}
|
||||||
|
color_map = (uint8_t*)ctx->rotate_buf;
|
||||||
}
|
}
|
||||||
color_map = (uint8_t*)ctx->rotate_buf;
|
|
||||||
lv_area_t rotated_area = { x1, y1, x2, y2 };
|
lv_area_t rotated_area = { x1, y1, x2, y2 };
|
||||||
lv_display_rotate_area(disp, &rotated_area);
|
lv_display_rotate_area(disp, &rotated_area);
|
||||||
x1 = rotated_area.x1;
|
x1 = rotated_area.x1;
|
||||||
@@ -246,10 +291,16 @@ static void* lvgl_display_fb_base(struct LvglDisplayCtx* ctx, const uint8_t* col
|
|||||||
bool swapped_wh = rotation == LV_DISPLAY_ROTATION_90 || rotation == LV_DISPLAY_ROTATION_270;
|
bool swapped_wh = rotation == LV_DISPLAY_ROTATION_90 || rotation == LV_DISPLAY_ROTATION_270;
|
||||||
int32_t logical_w = swapped_wh ? (int32_t)vres : (int32_t)hres;
|
int32_t logical_w = swapped_wh ? (int32_t)vres : (int32_t)hres;
|
||||||
int32_t logical_h = swapped_wh ? (int32_t)hres : (int32_t)vres;
|
int32_t logical_h = swapped_wh ? (int32_t)hres : (int32_t)vres;
|
||||||
uint32_t src_stride = lv_draw_buf_width_to_stride(logical_w, color_format);
|
|
||||||
uint32_t dest_stride = lv_draw_buf_width_to_stride(hres, color_format);
|
void* ppa_out = lvgl_display_try_ppa_rotate(ctx, fb_base, logical_w, logical_h, rotation, color_format);
|
||||||
lv_draw_sw_rotate(fb_base, ctx->rotate_buf, logical_w, logical_h, src_stride, dest_stride, rotation, color_format);
|
if (ppa_out != NULL) {
|
||||||
fb_base = (uint8_t*)ctx->rotate_buf;
|
fb_base = (uint8_t*)ppa_out;
|
||||||
|
} else {
|
||||||
|
uint32_t src_stride = lv_draw_buf_width_to_stride(logical_w, color_format);
|
||||||
|
uint32_t dest_stride = lv_draw_buf_width_to_stride(hres, color_format);
|
||||||
|
lv_draw_sw_rotate(fb_base, ctx->rotate_buf, logical_w, logical_h, src_stride, dest_stride, rotation, color_format);
|
||||||
|
fb_base = (uint8_t*)ctx->rotate_buf;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
display_draw_bitmap(ctx->device, 0, 0, hres, vres, fb_base);
|
display_draw_bitmap(ctx->device, 0, 0, hres, vres, fb_base);
|
||||||
@@ -291,6 +342,13 @@ error_t lvgl_display_add(struct Device* device, const struct LvglDisplayConfig*
|
|||||||
ctx->device = device;
|
ctx->device = device;
|
||||||
ctx->byte_swap = config->swap_bytes;
|
ctx->byte_swap = config->swap_bytes;
|
||||||
ctx->sw_rotate = config->sw_rotate;
|
ctx->sw_rotate = config->sw_rotate;
|
||||||
|
// Only relevant when sw_rotate is set - lvgl_display_try_ppa_rotate() also checks
|
||||||
|
// ctx->ppa_eligible directly, so this is safe to compute unconditionally.
|
||||||
|
ctx->ppa_eligible = lvgl_ppa_is_supported() && lvgl_ppa_supports_color_format(lv_color_format);
|
||||||
|
if (config->sw_rotate && !ctx->ppa_eligible) {
|
||||||
|
LOG_I(TAG, "PPA not available for this display (supported=%d, color_format=%d) - using lv_draw_sw_rotate()",
|
||||||
|
(int)lvgl_ppa_is_supported(), (int)lv_color_format);
|
||||||
|
}
|
||||||
ctx->has_swap_xy_cap = display_has_capability(device, DISPLAY_CAPABILITY_CAP_SWAP_XY);
|
ctx->has_swap_xy_cap = display_has_capability(device, DISPLAY_CAPABILITY_CAP_SWAP_XY);
|
||||||
ctx->has_mirror_cap = display_has_capability(device, DISPLAY_CAPABILITY_CAP_MIRROR);
|
ctx->has_mirror_cap = display_has_capability(device, DISPLAY_CAPABILITY_CAP_MIRROR);
|
||||||
ctx->has_set_gap_cap = display_has_capability(device, DISPLAY_CAPABILITY_CAP_SET_GAP);
|
ctx->has_set_gap_cap = display_has_capability(device, DISPLAY_CAPABILITY_CAP_SET_GAP);
|
||||||
@@ -424,6 +482,9 @@ void lvgl_display_remove(lv_display_t* display) {
|
|||||||
if (ctx->rotate_buf != NULL) {
|
if (ctx->rotate_buf != NULL) {
|
||||||
lvgl_display_free_buffer(ctx->rotate_buf);
|
lvgl_display_free_buffer(ctx->rotate_buf);
|
||||||
}
|
}
|
||||||
|
if (ctx->ppa_handle != NULL) {
|
||||||
|
lvgl_ppa_delete(ctx->ppa_handle);
|
||||||
|
}
|
||||||
free(ctx);
|
free(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#include <tactility/lvgl_ppa.h>
|
||||||
|
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
#include <soc/soc_caps.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ESP_PLATFORM) && SOC_PPA_SUPPORTED
|
||||||
|
|
||||||
|
#include <driver/ppa.h>
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#define TAG "lvgl_ppa"
|
||||||
|
|
||||||
|
// PPA output buffer alignment requirement (address AND size - see lvgl_ppa_get_or_create()):
|
||||||
|
// cache line size is hardware-specific, but 64 bytes covers every current PPA-capable target's
|
||||||
|
// L1/L2 cache line size, so a fixed constant avoids needing per-SoC Kconfig lookups here.
|
||||||
|
#define LVGL_PPA_ALIGN 64
|
||||||
|
#define LVGL_PPA_ALIGN_UP(n) (((n) + (LVGL_PPA_ALIGN - 1)) & ~(size_t)(LVGL_PPA_ALIGN - 1))
|
||||||
|
|
||||||
|
struct LvglPpa {
|
||||||
|
ppa_client_handle_t client;
|
||||||
|
uint8_t* out_buffer;
|
||||||
|
size_t out_buffer_size;
|
||||||
|
bool logged_first_rotate;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool lvgl_ppa_is_supported(void) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lvgl_ppa_supports_color_format(lv_color_format_t color_format) {
|
||||||
|
return color_format == LV_COLOR_FORMAT_RGB565 || color_format == LV_COLOR_FORMAT_RGB888;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ppa_srm_color_mode_t lvgl_ppa_color_mode(lv_color_format_t color_format) {
|
||||||
|
return color_format == LV_COLOR_FORMAT_RGB888 ? PPA_SRM_COLOR_MODE_RGB888 : PPA_SRM_COLOR_MODE_RGB565;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* lvgl_ppa_get_or_create(size_t out_buffer_size_bytes) {
|
||||||
|
struct LvglPpa* ppa = (struct LvglPpa*)calloc(1, sizeof(struct LvglPpa));
|
||||||
|
if (ppa == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PPA requires the output buffer's size, not just its address, to be cache-line aligned (see
|
||||||
|
// ppa_out_pic_blk_config_t's buffer_size doc) - round up before allocating so both match.
|
||||||
|
size_t aligned_size = LVGL_PPA_ALIGN_UP(out_buffer_size_bytes);
|
||||||
|
ppa->out_buffer = (uint8_t*)heap_caps_aligned_alloc(LVGL_PPA_ALIGN, aligned_size, MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
|
||||||
|
if (ppa->out_buffer == NULL) {
|
||||||
|
ppa->out_buffer = (uint8_t*)heap_caps_aligned_alloc(LVGL_PPA_ALIGN, aligned_size, MALLOC_CAP_DEFAULT);
|
||||||
|
}
|
||||||
|
if (ppa->out_buffer == NULL) {
|
||||||
|
LOG_E(TAG, "Failed to allocate PPA output buffer (%u bytes)", (unsigned)aligned_size);
|
||||||
|
free(ppa);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ppa->out_buffer_size = aligned_size;
|
||||||
|
|
||||||
|
ppa_client_config_t client_config = {
|
||||||
|
.oper_type = PPA_OPERATION_SRM,
|
||||||
|
};
|
||||||
|
if (ppa_register_client(&client_config, &ppa->client) != ESP_OK) {
|
||||||
|
LOG_E(TAG, "Failed to register PPA client");
|
||||||
|
heap_caps_free(ppa->out_buffer);
|
||||||
|
free(ppa);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_I(TAG, "PPA client created (out buffer: %u bytes)", (unsigned)aligned_size);
|
||||||
|
return ppa;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lvgl_ppa_delete(void* ppa_handle) {
|
||||||
|
if (ppa_handle == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
struct LvglPpa* ppa = (struct LvglPpa*)ppa_handle;
|
||||||
|
ppa_unregister_client(ppa->client);
|
||||||
|
heap_caps_free(ppa->out_buffer);
|
||||||
|
free(ppa);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* lvgl_ppa_rotate(
|
||||||
|
void* ppa_handle,
|
||||||
|
const uint8_t* in_buff,
|
||||||
|
int32_t w,
|
||||||
|
int32_t h,
|
||||||
|
lv_display_rotation_t rotation,
|
||||||
|
lv_color_format_t color_format,
|
||||||
|
bool swap_bytes
|
||||||
|
) {
|
||||||
|
struct LvglPpa* ppa = (struct LvglPpa*)ppa_handle;
|
||||||
|
ppa_srm_color_mode_t color_mode = lvgl_ppa_color_mode(color_format);
|
||||||
|
bool swapped_wh = rotation == LV_DISPLAY_ROTATION_90 || rotation == LV_DISPLAY_ROTATION_270;
|
||||||
|
int32_t out_w = swapped_wh ? h : w;
|
||||||
|
int32_t out_h = swapped_wh ? w : h;
|
||||||
|
|
||||||
|
const ppa_srm_oper_config_t oper_config = {
|
||||||
|
.in = {
|
||||||
|
.buffer = in_buff,
|
||||||
|
.pic_w = (uint32_t)w,
|
||||||
|
.pic_h = (uint32_t)h,
|
||||||
|
.block_w = (uint32_t)w,
|
||||||
|
.block_h = (uint32_t)h,
|
||||||
|
.block_offset_x = 0,
|
||||||
|
.block_offset_y = 0,
|
||||||
|
.srm_cm = color_mode,
|
||||||
|
},
|
||||||
|
.out = {
|
||||||
|
.buffer = ppa->out_buffer,
|
||||||
|
.buffer_size = ppa->out_buffer_size,
|
||||||
|
.pic_w = (uint32_t)out_w,
|
||||||
|
.pic_h = (uint32_t)out_h,
|
||||||
|
.block_offset_x = 0,
|
||||||
|
.block_offset_y = 0,
|
||||||
|
.srm_cm = color_mode,
|
||||||
|
},
|
||||||
|
// LVGL rotation is CCW, same convention as ppa_srm_rotation_angle_t - see lvgl_ppa.h.
|
||||||
|
.rotation_angle = (ppa_srm_rotation_angle_t)rotation,
|
||||||
|
.scale_x = 1.0f,
|
||||||
|
.scale_y = 1.0f,
|
||||||
|
.byte_swap = swap_bytes,
|
||||||
|
.mode = PPA_TRANS_MODE_BLOCKING,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ppa_do_scale_rotate_mirror(ppa->client, &oper_config) != ESP_OK) {
|
||||||
|
LOG_E(TAG, "PPA rotate failed (%" PRId32 "x%" PRId32 " -> %" PRId32 "x%" PRId32 ")", w, h, out_w, out_h);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ppa->logged_first_rotate) {
|
||||||
|
LOG_I(TAG, "PPA rotate in use (%" PRId32 "x%" PRId32 " -> %" PRId32 "x%" PRId32 ", angle=%d)", w, h, out_w, out_h, (int)rotation);
|
||||||
|
ppa->logged_first_rotate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ppa->out_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // !(ESP_PLATFORM && SOC_PPA_SUPPORTED)
|
||||||
|
|
||||||
|
bool lvgl_ppa_is_supported(void) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lvgl_ppa_supports_color_format(lv_color_format_t color_format) {
|
||||||
|
(void)color_format;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* lvgl_ppa_get_or_create(size_t out_buffer_size_bytes) {
|
||||||
|
(void)out_buffer_size_bytes;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lvgl_ppa_delete(void* ppa_handle) {
|
||||||
|
(void)ppa_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* lvgl_ppa_rotate(
|
||||||
|
void* ppa_handle,
|
||||||
|
const uint8_t* in_buff,
|
||||||
|
int32_t w,
|
||||||
|
int32_t h,
|
||||||
|
lv_display_rotation_t rotation,
|
||||||
|
lv_color_format_t color_format,
|
||||||
|
bool swap_bytes
|
||||||
|
) {
|
||||||
|
(void)ppa_handle;
|
||||||
|
(void)in_buff;
|
||||||
|
(void)w;
|
||||||
|
(void)h;
|
||||||
|
(void)rotation;
|
||||||
|
(void)color_format;
|
||||||
|
(void)swap_bytes;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user