Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement area clearing on the ESP32-S3 #364

Merged
merged 2 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}