Skip to content

Commit

Permalink
Implement area clearing on the ESP32-S3
Browse files Browse the repository at this point in the history
Makes `epd_push_pixels` respect the given area on ESP32-S3 chips. This should fix #320 .
  • Loading branch information
vroland authored Nov 3, 2024
1 parent 2591847 commit d12021e
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 51 deletions.
22 changes: 21 additions & 1 deletion examples/fb_mode_test/main/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include <epdiy.h>

#include "epd_display.h"
#include "sdkconfig.h"

#define WAVEFORM EPD_BUILTIN_WAVEFORM
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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();
}
}
17 changes: 17 additions & 0 deletions src/output_common/render_context.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "render_context.h"

#include <string.h>
#include "esp_log.h"

#include "../epdiy.h"
Expand Down Expand Up @@ -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;
}
}
}
12 changes: 12 additions & 0 deletions src/output_common/render_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -95,3 +98,12 @@ 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);
88 changes: 65 additions & 23 deletions src/output_lcd/render_lcd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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);
Expand Down
26 changes: 2 additions & 24 deletions src/render.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
);

Expand Down Expand Up @@ -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();

Expand Down
6 changes: 3 additions & 3 deletions test/test_line_mask.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
#include <sys/types.h>
#include <unity.h>

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);
}
Expand All @@ -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);
}

0 comments on commit d12021e

Please sign in to comment.