From e5d60ca44bced0ed1ea6348345376a1ded44a52b Mon Sep 17 00:00:00 2001 From: chenjintao Date: Tue, 11 Aug 2026 16:29:27 +0800 Subject: [PATCH] fix(platform): run LCD preheat on render task --- components/domain/include/domain.h | 6 +- components/platform/include/platform.h | 7 +- components/platform/src/lcd_direct.c | 161 +++++++++++++++++++++++-- 3 files changed, 158 insertions(+), 16 deletions(-) diff --git a/components/domain/include/domain.h b/components/domain/include/domain.h index 33d066e..a76a912 100644 --- a/components/domain/include/domain.h +++ b/components/domain/include/domain.h @@ -445,9 +445,9 @@ esp_err_t domain_runtime_request_expression(const domain_expression_request_t *r * @brief Preload assets for an expression without changing the active display. * * Intended for asset-backed expressions that are likely to play soon. The - * request is copied and the LCD render task performs the preload - * asynchronously. Unsupported or non-asset expressions return an explicit - * error. + * request is copied, the LCD render task performs the preload between display + * frames, and the caller waits for its result. Unsupported or non-asset + * expressions return an explicit error. */ esp_err_t domain_runtime_preheat_expression(const domain_expression_request_t *request); diff --git a/components/platform/include/platform.h b/components/platform/include/platform.h index 1e67ebb..dad4d28 100644 --- a/components/platform/include/platform.h +++ b/components/platform/include/platform.h @@ -426,14 +426,15 @@ esp_err_t platform_lcd_request_presentation(const platform_lcd_presentation_requ * @brief Preload the decoded assets for a LCD presentation without changing display state. * * Semantics: - * - Non-blocking. + * - Queues the preload on the LCD render task and blocks until it completes or + * the internal safety timeout elapses. * - Returns `ESP_ERR_INVALID_STATE` if the LCD is not initialized or is stopping. * - Request data is copied during the call; caller-owned pointers only need to * remain valid until this function returns. * - Only asset-sequence presentations are supported; other kinds return * `ESP_ERR_INVALID_ARG`. - * - The render task performs the preload incrementally and may drop older - * cached assets if memory is needed. + * - The render task performs the preload between display frames and may drop + * older cached assets if memory is needed. */ esp_err_t platform_lcd_preheat_presentation(const platform_lcd_presentation_request_t *request); diff --git a/components/platform/src/lcd_direct.c b/components/platform/src/lcd_direct.c index 8148ffc..2452a8c 100644 --- a/components/platform/src/lcd_direct.c +++ b/components/platform/src/lcd_direct.c @@ -45,6 +45,7 @@ #define LCD_DIRECT_PIXELS_PER_TRANSFER (LCD_DIRECT_WIDTH * LCD_DIRECT_ROWS_PER_TRANSFER) #define LCD_DIRECT_PRESENTATION_WAIT_POLL_MS 20U #define LCD_DIRECT_TRANSFER_TIMEOUT_MS 100U +#define LCD_DIRECT_PREHEAT_WAIT_TIMEOUT_MS 5000U #define LCD_DIRECT_FRAME_CACHE_MAX 4U #define LCD_DIRECT_PATH_MAX 96U #define LCD_DIRECT_LAST_FRAME_EXTRA_HOLD_MS 200U @@ -126,6 +127,7 @@ static esp_lcd_panel_io_handle_t s_panel_io; static esp_lcd_panel_handle_t s_panel; static SemaphoreHandle_t s_lock; static SemaphoreHandle_t s_completion_sem; +static SemaphoreHandle_t s_preheat_done_sem; static SemaphoreHandle_t s_transfer_done_sem; static TaskHandle_t s_render_task; static bool s_initialized; @@ -133,6 +135,13 @@ static bool s_render_loop_running; static bool s_stop_requested; static bool s_pending_valid; static lcd_direct_request_t s_pending; +static bool s_preheat_pending_valid; +static bool s_preheat_in_progress; +static bool s_preheat_waiter_active; +static lcd_direct_request_t s_preheat_pending; +static uint32_t s_preheat_generation; +static uint32_t s_preheat_completed_generation; +static esp_err_t s_preheat_result; static uint32_t s_completion_count; static portMUX_TYPE s_state_lock = portMUX_INITIALIZER_UNLOCKED; static uint16_t s_line_buffer[LCD_DIRECT_PIXELS_PER_TRANSFER]; @@ -842,6 +851,85 @@ static esp_err_t lcd_direct_preheat_basic_face(void) return lcd_direct_preheat_asset_sequence(&request); } +static bool lcd_direct_preheat_is_pending(void) +{ + bool pending; + portENTER_CRITICAL(&s_state_lock); + pending = s_preheat_pending_valid; + portEXIT_CRITICAL(&s_state_lock); + return pending; +} + +static bool lcd_direct_take_preheat(lcd_direct_request_t *out_request, + uint32_t *out_generation) +{ + bool has_request = false; + portENTER_CRITICAL(&s_state_lock); + if (s_preheat_pending_valid) { + *out_request = s_preheat_pending; + *out_generation = s_preheat_generation; + memset(&s_preheat_pending, 0, sizeof(s_preheat_pending)); + s_preheat_pending_valid = false; + s_preheat_in_progress = true; + has_request = true; + } + portEXIT_CRITICAL(&s_state_lock); + return has_request; +} + +static void lcd_direct_complete_preheat(uint32_t generation, esp_err_t result) +{ + portENTER_CRITICAL(&s_state_lock); + s_preheat_result = result; + s_preheat_completed_generation = generation; + s_preheat_in_progress = false; + portEXIT_CRITICAL(&s_state_lock); + + if (s_preheat_done_sem != NULL) { + (void)xSemaphoreGive(s_preheat_done_sem); + } +} + +/* Called only by the LCD render task while it owns s_lock. */ +static bool lcd_direct_service_preheat(void) +{ + lcd_direct_request_t request = {0}; + uint32_t generation = 0; + if (!lcd_direct_take_preheat(&request, &generation)) { + return false; + } + + esp_err_t result = lcd_direct_preheat_asset_sequence(&request); + + lcd_direct_complete_preheat(generation, result); + ESP_LOGI(TAG, + "LCD preheat completed asset=%s result=%s", + request.asset_dir, + esp_err_to_name(result)); + return true; +} + +static void lcd_direct_cancel_preheat(esp_err_t result) +{ + bool signal_waiter = false; + uint32_t generation = 0; + portENTER_CRITICAL(&s_state_lock); + if (s_preheat_pending_valid || s_preheat_in_progress) { + generation = s_preheat_generation; + memset(&s_preheat_pending, 0, sizeof(s_preheat_pending)); + s_preheat_pending_valid = false; + s_preheat_in_progress = false; + s_preheat_result = result; + s_preheat_completed_generation = generation; + signal_waiter = true; + } + portEXIT_CRITICAL(&s_state_lock); + + if (signal_waiter && s_preheat_done_sem != NULL) { + (void)xSemaphoreGive(s_preheat_done_sem); + } +} + static uint32_t lcd_direct_asset_sequence_frame_ms(const lcd_direct_request_t *request) { return (request != NULL && request->frame_ms > 0U) ? request->frame_ms : 100U; @@ -906,6 +994,7 @@ static esp_err_t lcd_direct_draw_asset_sequence_once(const lcd_direct_request_t const size_t frame_count = request->frame_order_count > 0U ? request->frame_order_count : 1U; for (size_t i = 0; i < frame_count; ++i) { + (void)lcd_direct_service_preheat(); bool stop = false; portENTER_CRITICAL(&s_state_lock); stop = s_stop_requested || s_pending_valid; @@ -957,6 +1046,7 @@ static esp_err_t lcd_direct_draw_asset_sequence_for_duration(const lcd_direct_re const size_t frame_count = request->frame_order_count > 0U ? request->frame_order_count : 1U; while ((xTaskGetTickCount() - start_tick) < duration_ticks) { for (size_t i = 0; i < frame_count; ++i) { + (void)lcd_direct_service_preheat(); bool stop = false; portENTER_CRITICAL(&s_state_lock); stop = s_stop_requested || s_pending_valid; @@ -1054,6 +1144,7 @@ static esp_err_t lcd_direct_play_step_sequence(const lcd_direct_request_t *reque "invalid step sequence"); for (size_t i = 0; i < request->step_count; ++i) { + (void)lcd_direct_service_preheat(); bool stop = false; portENTER_CRITICAL(&s_state_lock); stop = s_stop_requested || s_pending_valid; @@ -1108,6 +1199,7 @@ static esp_err_t lcd_direct_play_basic_face_until_pending(void) }; while (true) { + (void)lcd_direct_service_preheat(); bool stop = false; portENTER_CRITICAL(&s_state_lock); stop = s_stop_requested || s_pending_valid; @@ -1157,6 +1249,8 @@ esp_err_t lcd_direct_init_device(void) ESP_RETURN_ON_FALSE(s_lock != NULL, ESP_ERR_NO_MEM, TAG, "lock create failed"); s_completion_sem = xSemaphoreCreateBinary(); ESP_RETURN_ON_FALSE(s_completion_sem != NULL, ESP_ERR_NO_MEM, TAG, "completion sem create failed"); + s_preheat_done_sem = xSemaphoreCreateBinary(); + ESP_RETURN_ON_FALSE(s_preheat_done_sem != NULL, ESP_ERR_NO_MEM, TAG, "preheat sem create failed"); s_transfer_done_sem = xSemaphoreCreateBinary(); ESP_RETURN_ON_FALSE(s_transfer_done_sem != NULL, ESP_ERR_NO_MEM, @@ -1217,7 +1311,12 @@ void lcd_direct_start_render_loop(void) } lcd_direct_request_t request = {0}; - if (lcd_direct_take_pending(&request)) { + if (lcd_direct_preheat_is_pending()) { + if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) == pdTRUE) { + (void)lcd_direct_service_preheat(); + xSemaphoreGive(s_lock); + } + } else if (lcd_direct_take_pending(&request)) { if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) == pdTRUE) { esp_err_t draw_err = ESP_OK; if (request.kind == PLATFORM_LCD_PRESENTATION_IDLE) { @@ -1266,6 +1365,7 @@ void lcd_direct_start_render_loop(void) (void)ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(20)); } + lcd_direct_cancel_preheat(ESP_ERR_INVALID_STATE); portENTER_CRITICAL(&s_state_lock); s_render_task = NULL; s_render_loop_running = false; @@ -1319,6 +1419,7 @@ esp_err_t lcd_direct_stop_render_loop(int timeout_ms) s_render_loop_running = false; s_stop_requested = false; portEXIT_CRITICAL(&s_state_lock); + lcd_direct_cancel_preheat(ESP_ERR_INVALID_STATE); } return ESP_ERR_TIMEOUT; } @@ -1396,17 +1497,57 @@ esp_err_t lcd_direct_preheat_presentation(const platform_lcd_presentation_reques memcpy(copied.frame_order, request->frame_order, copied.frame_order_count); } - if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) != pdTRUE) { - return ESP_ERR_TIMEOUT; + if (copied.kind != PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) { + return ESP_ERR_INVALID_ARG; } - esp_err_t err = ESP_ERR_INVALID_ARG; - if (copied.kind == PLATFORM_LCD_PRESENTATION_IDLE) { - err = lcd_direct_preheat_basic_face(); - } else if (copied.kind == PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) { - err = lcd_direct_preheat_asset_sequence(&copied); + + TaskHandle_t render_task = NULL; + uint32_t generation = 0; + portENTER_CRITICAL(&s_state_lock); + if (!s_render_loop_running || s_stop_requested || s_render_task == NULL || + s_preheat_pending_valid || s_preheat_in_progress || s_preheat_waiter_active) { + portEXIT_CRITICAL(&s_state_lock); + return ESP_ERR_INVALID_STATE; } - xSemaphoreGive(s_lock); - return err; + /* Reserve the single request/wait slot before touching the completion + * semaphore so a concurrent caller cannot consume this caller's signal. */ + s_preheat_waiter_active = true; + portEXIT_CRITICAL(&s_state_lock); + + /* Discard a completion left by a caller that timed out after submission. */ + (void)xSemaphoreTake(s_preheat_done_sem, 0); + + portENTER_CRITICAL(&s_state_lock); + if (!s_render_loop_running || s_stop_requested || s_render_task == NULL) { + s_preheat_waiter_active = false; + portEXIT_CRITICAL(&s_state_lock); + return ESP_ERR_INVALID_STATE; + } + s_preheat_generation++; + if (s_preheat_generation == 0U) { + s_preheat_generation = 1U; + } + generation = s_preheat_generation; + s_preheat_pending = copied; + s_preheat_pending_valid = true; + render_task = s_render_task; + portEXIT_CRITICAL(&s_state_lock); + + xTaskNotifyGive(render_task); + ESP_LOGI(TAG, "LCD preheat queued asset=%s", copied.asset_dir); + + BaseType_t notified = xSemaphoreTake(s_preheat_done_sem, + pdMS_TO_TICKS(LCD_DIRECT_PREHEAT_WAIT_TIMEOUT_MS)); + esp_err_t result = ESP_ERR_TIMEOUT; + portENTER_CRITICAL(&s_state_lock); + if (s_preheat_completed_generation == generation) { + result = s_preheat_result; + } else if (notified == pdTRUE) { + result = ESP_ERR_INVALID_STATE; + } + s_preheat_waiter_active = false; + portEXIT_CRITICAL(&s_state_lock); + return result; } uint32_t lcd_direct_presentation_completion_count(void)