From fc1452573c3a8b7c11eaa8b5a428e615d901d0f9 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:26:15 +1000 Subject: [PATCH] fix(draw_buf): align allocated buffers to LV_DRAW_BUF_ALIGN (#9869) --- src/draw/lv_draw_buf.c | 5 +- tests/src/test_cases/test_draw_buf.c | 72 +++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/draw/lv_draw_buf.c b/src/draw/lv_draw_buf.c index 293f23d5eb..bedebf3edf 100644 --- a/src/draw/lv_draw_buf.c +++ b/src/draw/lv_draw_buf.c @@ -657,6 +657,9 @@ static void draw_buf_free(const lv_draw_buf_handlers_t * handlers, void * buf) /** * For given width, height, color format, and stride, calculate the size needed for a new draw buffer. + * The result is rounded up to LV_DRAW_BUF_ALIGN so the allocated buffer length is a multiple of the + * alignment boundary (required, for example, by the ESP32 PPA whose config structure wants the full + * allocation size rather than the bytes actually in use). */ static uint32_t _calculate_draw_buf_size(uint32_t w, uint32_t h, lv_color_format_t cf, uint32_t stride) { @@ -673,7 +676,7 @@ static uint32_t _calculate_draw_buf_size(uint32_t w, uint32_t h, lv_color_format size += LV_COLOR_INDEXED_PALETTE_SIZE(cf) * 4; } - return size; + return LV_ROUND_UP(size, LV_DRAW_BUF_ALIGN); } static void draw_buf_get_full_area(const lv_draw_buf_t * draw_buf, lv_area_t * full_area) diff --git a/tests/src/test_cases/test_draw_buf.c b/tests/src/test_cases/test_draw_buf.c index 09cc38fec6..68b85bc11c 100644 --- a/tests/src/test_cases/test_draw_buf.c +++ b/tests/src/test_cases/test_draw_buf.c @@ -79,8 +79,12 @@ void test_draw_buf_stride_adjust(void) res = lv_draw_buf_adjust_stride(decoded, min_stride - 1); TEST_ASSERT_EQUAL(LV_RESULT_INVALID, res); - /*Expand the stride should fail if stride is too large that buffer size overflow*/ - res = lv_draw_buf_adjust_stride(decoded, image_stride + 1); + /* Expand the stride should fail if the new stride is so large that the buffer + * would overflow. Use a stride that's guaranteed to push the required size past + * the aligned allocation (lv_draw_buf_create rounds the allocation up to + * LV_DRAW_BUF_ALIGN for PPA/cache-line reasons, so a +1 bump can fit in the + * padding for small images). Doubling the stride always overflows. */ + res = lv_draw_buf_adjust_stride(decoded, image_stride * 2); TEST_ASSERT_EQUAL(LV_RESULT_INVALID, res); /*Create a larger draw buffer*/ @@ -114,6 +118,70 @@ void test_draw_buf_stride_adjust(void) #endif } +/* Verify that the draw buffer allocation size reported in `data_size` + * is rounded up to a multiple of LV_DRAW_BUF_ALIGN, as required by back-ends + * (e.g. the ESP32 PPA) that need the allocation length to match the cache-line + * boundary rather than the raw image content size. */ +void test_draw_buf_size_alignment(void) +{ + /* A set of (w, h, cf, stride) cases chosen so that the raw image size + * (stride*h [+ alpha/palette]) is deliberately not a multiple of + * LV_DRAW_BUF_ALIGN, so the round-up behavior is observable. */ + struct { + uint32_t w; + uint32_t h; + lv_color_format_t cf; + uint32_t stride; /* 0 => let LVGL choose via lv_draw_buf_width_to_stride */ + } cases[] = { + /* Small indexed: stride=1, h=1, palette=8 => raw=9 (not a multiple of ALIGN) */ + { 5, 1, LV_COLOR_FORMAT_I1, 1 }, + /* A8 with odd raw size: 1*3 = 3 */ + { 1, 3, LV_COLOR_FORMAT_A8, 1 }, + /* RGB565 with an intentionally non-aligned raw size: 6*1 = 6 */ + { 3, 1, LV_COLOR_FORMAT_RGB565, 6 }, + /* RGB888 with raw = 9*1 = 9 */ + { 3, 1, LV_COLOR_FORMAT_RGB888, 9 }, + /* RGB565A8: rgb=4*1=4, a8=(4/2)*1=2 => raw=6 */ + { 2, 1, LV_COLOR_FORMAT_RGB565A8, 4 }, + /* Larger ARGB8888 with raw already aligned: 10*10*4 = 400 (rounding should be a no-op) */ + { 10, 10, LV_COLOR_FORMAT_ARGB8888, 40 }, + /* Stride auto-computed (0) path */ + { 7, 5, LV_COLOR_FORMAT_RGB565, 0 }, + }; + + for(unsigned i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + uint32_t w = cases[i].w; + uint32_t h = cases[i].h; + lv_color_format_t cf = cases[i].cf; + uint32_t stride = cases[i].stride; + + lv_draw_buf_t * buf = lv_draw_buf_create(w, h, cf, stride); + TEST_ASSERT_NOT_NULL(buf); + + /* Recompute the stride the same way `_calculate_draw_buf_size` would + * if the caller passed 0, so we can derive the expected raw size. */ + uint32_t effective_stride = stride ? stride : lv_draw_buf_width_to_stride(w, cf); + uint32_t raw_size = effective_stride * h; + if(cf == LV_COLOR_FORMAT_RGB565A8) { + raw_size += (effective_stride / 2) * h; + } + else if(LV_COLOR_FORMAT_IS_INDEXED(cf)) { + raw_size += LV_COLOR_INDEXED_PALETTE_SIZE(cf) * 4; + } + + uint32_t expected = LV_ROUND_UP(raw_size, LV_DRAW_BUF_ALIGN); + + /* data_size must be rounded up to LV_DRAW_BUF_ALIGN ... */ + TEST_ASSERT_EQUAL_UINT32(0, buf->data_size % LV_DRAW_BUF_ALIGN); + /* ... must cover at least the raw image content size ... */ + TEST_ASSERT_TRUE(buf->data_size >= raw_size); + /* ... and must match the explicit LV_ROUND_UP value. */ + TEST_ASSERT_EQUAL_UINT32(expected, buf->data_size); + + lv_draw_buf_destroy(buf); + } +} + void test_draw_buf_xy_access(void) { LV_DRAW_BUF_DEFINE_STATIC(draw_buf, 100, 100, LV_COLOR_FORMAT_RGB565);