Skip to content

Commit

Permalink
fix unaligned diffing for mod 8 display configurations (todo: LUT func)
Browse files Browse the repository at this point in the history
  • Loading branch information
vroland committed Apr 12, 2024
1 parent fac2a2b commit d468b3c
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 76 deletions.
102 changes: 63 additions & 39 deletions src/render.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,19 +87,19 @@ 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) {
* 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) {
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;
Expand Down Expand Up @@ -367,41 +367,20 @@ uint32_t epd_interlace_4bpp_line_VE(
#endif

/**
* Interlaces the lines at `to`, `from` into `interlaced`.
* returns `1` if there are differences, `0` otherwise.
* Interlaces `len` nibbles from the buffers `to` and `from` into `interlaced`.
* In the process, tracks which nibbles differ in `col_dirtyness`.
* Returns `1` if there are differences, `0` otherwise.
* Does not require special alignment of the buffers beyond 32 bit alignment.
*/
bool _epd_interlace_line(
static inline int _interlace_line_unaligned(
const uint8_t* to,
const uint8_t* from,
uint8_t* interlaced,
uint8_t* col_dirtyness,
int fb_width
int len
) {
uint32_t dirty = 0;
int unaligned_start = 0;
int unaligned_len = fb_width;

// use Vector Extensions with the ESP32-S3
#ifdef RENDER_METHOD_LCD
// since display horizontal resolutions should be divisible by 16,
// only one end should be unaligned by 8 bytes (16 pixels)
uint32_t to_addr = (uint32_t)to;
int alignment_offset = (to_addr % 16);
if (alignment_offset > 0) {
unaligned_start = 0;
unaligned_len = (16 - alignment_offset) * 2;
} else {
unaligned_start = fb_width & (~0x1F);
unaligned_len = fb_width & 0x1F;
}

dirty = epd_interlace_4bpp_line_VE(
to + alignment_offset, from + alignment_offset, interlaced + alignment_offset * 2,
col_dirtyness + alignment_offset, fb_width
);
#endif
assert((unaligned_start + unaligned_len <= fb_width));
for (int x = unaligned_start; x < unaligned_start + unaligned_len; x++) {
int dirty = 0;
for (int x = 0; x < len; x++) {
uint8_t t = *(to + x / 2);
uint8_t f = *(from + x / 2);
t = (x % 2) ? (t >> 4) : (t & 0x0f);
Expand All @@ -410,8 +389,47 @@ bool _epd_interlace_line(
dirty |= (t ^ f);
interlaced[x] = (t << 4) | f;
}
return dirty;
}

/**
* Interlaces the lines at `to`, `from` into `interlaced`.
* returns `1` if there are differences, `0` otherwise.
*/
bool _epd_interlace_line(
const uint8_t* to,
const uint8_t* from,
uint8_t* interlaced,
uint8_t* col_dirtyness,
int fb_width
) {
#ifdef RENDER_METHOD_I2S
return _interlace_line_unaligned(to, from, interlaced, col_dirtyness, fb_width) > 0;
#elif defined(RENDER_METHOD_LCD)
// Use Vector Extensions with the ESP32-S3.
// Both input buffers should have the same alignment w.r.t. 16 bytes,
// as asserted in epd_difference_image_base.
uint32_t dirty = 0;

return dirty > 0;
// alignment boundaries in pixels
int unaligned_len_front_px = ((16 - (uint32_t)to % 16) * 2) % 32;
int unaligned_len_back_px = (((uint32_t)to + fb_width / 2) % 16) * 2;
int unaligned_back_start_px = fb_width - unaligned_len_back_px;
int aligned_len_px = fb_width - unaligned_len_front_px - unaligned_len_back_px;

dirty |= _interlace_line_unaligned(to, from, interlaced, col_dirtyness, unaligned_len_front_px);
dirty |= epd_interlace_4bpp_line_VE(
to + unaligned_len_front_px / 2, from + unaligned_len_front_px / 2,
interlaced + unaligned_len_front_px, col_dirtyness + unaligned_len_front_px / 2,
aligned_len_px
);
dirty |= _interlace_line_unaligned(
to + unaligned_back_start_px / 2, from + unaligned_back_start_px / 2,
interlaced + unaligned_back_start_px, col_dirtyness + unaligned_back_start_px / 2,
unaligned_len_back_px
);
return dirty;
#endif
}

EpdRect epd_difference_image_base(
Expand All @@ -424,9 +442,15 @@ EpdRect epd_difference_image_base(
bool* dirty_lines,
uint8_t* col_dirtyness
) {
assert(fb_width % 16 == 0);
assert(fb_width % 8 == 0);
assert(col_dirtyness != NULL);

// these buffers should be allocated 16 byte aligned
assert((uint32_t)to % 16 == 0);
assert((uint32_t)from % 16 == 0);
assert((uint32_t)col_dirtyness % 16 == 0);
assert((uint32_t)interlaced % 16 == 0);

memset(col_dirtyness, 0, fb_width / 2);
memset(dirty_lines, 0, sizeof(bool) * fb_height);

Expand Down
170 changes: 133 additions & 37 deletions test/test_diff.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#include <esp_heap_caps.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unity.h>


bool _epd_interlace_line(
const uint8_t* to,
const uint8_t* from,
Expand All @@ -21,55 +22,150 @@ static const uint8_t expected_interlaced_pattern[16] = {
static const uint8_t expected_col_dirtyness_pattern[8] = {0x00, 0x0F, 0x00, 0x11,
0xFF, 0xFF, 0x00, 0x80};

TEST_CASE("line diff works", "[epdiy,unit]") {
int example_len = 176;
bool dirty;

uint8_t* from = heap_caps_aligned_alloc(16, example_len, MALLOC_CAP_DEFAULT);
uint8_t* to = heap_caps_aligned_alloc(16, example_len, MALLOC_CAP_DEFAULT);
uint8_t* interlaced = heap_caps_aligned_alloc(16, 2 * example_len, MALLOC_CAP_DEFAULT);
uint8_t* col_dirtyness = heap_caps_aligned_alloc(16, example_len, MALLOC_CAP_DEFAULT);
uint8_t* expected_interlaced = malloc(2 * example_len);
uint8_t* expected_col_dirtyness = malloc(example_len);
typedef struct {
uint8_t* from;
uint8_t* to;
uint8_t* interlaced;
uint8_t* col_dirtyness;
uint8_t* expected_interlaced;
uint8_t* expected_col_dirtyness;
} DiffTestBuffers;

/**
* (Re-)fill buffers with example data, clear result buffers.
*/
static void diff_test_buffers_fill(DiffTestBuffers* bufs, int example_len) {
// initialize test and check patterns
for (int i = 0; i < example_len / 8; i++) {
memcpy(from + (8 * i), from_pattern, 8);
memcpy(to + (8 * i), to_pattern, 8);
memcpy(expected_interlaced + (16 * i), expected_interlaced_pattern, 16);
memcpy(expected_col_dirtyness + (8 * i), expected_col_dirtyness_pattern, 8);
memcpy(bufs->from + (8 * i), from_pattern, 8);
memcpy(bufs->to + (8 * i), to_pattern, 8);
memcpy(bufs->expected_interlaced + (16 * i), expected_interlaced_pattern, 16);
memcpy(bufs->expected_col_dirtyness + (8 * i), expected_col_dirtyness_pattern, 8);
}
memset(col_dirtyness, 0, example_len);

memset(bufs->col_dirtyness, 0, example_len);
memset(bufs->interlaced, 0, example_len * 2);
}

/**
* Allocates and populates buffers for diff tests.
*/
static void diff_test_buffers_init(DiffTestBuffers* bufs, int example_len) {
bufs->from = heap_caps_aligned_alloc(16, example_len, MALLOC_CAP_DEFAULT);
bufs->to = heap_caps_aligned_alloc(16, example_len, MALLOC_CAP_DEFAULT);
bufs->interlaced = heap_caps_aligned_alloc(16, 2 * example_len, MALLOC_CAP_DEFAULT);
bufs->col_dirtyness = heap_caps_aligned_alloc(16, example_len, MALLOC_CAP_DEFAULT);
bufs->expected_interlaced = malloc(2 * example_len);
bufs->expected_col_dirtyness = malloc(example_len);

diff_test_buffers_fill(bufs, example_len);
}

/**
* Free buffers used for diff testing.
*/
static void diff_test_buffers_free(DiffTestBuffers* bufs) {
heap_caps_free(bufs->from);
heap_caps_free(bufs->to);
heap_caps_free(bufs->interlaced);
heap_caps_free(bufs->col_dirtyness);
free(bufs->expected_interlaced);
free(bufs->expected_col_dirtyness);
}

TEST_CASE("simple aligned diff works", "[epdiy,unit]") {
// length of the example buffers in bytes (i.e., half the length in pixels)
const int example_len = 176;
DiffTestBuffers bufs;
bool dirty;

diff_test_buffers_init(&bufs, example_len);

// This should trigger use of vector extensions on the S3
TEST_ASSERT((uint32_t)to % 16 == 0)
TEST_ASSERT((uint32_t)bufs.to % 16 == 0)

// fully aligned
dirty = _epd_interlace_line(to, from, interlaced, col_dirtyness, 2 * example_len);
dirty = _epd_interlace_line(
bufs.to, bufs.from, bufs.interlaced, bufs.col_dirtyness, 2 * example_len
);

TEST_ASSERT(dirty == true);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_col_dirtyness, col_dirtyness, example_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_interlaced, interlaced, 2 * example_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY(bufs.expected_col_dirtyness, bufs.col_dirtyness, example_len);
TEST_ASSERT_EQUAL_UINT8_ARRAY(bufs.expected_interlaced, bufs.interlaced, 2 * example_len);

// force an unaligned line end
dirty = _epd_interlace_line(to, from, interlaced, col_dirtyness, 2 * (example_len - 8));
diff_test_buffers_free(&bufs);
}

TEST_ASSERT(dirty == true);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_col_dirtyness, col_dirtyness, example_len - 8);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_interlaced, interlaced, 2 * (example_len - 8));
TEST_CASE("dirtynes for diff without changes is correct", "[epdiy,unit]") {
const int example_len = 176;
const uint8_t NULL_ARRAY[176 * 2] = {0};
DiffTestBuffers bufs;
bool dirty;

diff_test_buffers_init(&bufs, example_len);

// This should trigger use of vector extensions on the S3
TEST_ASSERT((uint32_t)bufs.to % 16 == 0)

// force an unaligned line start
// both use "from" buffer
dirty = _epd_interlace_line(
to + 8, from + 8, interlaced + 16, col_dirtyness + 8, 2 * (example_len - 8)
bufs.from, bufs.from, bufs.interlaced, bufs.col_dirtyness, 2 * example_len
);

TEST_ASSERT(dirty == true);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_col_dirtyness + 8, col_dirtyness + 8, example_len - 8);
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected_interlaced + 16, interlaced + 16, 2 * (example_len - 8));

heap_caps_free(from);
heap_caps_free(to);
heap_caps_free(interlaced);
heap_caps_free(col_dirtyness);
free(expected_interlaced);
free(expected_col_dirtyness);
TEST_ASSERT(dirty == false);
TEST_ASSERT_EQUAL_UINT8_ARRAY(NULL_ARRAY, bufs.col_dirtyness, example_len);

// both use "to" buffer, misaligned by 4 bytes
dirty = _epd_interlace_line(
bufs.to + 4, bufs.to + 4, bufs.interlaced, bufs.col_dirtyness, 2 * (example_len - 4)
);

TEST_ASSERT(dirty == false);
TEST_ASSERT_EQUAL_UINT8_ARRAY(NULL_ARRAY, bufs.col_dirtyness + 4, example_len - 4);

diff_test_buffers_free(&bufs);
}

TEST_CASE("different 4-byte alignments work", "[epdiy,unit]") {
const int example_len = 176;
const uint8_t NULL_ARRAY[176 * 2] = {0};
DiffTestBuffers bufs;
bool dirty;

diff_test_buffers_init(&bufs, example_len);

// test all combinations of start / end missalignment
for (int start_offset = 0; start_offset <= 16; start_offset += 4) {
for (int end_offset = 0; end_offset <= 16; end_offset += 4) {
int unaligned_len = example_len - end_offset - start_offset;

diff_test_buffers_fill(&bufs, example_len);

// before and after the designated range the buffer shoulld be clear
memset(bufs.expected_col_dirtyness, 0, start_offset);
memset(bufs.expected_interlaced, 0, 2 * start_offset);
memset(bufs.expected_col_dirtyness + start_offset + unaligned_len, 0, end_offset);
memset(
bufs.expected_interlaced + (start_offset + unaligned_len) * 2, 0, end_offset * 2
);

printf(
"testing with alignment (in px): (%d, %d)\n", 2 * start_offset, 2 * unaligned_len
);
dirty = _epd_interlace_line(
bufs.to + start_offset, bufs.from + start_offset,
bufs.interlaced + 2 * start_offset, bufs.col_dirtyness + start_offset,
2 * unaligned_len
);

TEST_ASSERT(dirty == true);

TEST_ASSERT_EQUAL_UINT8_ARRAY(
bufs.expected_col_dirtyness, bufs.col_dirtyness, example_len
);
TEST_ASSERT_EQUAL_UINT8_ARRAY(bufs.expected_interlaced, bufs.interlaced, example_len);
}
}

diff_test_buffers_free(&bufs);
}

0 comments on commit d468b3c

Please sign in to comment.