From 9a225477098eca18b604d5a96b79ac6335ff3dfe Mon Sep 17 00:00:00 2001 From: Tom Hoenderdos Date: Tue, 6 Oct 2026 10:12:38 +0200 Subject: [PATCH] Optimize draw_x with per-row item lists Every draw_x call tested every item's bounding box against the row, once per run of pixels. On the badge a racing game with about 89 items, of which about 10 cover any row, took 21 ms per frame to draw. The drivers now link the items that cover each row through a new next field with display_items_row(), and draw_x walks only that list. The field is rendering scratch and never compared. Nothing is allocated per update. Output is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Tom Hoenderdos --- dcs_lcd_display_driver.c | 3 ++- dcs_lcd_draw.c | 10 +++------- dcs_lcd_draw.h | 2 +- display_items.h | 21 +++++++++++++++++++++ epaper_display_driver.c | 3 ++- epaper_draw.c | 10 +++------- epaper_draw.h | 2 +- memory_display_driver.c | 3 ++- mono_draw.c | 10 +++------- mono_draw.h | 2 +- oled_display_driver.c | 3 ++- sdl_display/display.c | 13 +++++-------- tests/items/test_items.c | 3 ++- 13 files changed, 48 insertions(+), 37 deletions(-) diff --git a/dcs_lcd_display_driver.c b/dcs_lcd_display_driver.c index 14873ad..3ab2db5 100644 --- a/dcs_lcd_display_driver.c +++ b/dcs_lcd_display_driver.c @@ -156,9 +156,10 @@ static void do_update(Context *ctx, term display_list) bool transaction_in_progress = false; for (int ypos = 0; ypos < screen_height; ypos++) { + BaseDisplayItem *row = display_items_row(items, len, ypos); int xpos = 0; while (xpos < screen_width) { - int drawn_pixels = dcs_lcd_draw_x(&driver->screen, xpos, ypos, items, len); + int drawn_pixels = dcs_lcd_draw_x(&driver->screen, xpos, ypos, row); xpos += drawn_pixels; } diff --git a/dcs_lcd_draw.c b/dcs_lcd_draw.c index 49e888f..32d40f1 100644 --- a/dcs_lcd_draw.c +++ b/dcs_lcd_draw.c @@ -209,16 +209,12 @@ int dcs_lcd_draw_scaled_cropped_img_x(const struct DCSLCDScreen *screen, } int dcs_lcd_draw_x(const struct DCSLCDScreen *screen, - int xpos, int ypos, BaseDisplayItem items[], size_t items_len) + int xpos, int ypos, BaseDisplayItem *row) { int line_len = screen->w - xpos; bool below = false; - for (size_t i = 0; i < items_len; i++) { - BaseDisplayItem *item = &items[i]; - if ((ypos < item->y) || (ypos >= item->y + item->height)) { - continue; - } + for (BaseDisplayItem *item = row; item != NULL; item = item->next) { if (xpos < item->x) { int len_to_item = item->x - xpos; if (len_to_item < line_len) { @@ -233,7 +229,7 @@ int dcs_lcd_draw_x(const struct DCSLCDScreen *screen, int max_line_len = below ? 1 : line_len; int drawn_pixels = 0; - switch (items[i].primitive) { + switch (item->primitive) { case PrimitiveImage: drawn_pixels = dcs_lcd_draw_image_x(screen, xpos, ypos, max_line_len, item); break; diff --git a/dcs_lcd_draw.h b/dcs_lcd_draw.h index 6a79e1d..73c0394 100644 --- a/dcs_lcd_draw.h +++ b/dcs_lcd_draw.h @@ -37,6 +37,6 @@ int dcs_lcd_draw_scaled_cropped_img_x(const struct DCSLCDScreen *screen, int xpos, int ypos, int max_line_len, BaseDisplayItem *item); int dcs_lcd_draw_x(const struct DCSLCDScreen *screen, - int xpos, int ypos, BaseDisplayItem items[], size_t items_len); + int xpos, int ypos, BaseDisplayItem *row); #endif diff --git a/display_items.h b/display_items.h index 50b3168..6fa2260 100644 --- a/display_items.h +++ b/display_items.h @@ -84,6 +84,10 @@ struct BaseDisplayItem int y_scale; bool owns_data; + + // Rendering scratch, never compared: the next item that covers the row being drawn, see + // display_items_row() + struct BaseDisplayItem *next; }; typedef struct BaseDisplayItem BaseDisplayItem; @@ -95,6 +99,23 @@ typedef enum DisplayItemsOutOfMemory } display_items_result_t; +// Links the items that cover row ypos through their next field, in display list order, and +// returns the first of them, or NULL if none does. +static inline BaseDisplayItem *display_items_row(BaseDisplayItem items[], size_t items_len, int ypos) +{ + BaseDisplayItem *head = NULL; + BaseDisplayItem **link = &head; + for (size_t i = 0; i < items_len; i++) { + BaseDisplayItem *item = &items[i]; + if (ypos >= item->y && ypos < item->y + item->height) { + *link = item; + link = &item->next; + } + } + *link = NULL; + return head; +} + void display_items_init_item(BaseDisplayItem *item, term req, Context *ctx); display_items_result_t display_items_new_list(term display_list, BaseDisplayItem **items, size_t *items_len, Context *ctx); diff --git a/epaper_display_driver.c b/epaper_display_driver.c index d719d73..12f9152 100644 --- a/epaper_display_driver.c +++ b/epaper_display_driver.c @@ -215,9 +215,10 @@ static void do_update(Context *ctx, term display_list) spi_device_get_trans_result(driver->bus.spi_disp.handle, &trans, portMAX_DELAY); } + BaseDisplayItem *row = display_items_row(items, len, ypos); int xpos = 0; while (xpos < screen_width) { - int drawn_pixels = epaper_draw_x(&driver->screen, buf, xpos, ypos, items, len); + int drawn_pixels = epaper_draw_x(&driver->screen, buf, xpos, ypos, row); xpos += drawn_pixels; } diff --git a/epaper_draw.c b/epaper_draw.c index c90d2ed..d68013c 100644 --- a/epaper_draw.c +++ b/epaper_draw.c @@ -274,16 +274,12 @@ int epaper_draw_scaled_cropped_img_x(const struct EpaperScreen *screen, int epaper_draw_x(const struct EpaperScreen *screen, uint8_t *line_buf, int xpos, int ypos, - BaseDisplayItem items[], size_t items_len) + BaseDisplayItem *row) { int line_len = screen->w - xpos; bool below = false; - for (size_t i = 0; i < items_len; i++) { - BaseDisplayItem *item = &items[i]; - if ((ypos < item->y) || (ypos >= item->y + item->height)) { - continue; - } + for (BaseDisplayItem *item = row; item != NULL; item = item->next) { if (xpos < item->x) { int len_to_item = item->x - xpos; if (len_to_item < line_len) { @@ -298,7 +294,7 @@ int epaper_draw_x(const struct EpaperScreen *screen, int max_line_len = below ? 1 : line_len; int drawn_pixels = 0; - switch (items[i].primitive) { + switch (item->primitive) { case PrimitiveImage: drawn_pixels = epaper_draw_image_x(screen, line_buf, xpos, ypos, max_line_len, item); break; diff --git a/epaper_draw.h b/epaper_draw.h index 3fb5a28..3f0fe07 100644 --- a/epaper_draw.h +++ b/epaper_draw.h @@ -45,6 +45,6 @@ int epaper_draw_scaled_cropped_img_x(const struct EpaperScreen *screen, int epaper_draw_x(const struct EpaperScreen *screen, uint8_t *line_buf, int xpos, int ypos, - BaseDisplayItem items[], size_t items_len); + BaseDisplayItem *row); #endif diff --git a/memory_display_driver.c b/memory_display_driver.c index 5233fc9..d0b07bd 100644 --- a/memory_display_driver.c +++ b/memory_display_driver.c @@ -118,9 +118,10 @@ static void do_update(Context *ctx, term display_list) memset(buf + 2, 0xFF, DISPLAY_WIDTH / 8); + BaseDisplayItem *row = display_items_row(items, len, ypos); int xpos = 0; while (xpos < screen_width) { - int drawn_pixels = mono_draw_x(&driver->screen, buf + 2, xpos, ypos, items, len); + int drawn_pixels = mono_draw_x(&driver->screen, buf + 2, xpos, ypos, row); xpos += drawn_pixels; } diff --git a/mono_draw.c b/mono_draw.c index 5fa278f..0eaf41d 100644 --- a/mono_draw.c +++ b/mono_draw.c @@ -299,16 +299,12 @@ int mono_draw_scaled_cropped_img_x(const struct MonoScreen *screen, int mono_draw_x(const struct MonoScreen *screen, uint8_t *line_buf, int xpos, int ypos, - BaseDisplayItem items[], size_t items_len) + BaseDisplayItem *row) { int line_len = screen->w - xpos; bool below = false; - for (size_t i = 0; i < items_len; i++) { - BaseDisplayItem *item = &items[i]; - if ((ypos < item->y) || (ypos >= item->y + item->height)) { - continue; - } + for (BaseDisplayItem *item = row; item != NULL; item = item->next) { if (xpos < item->x) { int len_to_item = item->x - xpos; if (len_to_item < line_len) { @@ -323,7 +319,7 @@ int mono_draw_x(const struct MonoScreen *screen, int max_line_len = below ? 1 : line_len; int drawn_pixels = 0; - switch (items[i].primitive) { + switch (item->primitive) { case PrimitiveImage: //fprintf(stderr, "Image\n"); drawn_pixels = mono_draw_image_x(screen, line_buf, xpos, ypos, max_line_len, item); diff --git a/mono_draw.h b/mono_draw.h index 97e0497..e35cd36 100644 --- a/mono_draw.h +++ b/mono_draw.h @@ -50,6 +50,6 @@ int mono_draw_scaled_cropped_img_x(const struct MonoScreen *screen, int mono_draw_x(const struct MonoScreen *screen, uint8_t *line_buf, int xpos, int ypos, - BaseDisplayItem items[], size_t items_len); + BaseDisplayItem *row); #endif diff --git a/oled_display_driver.c b/oled_display_driver.c index 47da592..cd8a3e3 100644 --- a/oled_display_driver.c +++ b/oled_display_driver.c @@ -124,9 +124,10 @@ static void do_update(Context *ctx, term display_list) } for (int ypos = 0; ypos < screen_height; ypos++) { + BaseDisplayItem *row = display_items_row(items, len, ypos); int xpos = 0; while (xpos < screen_width) { - int drawn_pixels = mono_draw_x(&driver->screen, buf, xpos, ypos, items, len); + int drawn_pixels = mono_draw_x(&driver->screen, buf, xpos, ypos, row); xpos += drawn_pixels; } diff --git a/sdl_display/display.c b/sdl_display/display.c index 1313241..5e905b4 100644 --- a/sdl_display/display.c +++ b/sdl_display/display.c @@ -269,16 +269,12 @@ static int draw_text_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *it return drawn_pixels; } -static int draw_x(int xpos, int ypos, BaseDisplayItem items[], size_t items_len) +static int draw_x(int xpos, int ypos, BaseDisplayItem *row) { int line_len = screen->w - xpos; bool below = false; - for (size_t i = 0; i < items_len; i++) { - BaseDisplayItem *item = &items[i]; - if ((ypos < item->y) || (ypos >= item->y + item->height)) { - continue; - } + for (BaseDisplayItem *item = row; item != NULL; item = item->next) { if (xpos < item->x) { int len_to_item = item->x - xpos; if (len_to_item < line_len) { @@ -293,7 +289,7 @@ static int draw_x(int xpos, int ypos, BaseDisplayItem items[], size_t items_len) int max_line_len = below ? 1 : line_len; int drawn_pixels = 0; - switch (items[i].primitive) { + switch (item->primitive) { case PrimitiveImage: drawn_pixels = draw_image_x(xpos, ypos, max_line_len, item); break; @@ -334,9 +330,10 @@ static void do_update(Context *ctx, term display_list) } for (int ypos = 0; ypos < screen->h; ypos++) { + BaseDisplayItem *row = display_items_row(items, len, ypos); int xpos = 0; while (xpos < screen->w) { - xpos += draw_x(xpos, ypos, items, len); + xpos += draw_x(xpos, ypos, row); } } diff --git a/tests/items/test_items.c b/tests/items/test_items.c index b5eaad8..aa369f3 100644 --- a/tests/items/test_items.c +++ b/tests/items/test_items.c @@ -238,10 +238,11 @@ static void render(term display_list, uint16_t *frame) screen.pixels = line; for (int ypos = 0; ypos < SCREEN_H; ypos++) { + BaseDisplayItem *row = display_items_row(items, len, ypos); memset(line, 0, sizeof(line)); int xpos = 0; while (xpos < SCREEN_W) { - int drawn_pixels = dcs_lcd_draw_x(&screen, xpos, ypos, items, len); + int drawn_pixels = dcs_lcd_draw_x(&screen, xpos, ypos, row); if (drawn_pixels <= 0) { fprintf(stderr, "dcs_lcd_draw_x returned %d at (%d, %d)\n", drawn_pixels, xpos, ypos); abort();