perf(lcd): precompose emote updates and smooth closed eyes
This commit is contained in:
@@ -101,7 +101,9 @@
|
||||
#if CONFIG_TQ_LCD_EMOTE_GFX_TEST
|
||||
#define LCD_DIRECT_HUD_ROWS 40U
|
||||
#define LCD_DIRECT_EMOTE_FPS 30U
|
||||
#define LCD_DIRECT_EMOTE_BUFFER_ROWS 4U
|
||||
#define LCD_DIRECT_EMOTE_DMA_ROWS 16U
|
||||
#define LCD_DIRECT_EMOTE_CLOSED_ROWS 14U
|
||||
#define LCD_DIRECT_EMOTE_CLOSED_THRESHOLD 232U
|
||||
#define LCD_DIRECT_EMOTE_EYES_X 41
|
||||
#define LCD_DIRECT_EMOTE_EYES_Y 65
|
||||
#define LCD_DIRECT_EMOTE_OPEN_MS 2000U
|
||||
@@ -254,7 +256,13 @@ static gfx_handle_t s_emote_handle;
|
||||
static gfx_disp_t *s_emote_disp;
|
||||
static gfx_obj_t *s_emote_eyes_open;
|
||||
static gfx_mesh_img_point_q8_t s_emote_eye_rest[LCD_DIRECT_EMOTE_MESH_POINTS];
|
||||
static gfx_disp_t *s_emote_flush_pending;
|
||||
static uint16_t *s_emote_dma_pixels;
|
||||
static esp_err_t s_emote_flush_error;
|
||||
static bool s_emote_transfer_fault;
|
||||
static gfx_obj_t *s_emote_closed_eyes;
|
||||
static gfx_image_dsc_t s_emote_closed_image;
|
||||
static uint8_t *s_emote_closed_pixels;
|
||||
static bool s_emote_show_closed;
|
||||
static bool s_emote_active;
|
||||
#endif
|
||||
|
||||
@@ -288,16 +296,7 @@ static bool IRAM_ATTR lcd_direct_on_color_trans_done(esp_lcd_panel_io_handle_t p
|
||||
(void)user_ctx;
|
||||
|
||||
BaseType_t higher_priority_task_woken = pdFALSE;
|
||||
#if CONFIG_TQ_LCD_EMOTE_GFX_TEST
|
||||
gfx_disp_t *emote_disp = NULL;
|
||||
portENTER_CRITICAL_ISR(&s_state_lock);
|
||||
emote_disp = s_emote_flush_pending;
|
||||
s_emote_flush_pending = NULL;
|
||||
portEXIT_CRITICAL_ISR(&s_state_lock);
|
||||
if (emote_disp != NULL) {
|
||||
(void)gfx_disp_flush_ready(emote_disp, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (s_transfer_done_sem != NULL) {
|
||||
(void)xSemaphoreGiveFromISR(s_transfer_done_sem, &higher_priority_task_woken);
|
||||
}
|
||||
@@ -314,26 +313,46 @@ static void lcd_direct_emote_flush(gfx_disp_t *disp,
|
||||
{
|
||||
portENTER_CRITICAL(&s_state_lock);
|
||||
bool active = s_emote_active;
|
||||
if (active) {
|
||||
s_emote_flush_pending = disp;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_state_lock);
|
||||
|
||||
if (!active) {
|
||||
if (!active || s_emote_transfer_fault) {
|
||||
(void)gfx_disp_flush_ready(disp, false);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_lcd_panel_draw_bitmap(s_panel, x1, y1, x2, y2, data);
|
||||
if (err != ESP_OK) {
|
||||
portENTER_CRITICAL(&s_state_lock);
|
||||
if (s_emote_flush_pending == disp) {
|
||||
s_emote_flush_pending = NULL;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_state_lock);
|
||||
ESP_LOGE(TAG, "esp_emote_gfx flush failed: %s", esp_err_to_name(err));
|
||||
/* GFX 已在 PSRAM 中合成完整区域。这里仅复制/传输,不穿插网格计算。
|
||||
* 单 DMA 暂存区必须等 ISR 确认完成才能复用;整区域发完再通知 GFX。 */
|
||||
const uint16_t *source = data;
|
||||
const size_t width = (size_t)(x2 - x1);
|
||||
if (width == 0 || width > LCD_DIRECT_WIDTH || y2 <= y1 || s_emote_dma_pixels == NULL) {
|
||||
s_emote_flush_error = ESP_ERR_INVALID_SIZE;
|
||||
(void)gfx_disp_flush_ready(disp, false);
|
||||
return;
|
||||
}
|
||||
const size_t capacity = LCD_DIRECT_WIDTH * LCD_DIRECT_EMOTE_DMA_ROWS;
|
||||
for (int y = y1; y < y2;) {
|
||||
size_t rows = capacity / width;
|
||||
if (rows > (size_t)(y2 - y)) {
|
||||
rows = (size_t)(y2 - y);
|
||||
}
|
||||
memcpy(s_emote_dma_pixels, source, width * rows * sizeof(uint16_t));
|
||||
(void)xSemaphoreTake(s_transfer_done_sem, 0);
|
||||
esp_err_t err = esp_lcd_panel_draw_bitmap(s_panel, x1, y, x2, y + rows,
|
||||
s_emote_dma_pixels);
|
||||
if (err == ESP_OK && xSemaphoreTake(s_transfer_done_sem,
|
||||
pdMS_TO_TICKS(LCD_DIRECT_TRANSFER_TIMEOUT_MS)) != pdTRUE) {
|
||||
err = ESP_ERR_TIMEOUT;
|
||||
/* 超时后不再复用可能仍被 DMA 读取的缓冲,重启前禁止此路径传输。 */
|
||||
s_emote_transfer_fault = true;
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
s_emote_flush_error = err;
|
||||
ESP_LOGE(TAG, "composed emote transfer failed: %s", esp_err_to_name(err));
|
||||
break;
|
||||
}
|
||||
source += width * rows;
|
||||
y += rows;
|
||||
}
|
||||
(void)gfx_disp_flush_ready(disp, false);
|
||||
}
|
||||
|
||||
static esp_err_t lcd_direct_emote_decode_asset(const lcd_emote_rle_asset_t *asset,
|
||||
@@ -386,6 +405,44 @@ static esp_err_t lcd_direct_emote_prepare_assets(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* 一条带覆盖度的抛物线代替被压成碎像素的双层眼圈。无新图片资源。 */
|
||||
static void lcd_direct_emote_draw_closed_eyes(uint8_t *dst, uint16_t width,
|
||||
uint16_t height, uint16_t color)
|
||||
{
|
||||
const size_t count = (size_t)width * height;
|
||||
memset(dst, 0, count * 3);
|
||||
for (int x = 0; x < width; ++x) {
|
||||
int dx = x - (x < 119 ? 49 : 189);
|
||||
if (abs(dx) > 46) {
|
||||
continue;
|
||||
}
|
||||
int y_q8 = 3 * 256 + dx * dx * (8 * 256) / (46 * 46);
|
||||
for (int y = 0; y < height; ++y) {
|
||||
int coverage = 2 * 256 - abs(y * 256 - y_q8);
|
||||
if (coverage <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (coverage > 255) {
|
||||
coverage = 255;
|
||||
}
|
||||
size_t index = (size_t)y * width + x;
|
||||
dst[index * 2] = color >> 8;
|
||||
dst[index * 2 + 1] = color & 0xFF;
|
||||
dst[count * 2 + index] = coverage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t lcd_direct_emote_refresh(void)
|
||||
{
|
||||
if (s_emote_transfer_fault) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
s_emote_flush_error = ESP_OK;
|
||||
esp_err_t err = gfx_refr_now(s_emote_handle);
|
||||
return err != ESP_OK ? err : s_emote_flush_error;
|
||||
}
|
||||
|
||||
static esp_err_t lcd_direct_emote_init(void)
|
||||
{
|
||||
ESP_RETURN_ON_ERROR(lcd_direct_emote_prepare_assets(), TAG, "decode emote assets failed");
|
||||
@@ -402,17 +459,20 @@ static esp_err_t lcd_direct_emote_init(void)
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
s_emote_dma_pixels = heap_caps_malloc(LCD_DIRECT_WIDTH * LCD_DIRECT_EMOTE_DMA_ROWS *
|
||||
sizeof(uint16_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
|
||||
ESP_RETURN_ON_FALSE(s_emote_dma_pixels != NULL, ESP_ERR_NO_MEM, TAG, "allocate emote DMA failed");
|
||||
const gfx_disp_config_t display_config = {
|
||||
.h_res = LCD_DIRECT_WIDTH,
|
||||
.v_res = LCD_DIRECT_HEIGHT,
|
||||
.flush_cb = lcd_direct_emote_flush,
|
||||
.flags = {
|
||||
.swap = true,
|
||||
.buff_dma = true,
|
||||
.double_buffer = true,
|
||||
.buff_spiram = true,
|
||||
.double_buffer = false,
|
||||
},
|
||||
.buffers = {
|
||||
.buf_pixels = LCD_DIRECT_WIDTH * LCD_DIRECT_EMOTE_BUFFER_ROWS,
|
||||
.buf_pixels = LCD_DIRECT_WIDTH * LCD_DIRECT_HEIGHT,
|
||||
},
|
||||
};
|
||||
s_emote_disp = gfx_disp_add(s_emote_handle, &display_config);
|
||||
@@ -443,6 +503,33 @@ static esp_err_t lcd_direct_emote_init(void)
|
||||
ESP_RETURN_ON_ERROR(gfx_mesh_img_get_point_q8(s_emote_eyes_open, i, &s_emote_eye_rest[i]),
|
||||
TAG, "read eye mesh failed");
|
||||
}
|
||||
const size_t closed_count = idle_eyes_open_rle_asset.width * LCD_DIRECT_EMOTE_CLOSED_ROWS;
|
||||
s_emote_closed_pixels = heap_caps_malloc(closed_count * 3, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
ESP_RETURN_ON_FALSE(s_emote_closed_pixels != NULL, ESP_ERR_NO_MEM, TAG, "allocate closed eyes failed");
|
||||
uint16_t eye_color = 0xFFFF;
|
||||
const size_t eye_count = idle_eyes_open_rle_asset.width * idle_eyes_open_rle_asset.height;
|
||||
for (size_t i = 0; i < eye_count; ++i) {
|
||||
if (s_emote_eye_pixels[eye_count * 2 + i] >= 200) {
|
||||
eye_color = ((uint16_t)s_emote_eye_pixels[i * 2] << 8) | s_emote_eye_pixels[i * 2 + 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
lcd_direct_emote_draw_closed_eyes(s_emote_closed_pixels, idle_eyes_open_rle_asset.width,
|
||||
LCD_DIRECT_EMOTE_CLOSED_ROWS, eye_color);
|
||||
s_emote_closed_image = (gfx_image_dsc_t) {
|
||||
.header.cf = GFX_COLOR_FORMAT_RGB565A8,
|
||||
.header.magic = C_ARRAY_HEADER_MAGIC,
|
||||
.header.w = idle_eyes_open_rle_asset.width,
|
||||
.header.h = LCD_DIRECT_EMOTE_CLOSED_ROWS,
|
||||
.data_size = closed_count * 3,
|
||||
.data = s_emote_closed_pixels,
|
||||
};
|
||||
s_emote_closed_eyes = gfx_img_create(s_emote_disp);
|
||||
ESP_RETURN_ON_FALSE(s_emote_closed_eyes != NULL, ESP_ERR_NO_MEM, TAG, "create closed eyes failed");
|
||||
ESP_RETURN_ON_ERROR(gfx_img_set_src(s_emote_closed_eyes, &s_emote_closed_image), TAG, "set closed eyes failed");
|
||||
ESP_RETURN_ON_ERROR(gfx_obj_set_pos(s_emote_closed_eyes, LCD_DIRECT_EMOTE_EYES_X,
|
||||
LCD_DIRECT_EMOTE_EYES_Y + 39), TAG, "position closed eyes failed");
|
||||
ESP_RETURN_ON_ERROR(gfx_obj_set_visible(s_emote_closed_eyes, false), TAG, "hide closed eyes failed");
|
||||
s_emote_hud_pixels = heap_caps_calloc(LCD_DIRECT_WIDTH * LCD_DIRECT_HUD_ROWS,
|
||||
sizeof(uint16_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
ESP_RETURN_ON_FALSE(s_emote_hud_pixels != NULL, ESP_ERR_NO_MEM, TAG, "allocate HUD failed");
|
||||
@@ -459,9 +546,9 @@ static esp_err_t lcd_direct_emote_init(void)
|
||||
ESP_RETURN_ON_ERROR(gfx_img_set_src(s_emote_hud, &s_emote_hud_image), TAG, "set HUD failed");
|
||||
s_emote_ready = true;
|
||||
ESP_LOGI(TAG,
|
||||
"esp_emote_gfx idle test ready (%u fps, 2 x %u-row DMA buffers)",
|
||||
"esp_emote_gfx idle test ready (%u fps, PSRAM composed frame, %u-row DMA staging)",
|
||||
(unsigned)LCD_DIRECT_EMOTE_FPS,
|
||||
(unsigned)LCD_DIRECT_EMOTE_BUFFER_ROWS);
|
||||
(unsigned)LCD_DIRECT_EMOTE_DMA_ROWS);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -517,8 +604,8 @@ static esp_err_t lcd_direct_emote_update_hud(bool force, bool *changed)
|
||||
|
||||
static esp_err_t lcd_direct_emote_wait(uint32_t duration_ms)
|
||||
{
|
||||
uint32_t remaining_ms = duration_ms;
|
||||
while (remaining_ms > 0U) {
|
||||
const int64_t deadline_us = esp_timer_get_time() + (int64_t)duration_ms * 1000;
|
||||
while (esp_timer_get_time() < deadline_us) {
|
||||
if (lcd_direct_should_stop_idle()) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
@@ -526,17 +613,23 @@ static esp_err_t lcd_direct_emote_wait(uint32_t duration_ms)
|
||||
bool changed = false;
|
||||
esp_err_t hud_err = lcd_direct_emote_update_hud(false, &changed);
|
||||
if (hud_err == ESP_OK && changed) {
|
||||
hud_err = gfx_refr_now(s_emote_handle);
|
||||
hud_err = lcd_direct_emote_refresh();
|
||||
}
|
||||
esp_err_t unlock_err = gfx_emote_unlock(s_emote_handle);
|
||||
if (hud_err != ESP_OK || unlock_err != ESP_OK) {
|
||||
return hud_err != ESP_OK ? hud_err : unlock_err;
|
||||
}
|
||||
const int64_t remaining_us = deadline_us - esp_timer_get_time();
|
||||
if (remaining_us <= 0) {
|
||||
break;
|
||||
}
|
||||
const uint32_t remaining_ms = (remaining_us + 999) / 1000;
|
||||
uint32_t slice_ms = remaining_ms > LCD_DIRECT_PRESENTATION_WAIT_POLL_MS
|
||||
? LCD_DIRECT_PRESENTATION_WAIT_POLL_MS
|
||||
: remaining_ms;
|
||||
vTaskDelay(pdMS_TO_TICKS(slice_ms));
|
||||
remaining_ms -= slice_ms;
|
||||
TickType_t ticks = pdMS_TO_TICKS(slice_ms);
|
||||
/* 当前 tick 为 10ms;短余量也至少让出一个 tick,避免忙等。 */
|
||||
vTaskDelay(ticks > 0 ? ticks : 1);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -545,6 +638,15 @@ static esp_err_t lcd_direct_emote_wait(uint32_t duration_ms)
|
||||
* closure: 0=睁眼,255=闭眼。Q8 网格保留亚像素精度。 */
|
||||
static esp_err_t lcd_direct_emote_set_eye_closure(uint8_t closure)
|
||||
{
|
||||
bool closed = closure >= LCD_DIRECT_EMOTE_CLOSED_THRESHOLD;
|
||||
if (closed != s_emote_show_closed) {
|
||||
ESP_RETURN_ON_ERROR(gfx_obj_set_visible(s_emote_eyes_open, !closed), TAG, "toggle mesh eyes failed");
|
||||
ESP_RETURN_ON_ERROR(gfx_obj_set_visible(s_emote_closed_eyes, closed), TAG, "toggle closed eyes failed");
|
||||
s_emote_show_closed = closed;
|
||||
}
|
||||
if (closed) {
|
||||
return ESP_OK;
|
||||
}
|
||||
gfx_mesh_img_point_q8_t points[LCD_DIRECT_EMOTE_MESH_POINTS];
|
||||
/* 上下眼睑对称收向原眼睛中线,包围盒位置补偿保持脸部锚点。 */
|
||||
const int32_t center_y = 46 * 256;
|
||||
@@ -594,13 +696,13 @@ static esp_err_t lcd_direct_play_emote_face_until_pending(const lcd_direct_reque
|
||||
}
|
||||
if (result == ESP_OK) {
|
||||
gfx_disp_refresh_all(s_emote_disp);
|
||||
result = gfx_refr_now(s_emote_handle);
|
||||
result = lcd_direct_emote_refresh();
|
||||
}
|
||||
esp_err_t unlock_err = gfx_emote_unlock(s_emote_handle);
|
||||
if (result == ESP_OK) {
|
||||
result = unlock_err;
|
||||
}
|
||||
ESP_LOGI(TAG, "esp_emote_gfx idle playback started (single-mesh blink)");
|
||||
ESP_LOGI(TAG, "esp_emote_gfx idle playback started (composed mesh/arc blink)");
|
||||
|
||||
/* 约 170ms 闭合、34ms 停顿、204ms 睁开,保持单一轮廓。 */
|
||||
static const uint8_t blink_closure[] = {
|
||||
@@ -616,23 +718,40 @@ static esp_err_t lcd_direct_play_emote_face_until_pending(const lcd_direct_reque
|
||||
if (result != ESP_OK) {
|
||||
break;
|
||||
}
|
||||
uint64_t draw_us_total = 0;
|
||||
uint32_t late_frames = 0;
|
||||
for (size_t i = 0; i < sizeof(blink_closure) / sizeof(blink_closure[0]) && result == ESP_OK; ++i) {
|
||||
const int64_t frame_start_us = esp_timer_get_time();
|
||||
result = gfx_emote_lock(s_emote_handle);
|
||||
if (result != ESP_OK) {
|
||||
break;
|
||||
}
|
||||
result = lcd_direct_emote_set_eye_closure(blink_closure[i]);
|
||||
if (result == ESP_OK) {
|
||||
result = gfx_refr_now(s_emote_handle);
|
||||
result = lcd_direct_emote_refresh();
|
||||
}
|
||||
unlock_err = gfx_emote_unlock(s_emote_handle);
|
||||
if (result == ESP_OK) {
|
||||
result = unlock_err;
|
||||
}
|
||||
if (result == ESP_OK) {
|
||||
result = lcd_direct_emote_wait(LCD_DIRECT_EMOTE_STEP_MS);
|
||||
const uint32_t elapsed_us = (uint32_t)(esp_timer_get_time() - frame_start_us);
|
||||
draw_us_total += elapsed_us;
|
||||
const uint32_t period_us = LCD_DIRECT_EMOTE_STEP_MS * 1000U;
|
||||
if (elapsed_us < period_us) {
|
||||
/* 绘制时间属于本帧预算,不再额外固定睡 34ms。 */
|
||||
result = lcd_direct_emote_wait((period_us - elapsed_us + 999U) / 1000U);
|
||||
} else {
|
||||
++late_frames;
|
||||
/* 过载仍给其他任务调度机会,并在下一步检查 pending。 */
|
||||
result = lcd_direct_emote_wait(1U);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == ESP_OK) {
|
||||
ESP_LOGI(TAG, "emote blink composed draw_us_avg=%llu late_frames=%u/12",
|
||||
(unsigned long long)(draw_us_total / 12U), (unsigned)late_frames);
|
||||
}
|
||||
++cycles;
|
||||
}
|
||||
|
||||
@@ -969,13 +1088,17 @@ static esp_err_t lcd_direct_gpio_init(void)
|
||||
static esp_err_t lcd_direct_panel_init(void)
|
||||
{
|
||||
/* 面板命令、方向和颜色格式在显示开启前一次性配置完成。 */
|
||||
size_t max_transfer_pixels = LCD_DIRECT_PIXELS_PER_TRANSFER;
|
||||
#if CONFIG_TQ_LCD_EMOTE_GFX_TEST
|
||||
max_transfer_pixels = LCD_DIRECT_WIDTH * LCD_DIRECT_EMOTE_DMA_ROWS;
|
||||
#endif
|
||||
spi_bus_config_t bus_config = {
|
||||
.mosi_io_num = CONFIG_TQ_SCREEN_SPI_MOSI_GPIO,
|
||||
.miso_io_num = CONFIG_TQ_SCREEN_SPI_MISO_GPIO,
|
||||
.sclk_io_num = CONFIG_TQ_SCREEN_SPI_CLK_GPIO,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = LCD_DIRECT_PIXELS_PER_TRANSFER * sizeof(uint16_t),
|
||||
.max_transfer_sz = max_transfer_pixels * sizeof(uint16_t),
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(spi_bus_initialize(LCD_DIRECT_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO),
|
||||
TAG,
|
||||
|
||||
Reference in New Issue
Block a user