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.
|
* @brief Preload assets for an expression without changing the active display.
|
||||||
*
|
*
|
||||||
* Intended for asset-backed expressions that are likely to play soon. The
|
* Intended for asset-backed expressions that are likely to play soon. The
|
||||||
* request is copied and the LCD render task performs the preload
|
* request is copied, the LCD render task performs the preload between display
|
||||||
* asynchronously. Unsupported or non-asset expressions return an explicit
|
* frames, and the caller waits for its result. Unsupported or non-asset
|
||||||
* error.
|
* expressions return an explicit error.
|
||||||
*/
|
*/
|
||||||
esp_err_t domain_runtime_preheat_expression(const domain_expression_request_t *request);
|
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.
|
* @brief Preload the decoded assets for a LCD presentation without changing display state.
|
||||||
*
|
*
|
||||||
* Semantics:
|
* 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.
|
* - 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
|
* - Request data is copied during the call; caller-owned pointers only need to
|
||||||
* remain valid until this function returns.
|
* remain valid until this function returns.
|
||||||
* - Only asset-sequence presentations are supported; other kinds return
|
* - Only asset-sequence presentations are supported; other kinds return
|
||||||
* `ESP_ERR_INVALID_ARG`.
|
* `ESP_ERR_INVALID_ARG`.
|
||||||
* - The render task performs the preload incrementally and may drop older
|
* - The render task performs the preload between display frames and may drop
|
||||||
* cached assets if memory is needed.
|
* older cached assets if memory is needed.
|
||||||
*/
|
*/
|
||||||
esp_err_t platform_lcd_preheat_presentation(const platform_lcd_presentation_request_t *request);
|
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_PIXELS_PER_TRANSFER (LCD_DIRECT_WIDTH * LCD_DIRECT_ROWS_PER_TRANSFER)
|
||||||
#define LCD_DIRECT_PRESENTATION_WAIT_POLL_MS 20U
|
#define LCD_DIRECT_PRESENTATION_WAIT_POLL_MS 20U
|
||||||
#define LCD_DIRECT_TRANSFER_TIMEOUT_MS 100U
|
#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_FRAME_CACHE_MAX 4U
|
||||||
#define LCD_DIRECT_PATH_MAX 96U
|
#define LCD_DIRECT_PATH_MAX 96U
|
||||||
#define LCD_DIRECT_LAST_FRAME_EXTRA_HOLD_MS 200U
|
#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 esp_lcd_panel_handle_t s_panel;
|
||||||
static SemaphoreHandle_t s_lock;
|
static SemaphoreHandle_t s_lock;
|
||||||
static SemaphoreHandle_t s_completion_sem;
|
static SemaphoreHandle_t s_completion_sem;
|
||||||
|
static SemaphoreHandle_t s_preheat_done_sem;
|
||||||
static SemaphoreHandle_t s_transfer_done_sem;
|
static SemaphoreHandle_t s_transfer_done_sem;
|
||||||
static TaskHandle_t s_render_task;
|
static TaskHandle_t s_render_task;
|
||||||
static bool s_initialized;
|
static bool s_initialized;
|
||||||
@@ -133,6 +135,13 @@ static bool s_render_loop_running;
|
|||||||
static bool s_stop_requested;
|
static bool s_stop_requested;
|
||||||
static bool s_pending_valid;
|
static bool s_pending_valid;
|
||||||
static lcd_direct_request_t s_pending;
|
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 uint32_t s_completion_count;
|
||||||
static portMUX_TYPE s_state_lock = portMUX_INITIALIZER_UNLOCKED;
|
static portMUX_TYPE s_state_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||||
static uint16_t s_line_buffer[LCD_DIRECT_PIXELS_PER_TRANSFER];
|
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);
|
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)
|
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;
|
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;
|
const size_t frame_count = request->frame_order_count > 0U ? request->frame_order_count : 1U;
|
||||||
for (size_t i = 0; i < frame_count; ++i) {
|
for (size_t i = 0; i < frame_count; ++i) {
|
||||||
|
(void)lcd_direct_service_preheat();
|
||||||
bool stop = false;
|
bool stop = false;
|
||||||
portENTER_CRITICAL(&s_state_lock);
|
portENTER_CRITICAL(&s_state_lock);
|
||||||
stop = s_stop_requested || s_pending_valid;
|
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;
|
const size_t frame_count = request->frame_order_count > 0U ? request->frame_order_count : 1U;
|
||||||
while ((xTaskGetTickCount() - start_tick) < duration_ticks) {
|
while ((xTaskGetTickCount() - start_tick) < duration_ticks) {
|
||||||
for (size_t i = 0; i < frame_count; ++i) {
|
for (size_t i = 0; i < frame_count; ++i) {
|
||||||
|
(void)lcd_direct_service_preheat();
|
||||||
bool stop = false;
|
bool stop = false;
|
||||||
portENTER_CRITICAL(&s_state_lock);
|
portENTER_CRITICAL(&s_state_lock);
|
||||||
stop = s_stop_requested || s_pending_valid;
|
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");
|
"invalid step sequence");
|
||||||
|
|
||||||
for (size_t i = 0; i < request->step_count; ++i) {
|
for (size_t i = 0; i < request->step_count; ++i) {
|
||||||
|
(void)lcd_direct_service_preheat();
|
||||||
bool stop = false;
|
bool stop = false;
|
||||||
portENTER_CRITICAL(&s_state_lock);
|
portENTER_CRITICAL(&s_state_lock);
|
||||||
stop = s_stop_requested || s_pending_valid;
|
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) {
|
while (true) {
|
||||||
|
(void)lcd_direct_service_preheat();
|
||||||
bool stop = false;
|
bool stop = false;
|
||||||
portENTER_CRITICAL(&s_state_lock);
|
portENTER_CRITICAL(&s_state_lock);
|
||||||
stop = s_stop_requested || s_pending_valid;
|
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");
|
ESP_RETURN_ON_FALSE(s_lock != NULL, ESP_ERR_NO_MEM, TAG, "lock create failed");
|
||||||
s_completion_sem = xSemaphoreCreateBinary();
|
s_completion_sem = xSemaphoreCreateBinary();
|
||||||
ESP_RETURN_ON_FALSE(s_completion_sem != NULL, ESP_ERR_NO_MEM, TAG, "completion sem create failed");
|
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();
|
s_transfer_done_sem = xSemaphoreCreateBinary();
|
||||||
ESP_RETURN_ON_FALSE(s_transfer_done_sem != NULL,
|
ESP_RETURN_ON_FALSE(s_transfer_done_sem != NULL,
|
||||||
ESP_ERR_NO_MEM,
|
ESP_ERR_NO_MEM,
|
||||||
@@ -1217,7 +1311,12 @@ void lcd_direct_start_render_loop(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
lcd_direct_request_t request = {0};
|
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) {
|
if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) == pdTRUE) {
|
||||||
esp_err_t draw_err = ESP_OK;
|
esp_err_t draw_err = ESP_OK;
|
||||||
if (request.kind == PLATFORM_LCD_PRESENTATION_IDLE) {
|
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));
|
(void)ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(20));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lcd_direct_cancel_preheat(ESP_ERR_INVALID_STATE);
|
||||||
portENTER_CRITICAL(&s_state_lock);
|
portENTER_CRITICAL(&s_state_lock);
|
||||||
s_render_task = NULL;
|
s_render_task = NULL;
|
||||||
s_render_loop_running = false;
|
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_render_loop_running = false;
|
||||||
s_stop_requested = false;
|
s_stop_requested = false;
|
||||||
portEXIT_CRITICAL(&s_state_lock);
|
portEXIT_CRITICAL(&s_state_lock);
|
||||||
|
lcd_direct_cancel_preheat(ESP_ERR_INVALID_STATE);
|
||||||
}
|
}
|
||||||
return ESP_ERR_TIMEOUT;
|
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);
|
memcpy(copied.frame_order, request->frame_order, copied.frame_order_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) != pdTRUE) {
|
if (copied.kind != PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) {
|
||||||
return ESP_ERR_TIMEOUT;
|
return ESP_ERR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
esp_err_t err = ESP_ERR_INVALID_ARG;
|
|
||||||
if (copied.kind == PLATFORM_LCD_PRESENTATION_IDLE) {
|
TaskHandle_t render_task = NULL;
|
||||||
err = lcd_direct_preheat_basic_face();
|
uint32_t generation = 0;
|
||||||
} else if (copied.kind == PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) {
|
portENTER_CRITICAL(&s_state_lock);
|
||||||
err = lcd_direct_preheat_asset_sequence(&copied);
|
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);
|
/* Reserve the single request/wait slot before touching the completion
|
||||||
return err;
|
* 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)
|
uint32_t lcd_direct_presentation_completion_count(void)
|
||||||
|
|||||||
Reference in New Issue
Block a user