fix(platform): run LCD preheat on render task

This commit is contained in:
chenjintao
2026-08-11 16:29:27 +08:00
parent 0f47e6b585
commit e5d60ca44b
3 changed files with 158 additions and 16 deletions
+4 -3
View File
@@ -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);
+151 -10
View File
@@ -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)