fix(platform): run LCD preheat on render task
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user