From 648a911d2a352d780f66f86b954bb086f777301d Mon Sep 17 00:00:00 2001 From: Valentin Roland Date: Tue, 15 Oct 2024 22:51:45 +0200 Subject: [PATCH] implement area clearing on the ESP32-S3 --- examples/fb_mode_test/main/main.c | 22 +++++++- src/output_common/render_context.c | 17 ++++++ src/output_common/render_context.h | 13 +++++ src/output_lcd/render_lcd.c | 88 ++++++++++++++++++++++-------- src/render.c | 26 +-------- test/test_line_mask.c | 6 +- 6 files changed, 121 insertions(+), 51 deletions(-) diff --git a/examples/fb_mode_test/main/main.c b/examples/fb_mode_test/main/main.c index 567b3c66..b7e0e84d 100644 --- a/examples/fb_mode_test/main/main.c +++ b/examples/fb_mode_test/main/main.c @@ -23,6 +23,7 @@ #include +#include "epd_display.h" #include "sdkconfig.h" #define WAVEFORM EPD_BUILTIN_WAVEFORM @@ -55,6 +56,16 @@ void clear() { memset(framebuffer, 0xFF, fb_size); } +/** + * Clear an example area on the screen to white, does not reset the framebuffer. + */ +void clear_area(EpdRect clear_area) { + epd_poweron(); + epd_clear_area(clear_area); + epd_poweroff(); + memset(framebuffer, 0xFF, fb_size); +} + /** * Draw triangles at varying alignments into the framebuffer in 8ppB mode. * start_line, start_column specify the start position. @@ -197,7 +208,16 @@ void app_main() { test_2ppB(); + // Clear at different positions + EpdRect area = { .x = 100, .y = epd_height() - 200, .width = 80, .height = 100 }; + clear_area(area); + area.width += 1; + area.x += 100; + clear_area(area); + area.x += 101; + clear_area(area); + printf("going to sleep...\n"); epd_deinit(); esp_deep_sleep_start(); -} \ No newline at end of file +} diff --git a/src/output_common/render_context.c b/src/output_common/render_context.c index e5abc584..bb1890ea 100644 --- a/src/output_common/render_context.c +++ b/src/output_common/render_context.c @@ -1,5 +1,6 @@ #include "render_context.h" +#include #include "esp_log.h" #include "../epdiy.h" @@ -87,3 +88,19 @@ void IRAM_ATTR prepare_context_for_next_frame(RenderContext_t* ctx) { ctx->lines_prepared = 0; ctx->lines_consumed = 0; } + +void epd_populate_line_mask(uint8_t* line_mask, const uint8_t* dirty_columns, int mask_len) { + if (dirty_columns == NULL) { + memset(line_mask, 0xFF, mask_len); + } else { + int pixels = mask_len * 4; + for (int c = 0; c < pixels / 2; c += 2) { + uint8_t mask = 0; + mask |= (dirty_columns[c + 1] & 0xF0) != 0 ? 0xC0 : 0x00; + mask |= (dirty_columns[c + 1] & 0x0F) != 0 ? 0x30 : 0x00; + mask |= (dirty_columns[c] & 0xF0) != 0 ? 0x0C : 0x00; + mask |= (dirty_columns[c] & 0x0F) != 0 ? 0x03 : 0x00; + line_mask[c / 2] = mask; + } + } +} \ No newline at end of file diff --git a/src/output_common/render_context.h b/src/output_common/render_context.h index 232dbe27..47f8ddbc 100644 --- a/src/output_common/render_context.h +++ b/src/output_common/render_context.h @@ -74,6 +74,9 @@ typedef struct { /// track line skipping when working in old i2s mode int skipping; + + /// line buffer when using epd_push_pixels + uint8_t* static_line_buffer; } RenderContext_t; /** @@ -95,3 +98,13 @@ void get_buffer_params( * (Reset counters, etc) */ void prepare_context_for_next_frame(RenderContext_t* ctx); + +/** + * Populate an output line mask from line dirtyness with two bits per pixel. + * If the dirtyness data is NULL, set the mask to neutral. + * + * don't inline for to ensure availability in tests. + */ +void __attribute__((noinline)) epd_populate_line_mask( + uint8_t* line_mask, const uint8_t* dirty_columns, int mask_len +); \ No newline at end of file diff --git a/src/output_lcd/render_lcd.c b/src/output_lcd/render_lcd.c index 71aadabb..c5229d3d 100644 --- a/src/output_lcd/render_lcd.c +++ b/src/output_lcd/render_lcd.c @@ -17,20 +17,8 @@ #include "lcd_driver.h" #include "render_lcd.h" -static bool IRAM_ATTR fill_line_noop(RenderContext_t* ctx, uint8_t* line) { - memset(line, 0x00, ctx->display_width / 4); - return false; -} - -static bool IRAM_ATTR fill_line_white(RenderContext_t* ctx, uint8_t* line) { - memset(line, CLEAR_BYTE, ctx->display_width / 4); - return false; -} - -static bool IRAM_ATTR fill_line_black(RenderContext_t* ctx, uint8_t* line) { - memset(line, DARK_BYTE, ctx->display_width / 4); - return false; -} +// declare vector optimized line mask application. +void epd_apply_line_mask_VE(uint8_t* line, const uint8_t* mask, int mask_len); __attribute__((optimize("O3"))) static bool IRAM_ATTR retrieve_line_isr(RenderContext_t* ctx, uint8_t* buf) { @@ -97,20 +85,75 @@ void lcd_do_update(RenderContext_t* ctx) { epd_set_mode(0); } +__attribute__((optimize("O3"))) static bool IRAM_ATTR +push_pixels_isr(RenderContext_t* ctx, uint8_t* buf) { + // Output no-op outside of drawn area + if (ctx->lines_consumed < ctx->area.y) { + memset(buf, 0, ctx->display_width / 4); + } else if (ctx->lines_consumed >= ctx->area.y + ctx->area.height) { + memset(buf, 0, ctx->display_width / 4); + } else { + memcpy(buf, ctx->static_line_buffer, ctx->display_width / 4); + } + ctx->lines_consumed += 1; + return pdFALSE; +} + +/** + * Populate the line mask for use in epd_push_pixels. + */ +static void push_pixels_populate_line(RenderContext_t* ctx, int color) { + // Select fill pattern by draw color + int fill_byte = 0; + switch (color) { + case 0: + fill_byte = DARK_BYTE; + break; + case 1: + fill_byte = CLEAR_BYTE; + break; + default: + fill_byte = 0x00; + } + + // Compute a line mask based on the drawn area + uint8_t* dirtyness = malloc(ctx->display_width / 2); + assert(dirtyness != NULL); + + memset(dirtyness, 0, ctx->display_width / 2); + + for (int i = 0; i < ctx->display_width; i++) { + if ((i >= ctx->area.x) && (i < ctx->area.x + ctx->area.width)) { + dirtyness[i / 2] |= i % 2 ? 0xF0 : 0x0F; + } + } + epd_populate_line_mask(ctx->line_mask, dirtyness, ctx->display_width / 4); + + // mask the line pattern with the populated mask + memset(ctx->static_line_buffer, fill_byte, ctx->display_width / 4); + epd_apply_line_mask(ctx->static_line_buffer, ctx->line_mask, ctx->display_width / 4); + + free(dirtyness); +} + void epd_push_pixels_lcd(RenderContext_t* ctx, short time, int color) { - epd_set_mode(1); ctx->current_frame = 0; + ctx->lines_total = ctx->display_height; + ctx->lines_consumed = 0; + ctx->static_line_buffer = malloc(ctx->display_width / 4); + assert(ctx->static_line_buffer != NULL); + + push_pixels_populate_line(ctx, color); epd_lcd_frame_done_cb((frame_done_func_t)handle_lcd_frame_done, ctx); - if (color == 0) { - epd_lcd_line_source_cb((line_cb_func_t)&fill_line_black, ctx); - } else if (color == 1) { - epd_lcd_line_source_cb((line_cb_func_t)&fill_line_white, ctx); - } else { - epd_lcd_line_source_cb((line_cb_func_t)&fill_line_noop, ctx); - } + epd_lcd_line_source_cb((line_cb_func_t)&push_pixels_isr, ctx); + + epd_set_mode(1); epd_lcd_start_frame(); xSemaphoreTake(ctx->frame_done, portMAX_DELAY); epd_set_mode(0); + + free(ctx->static_line_buffer); + ctx->static_line_buffer = NULL; } #define int_min(a, b) (((a) < (b)) ? (a) : (b)) @@ -194,7 +237,6 @@ lcd_calculate_frame(RenderContext_t* ctx, int thread_id) { ctx->lut_lookup_func(lp, buf, ctx->conversion_lut, ctx->display_width); // apply the line mask - void epd_apply_line_mask_VE(uint8_t * line, const uint8_t* mask, int mask_len); epd_apply_line_mask_VE(buf, ctx->line_mask, ctx->display_width / 4); lq_commit(lq); diff --git a/src/render.c b/src/render.c index 713d660b..eaba217d 100644 --- a/src/render.c +++ b/src/render.c @@ -88,29 +88,6 @@ static inline int rounded_display_height() { return (((epd_height() + 7) / 8) * 8); } -/** - * Populate an output line mask from line dirtyness with one nibble per pixel. - * If the dirtyness data is NULL, set the mask to neutral. - * - * don't inline for to ensure availability in tests. - */ -void __attribute__((noinline)) -_epd_populate_line_mask(uint8_t* line_mask, const uint8_t* dirty_columns, int mask_len) { - if (dirty_columns == NULL) { - memset(line_mask, 0xFF, mask_len); - } else { - int pixels = mask_len * 4; - for (int c = 0; c < pixels / 2; c += 2) { - uint8_t mask = 0; - mask |= (dirty_columns[c + 1] & 0xF0) != 0 ? 0xC0 : 0x00; - mask |= (dirty_columns[c + 1] & 0x0F) != 0 ? 0x30 : 0x00; - mask |= (dirty_columns[c] & 0xF0) != 0 ? 0x0C : 0x00; - mask |= (dirty_columns[c] & 0x0F) != 0 ? 0x03 : 0x00; - line_mask[c / 2] = mask; - } - } -} - // FIXME: fix misleading naming: // area -> buffer dimensions // crop -> area taken out of buffer @@ -198,7 +175,7 @@ enum EpdDrawError IRAM_ATTR epd_draw_base( render_context.phase_times = waveform_phases->phase_times; } - _epd_populate_line_mask( + epd_populate_line_mask( render_context.line_mask, drawn_columns, render_context.display_width / 4 ); @@ -294,6 +271,7 @@ void epd_renderer_init(enum EpdInitOptions options) { abort(); } render_context.conversion_lut_size = lut_size; + render_context.static_line_buffer = NULL; render_context.frame_done = xSemaphoreCreateBinary(); diff --git a/test/test_line_mask.c b/test/test_line_mask.c index ec80a07d..8b3f8a3a 100644 --- a/test/test_line_mask.c +++ b/test/test_line_mask.c @@ -3,14 +3,14 @@ #include #include -void _epd_populate_line_mask(uint8_t* line_mask, const uint8_t* dirty_columns, int mask_len); +void epd_populate_line_mask(uint8_t* line_mask, const uint8_t* dirty_columns, int mask_len); const uint8_t col_dirtyness_example[8] = { 0x00, 0x0F, 0x00, 0x11, 0xFF, 0xFF, 0x00, 0x80 }; TEST_CASE("mask populated correctly", "[epdiy,unit]") { const uint8_t expected_mask[8] = { 0x30, 0xF0, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00 }; uint8_t mask[8] = { 0 }; - _epd_populate_line_mask(mask, col_dirtyness_example, 4); + epd_populate_line_mask(mask, col_dirtyness_example, 4); TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_mask, mask, 8); } @@ -19,7 +19,7 @@ TEST_CASE("neutral mask with null dirtyness", "[epdiy,unit]") { const uint8_t expected_mask[8] = { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 }; uint8_t mask[8] = { 0 }; - _epd_populate_line_mask(mask, NULL, 4); + epd_populate_line_mask(mask, NULL, 4); TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_mask, mask, 8); }