display: make SAFE double-buffer non-blocking with pending submit

This commit is contained in:
Stanislav N Mikhailov
2026-04-04 20:38:35 +03:00
parent 529daa68c8
commit a2e378d911
3 changed files with 86 additions and 23 deletions
+2 -1
View File
@@ -2,7 +2,7 @@
В движке используется явный контракт рисования:
- `display_begin_paint_try()` или `display_begin_paint_blocking()` открывает кадр и даёт буфер.
- `display_end_paint()` закрывает кадр и сразу отправляет его на экран.
- `display_end_paint()` закрывает кадр и передаёт его в вывод (в SAFE+2 может отложить на один кадр, если DMA занят).
Главное правило: каждый успешный `begin` должен завершаться `display_end_paint()`.
@@ -48,6 +48,7 @@
Что получить:
- Главный цикл всегда остаётся отзывчивым.
- Пока DMA выводит буфер A, можно рисовать буфер B без блокировки.
Конфигурация:
- `mode = DISPLAY_MODE_SAFE`
+4 -3
View File
@@ -78,7 +78,8 @@ uint16_t* display_begin_paint_try(void);
/* Блокирующе начинает рисование кадра и возвращает валидный буфер. */
uint16_t* display_begin_paint_blocking(void);
/* Завершает рисование кадра и запускает его отправку; false при ошибке контракта. */
/* Завершает рисование кадра и передаёт его в вывод (в SAFE+2 возможна отложенная отправка). */
/* false при ошибке контракта или переполнении очереди из одного pending-кадра. */
bool display_end_paint(void);
@@ -99,11 +100,11 @@ bool display_swap_buffers(void);
============================================================ */
/* Запускает DMA-передачу текущего scanout-буфера. */
/* Возвращает false, если DMA занят или сейчас открыта paint-секция. */
/* В SAFE+2 при занятом DMA ставит кадр в очередь из одного pending-кадра. */
bool display_submit(void);
/* Возвращает true, когда DMA неактивен и можно отправлять следующий кадр. */
/* Возвращает true, когда DMA неактивен и нет отложенного pending-кадра. */
bool display_ready(void);
+80 -19
View File
@@ -27,6 +27,8 @@ typedef struct
uint8_t draw_index;
uint8_t scanout_index;
uint8_t pending_scanout_index;
bool pending_submit;
bool paint_in_progress;
} display_context_t;
@@ -269,6 +271,30 @@ static void hw_raise_cs(void)
gpio_put(PIN_CS, 1);
}
/*
* Назначение:
* Пытается запустить отложенный submit, если DMA уже освободился.
*/
static void display_kick_pending_if_possible(void)
{
if (!ctx.pending_submit || ctx.dma_busy)
{
return;
}
/* Продвигаем отложенный кадр в scanout и возвращаем draw на второй буфер. */
ctx.scanout_index = ctx.pending_scanout_index;
if (ctx.buffer_count == 2)
{
ctx.draw_index = (uint8_t)(ctx.scanout_index ^ 1u);
}
ctx.pending_submit = false;
size_t pixels = (size_t)ctx.width * ctx.height;
ctx.dma_busy = true;
hw_start_dma(ctx.buffers[ctx.scanout_index], pixels);
}
/* ============================================================
=== Обработчик DMA IRQ (позже подключить к реальному IRQ)
@@ -346,9 +372,11 @@ void display_init(const display_config_t* cfg)
memset(ctx.buffers[i], 0, bytes);
}
/* На старте draw и scanout указывают на первый буфер. */
ctx.draw_index = 0;
/* SAFE+double-buffer: draw и scanout должны смотреть на разные буферы. */
ctx.scanout_index = 0;
ctx.draw_index = (ctx.mode == DISPLAY_MODE_SAFE && ctx.buffer_count == 2) ? 1u : 0u;
ctx.pending_scanout_index = 0;
ctx.pending_submit = false;
/* DMA в начале свободен. */
ctx.dma_busy = false;
@@ -371,6 +399,8 @@ void display_init(const display_config_t* cfg)
*/
uint16_t* display_begin_paint_try(void)
{
display_kick_pending_if_possible();
if (ctx.paint_in_progress)
{
return NULL;
@@ -384,6 +414,14 @@ uint16_t* display_begin_paint_try(void)
return NULL;
}
/* SAFE+double-buffer: при отложенном кадре свободного draw-буфера нет. */
if (ctx.mode == DISPLAY_MODE_SAFE &&
ctx.buffer_count == 2 &&
ctx.pending_submit)
{
return NULL;
}
ctx.paint_in_progress = true;
return ctx.buffers[ctx.draw_index];
}
@@ -425,8 +463,8 @@ uint16_t* display_begin_paint_blocking(void)
* Завершает фазу рисования кадра и отправляет кадр на экран.
*
* Возвращаемое значение:
* bool - true, если кадр успешно передан в вывод.
* bool - false, если нарушен порядок begin/end или DMA занят.
* bool - true, если кадр принят к выводу (запущен сразу или поставлен в очередь).
* bool - false, если нарушен порядок begin/end или очередь уже заполнена.
*/
bool display_end_paint(void)
{
@@ -495,26 +533,41 @@ bool display_swap_buffers(void)
* Запускает отправку текущего готового кадра на экран.
*
* Возвращаемое значение:
* bool - true, если передача запущена.
* bool - false, если DMA уже активен или сейчас открыта paint-секция.
* bool - true, если кадр принят (старт DMA или постановка в очередь SAFE+double-buffer).
* bool - false, если сейчас открыта paint-секция, DMA занят в других режимах
* или очередь SAFE+double-buffer уже заполнена.
*/
bool display_submit(void)
{
display_kick_pending_if_possible();
/* Пока идёт paint-секция, submit допускается только через end_paint. */
if (ctx.paint_in_progress)
return false;
/* Нельзя стартовать новый кадр, пока текущий DMA ещё не завершён. */
if (ctx.dma_busy)
return false;
/* В SAFE+double-buffer автоматически делаем flip перед отправкой кадра. */
/* SAFE+double-buffer: при занятом DMA кадр ставится в очередь. */
if (ctx.mode == DISPLAY_MODE_SAFE &&
ctx.buffer_count == 2)
{
uint8_t tmp = ctx.draw_index;
ctx.draw_index = ctx.scanout_index;
ctx.scanout_index = tmp;
if (ctx.dma_busy)
{
if (ctx.pending_submit)
{
return false;
}
ctx.pending_scanout_index = ctx.draw_index;
ctx.pending_submit = true;
return true;
}
ctx.scanout_index = ctx.draw_index;
ctx.draw_index = (uint8_t)(ctx.scanout_index ^ 1u);
}
else if (ctx.dma_busy)
{
/* Для остальных режимов submit во время DMA запрещён. */
return false;
}
/* Считаем количество пикселей для DMA-передачи. */
@@ -546,8 +599,10 @@ bool display_submit(void)
*/
bool display_ready(void)
{
/* Дисплей готов к новому submit, когда DMA не занят. */
return !ctx.dma_busy;
display_kick_pending_if_possible();
/* Готовность есть, когда нет активной DMA и нет отложенного кадра. */
return !ctx.dma_busy && !ctx.pending_submit;
}
@@ -559,9 +614,15 @@ bool display_ready(void)
*/
void display_wait(void)
{
/* Блокирующее активное ожидание завершения DMA-передачи. */
while (ctx.dma_busy)
/* Ждём полного опустошения очереди кадров и завершения DMA. */
while (true)
{
/* активное ожидание */
display_kick_pending_if_possible();
if (!ctx.dma_busy && !ctx.pending_submit)
{
break;
}
tight_loop_contents();
}
}