You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
7.2 KiB
257 lines
7.2 KiB
2 years ago
|
/**
|
||
|
* TODO file description
|
||
|
*/
|
||
|
|
||
|
#include <string.h>
|
||
|
#include <assert.h>
|
||
|
#include "lcdbuf.h"
|
||
|
|
||
|
|
||
|
/** Initialize the struct */
|
||
|
void LcdBuffer_Init(struct LcdBuffer *self, const struct cgrom_entry *cgrom, const struct cgram_pattern *custom_symbols)
|
||
|
{
|
||
|
assert(self);
|
||
|
assert(cgrom);
|
||
|
assert(custom_symbols);
|
||
|
|
||
|
LcdBuffer_Clear(self);
|
||
|
self->cgrom = cgrom;
|
||
|
self->custom_symbols = custom_symbols;
|
||
|
}
|
||
|
|
||
|
/** Clear the screen */
|
||
|
void LcdBuffer_Clear(struct LcdBuffer *self)
|
||
|
{
|
||
|
assert(self);
|
||
|
|
||
|
memset(self->cgram, 0, sizeof(self->cgram));
|
||
|
memset(self->screen, 32, sizeof(self->screen));
|
||
|
memset(self->dirty_extents, 0, sizeof(self->dirty_extents));
|
||
|
self->full_repaint_required = false;
|
||
|
}
|
||
|
|
||
|
/** Write what needs to be written to the HW, clear all dirty marks */
|
||
|
void LcdBuffer_Flush(struct LcdBuffer *self)
|
||
|
{
|
||
|
for (int i = 0; i < 8; i++) {
|
||
|
if (self->cgram[i].refcount > 0 && self->cgram[i].dirty) {
|
||
|
LcdBuffer_IO_WriteCGRAM(i, self->custom_symbols[self->cgram[i].symbol_index].data);
|
||
|
self->cgram[i].dirty = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (self->full_repaint_required) {
|
||
|
for (int r = 0; r < LINE_NUM; r++) {
|
||
|
LcdBuffer_IO_WriteAt(r, 0, self->screen[r], LINE_LEN);
|
||
|
}
|
||
|
memset(self->dirty_extents, 0, sizeof(self->dirty_extents));
|
||
|
} else {
|
||
|
for (int e = 0; e < BUFLEN_DIRTY_LIST; e++) {
|
||
|
struct DirtyExtent *ext = &self->dirty_extents[e];
|
||
|
if (!ext->count) {
|
||
|
continue;
|
||
|
}
|
||
|
LcdBuffer_IO_WriteAt(ext->row, ext->col, &self->screen[ext->row][ext->col], ext->count);
|
||
|
ext->count = 0; // mark the slot as free
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Fully write everything to the display */
|
||
|
void LcdBuffer_FlushAll(struct LcdBuffer *self)
|
||
|
{
|
||
|
for (int i = 0; i < 8; i++) {
|
||
|
if (self->cgram[i].refcount > 0) {
|
||
|
LcdBuffer_IO_WriteCGRAM(i, self->custom_symbols[self->cgram[i].symbol_index].data);
|
||
|
}
|
||
|
self->cgram[i].dirty = false;
|
||
|
}
|
||
|
|
||
|
for (int r = 0; r < LINE_NUM; r++) {
|
||
|
LcdBuffer_IO_WriteAt(r, 0, self->screen[r], LINE_LEN);
|
||
|
}
|
||
|
|
||
|
memset(self->dirty_extents, 0, sizeof(self->dirty_extents));
|
||
|
self->full_repaint_required = false;
|
||
|
}
|
||
|
|
||
|
static void mark_dirty(struct LcdBuffer *self, uint8_t row, uint8_t col)
|
||
|
{
|
||
|
int first_empty_extent_slot = -1;
|
||
|
for (int i = 0; i < BUFLEN_DIRTY_LIST; i++) {
|
||
|
struct DirtyExtent *ext = &self->dirty_extents[i];
|
||
|
if (ext->count == 0) {
|
||
|
// unused
|
||
|
if (first_empty_extent_slot == -1) {
|
||
|
first_empty_extent_slot = i;
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
if (ext->row != row) {
|
||
|
// not this row
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// this is a filled extent
|
||
|
|
||
|
if (ext->col < col && ext->col + ext->count > col) {
|
||
|
// already in this extent
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (col < ext->col && (ext->col - col) <= 5) {
|
||
|
ext->count += (ext->col - col);
|
||
|
ext->col = col;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (col >= ext->col + ext->count && (col - ext->col + ext->count) <= 5) {
|
||
|
ext->count += (col - ext->col + ext->count);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (first_empty_extent_slot == -1) {
|
||
|
self->full_repaint_required = true;
|
||
|
} else {
|
||
|
self->dirty_extents[first_empty_extent_slot].col = col;
|
||
|
self->dirty_extents[first_empty_extent_slot].row = row;
|
||
|
self->dirty_extents[first_empty_extent_slot].count = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Set one utf8 character at a position */
|
||
|
void LcdBuffer_Set(struct LcdBuffer *self, uint8_t row, uint8_t col, struct Utf8Char ch)
|
||
|
{
|
||
|
assert(self);
|
||
|
assert(row < LINE_NUM);
|
||
|
assert(col < LINE_LEN);
|
||
|
|
||
|
uint8_t oldchar = self->screen[row][col];
|
||
|
|
||
|
if (oldchar >= 8 && oldchar == ch.uint) {
|
||
|
// No change
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Fast path for standard ASCII
|
||
|
if (ch.uint >= 32 && ch.uint < 126 && ch.uint != '\\') { // A00 has YEN in place of BACKSLASH
|
||
|
// normal ASCII
|
||
|
if (oldchar < 8) {
|
||
|
// release refcount on the CGRAM cell
|
||
|
self->cgram[oldchar].refcount -= 1;
|
||
|
}
|
||
|
|
||
|
self->screen[row][col] = ch.uint;
|
||
|
goto done_dirty;
|
||
|
}
|
||
|
|
||
|
// Find if it's in CGROM
|
||
|
const struct cgrom_entry *rom = self->cgrom;
|
||
|
for (;;) {
|
||
|
if (rom->symbol.uint == 0) {
|
||
|
// End of the lookup table
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (rom->symbol.uint == ch.uint) {
|
||
|
// found it!
|
||
|
if (oldchar < 8) {
|
||
|
// release refcount on the CGRAM cell
|
||
|
self->cgram[oldchar].refcount -= 1;
|
||
|
}
|
||
|
|
||
|
self->screen[row][col] = rom->address;
|
||
|
goto done_dirty;
|
||
|
}
|
||
|
|
||
|
rom++;
|
||
|
}
|
||
|
|
||
|
// Check if the same custom char is already used - if so, increment refcount and reuse it
|
||
|
int first_empty_custom_slot = -1;
|
||
|
for (int i = 0; i < 8; i++) {
|
||
|
if (self->cgram[i].refcount > 0) {
|
||
|
if (self->cgram[i].uint == ch.uint) {
|
||
|
if (oldchar == i) {
|
||
|
// No change, was already the same custom
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (oldchar < 8) {
|
||
|
// release refcount on the CGRAM cell
|
||
|
self->cgram[oldchar].refcount -= 1;
|
||
|
}
|
||
|
|
||
|
self->cgram[i].refcount += 1;
|
||
|
self->screen[row][col] = i;
|
||
|
goto done_dirty;
|
||
|
}
|
||
|
} else if (first_empty_custom_slot == -1) {
|
||
|
first_empty_custom_slot = i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// New custom pattern is needed
|
||
|
|
||
|
if (oldchar < 8) {
|
||
|
// release refcount on the CGRAM cell
|
||
|
self->cgram[oldchar].refcount -= 1;
|
||
|
}
|
||
|
|
||
|
uint32_t index = 0;
|
||
|
const struct cgram_pattern *pattern = self->custom_symbols;
|
||
|
for (;;) {
|
||
|
if (pattern->symbol.uint == 0) {
|
||
|
// End of the lookup table
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (pattern->symbol.uint == ch.uint) {
|
||
|
// found it!
|
||
|
|
||
|
if (first_empty_custom_slot == -1) {
|
||
|
// Whoops, out of slots. Show a fallback glyph
|
||
|
if (oldchar != pattern->fallback) {
|
||
|
self->screen[row][col] = pattern->fallback;
|
||
|
goto done_dirty;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Allocate a new slot in the CGRAM
|
||
|
self->cgram[first_empty_custom_slot].refcount = 1;
|
||
|
self->cgram[first_empty_custom_slot].uint = ch.uint;
|
||
|
self->cgram[first_empty_custom_slot].dirty = true; // it should be flushed!
|
||
|
self->cgram[first_empty_custom_slot].symbol_index = index;
|
||
|
|
||
|
self->screen[row][col] = first_empty_custom_slot;
|
||
|
goto done_dirty;
|
||
|
}
|
||
|
|
||
|
index++;
|
||
|
pattern++;
|
||
|
}
|
||
|
|
||
|
// Fallback, no way to show this glyph
|
||
|
self->screen[row][col] = '?';
|
||
|
|
||
|
done_dirty:
|
||
|
mark_dirty(self, row, col);
|
||
|
}
|
||
|
|
||
|
/** Write a UTF8 string at a position */
|
||
|
void LcdBuffer_Write(struct LcdBuffer *self, uint8_t row, uint8_t col, char *utf_string)
|
||
|
{
|
||
|
struct Utf8Iterator iter;
|
||
|
Utf8Iterator_Init(&iter, utf_string);
|
||
|
struct Utf8Char uchar;
|
||
|
while ((uchar = Utf8Iterator_Next(&iter)).uint) {
|
||
|
if (col >= LINE_LEN) {
|
||
|
break;
|
||
|
}
|
||
|
LcdBuffer_Set(self, row, col, uchar);
|
||
|
col++;
|
||
|
}
|
||
|
}
|