Reduce data sent to GPU per draw by 30%

Split up the Cell structure into a CPUCell and a GPUCell. Only the
GPUCell part needs to be sent to the GPU. Should make kitty use even
less system resources and make a performance difference on systems where
the GPU bandwidth is constrained.

Also allows adding more CPU only data in the future without affecting
GPU bandwidth. For example, hyperlinks or more combining characters.
This commit is contained in:
Kovid Goyal 2018-05-27 21:25:09 +05:30
parent c8756612f7
commit 8dea5b3e3e
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
13 changed files with 256 additions and 212 deletions

View File

@ -178,7 +178,7 @@ find_substitute_face(CFStringRef str, CTFontRef old_font) {
}
PyObject*
create_fallback_face(PyObject *base_face, Cell* cell, bool UNUSED bold, bool UNUSED italic, bool emoji_presentation, FONTS_DATA_HANDLE fg UNUSED) {
create_fallback_face(PyObject *base_face, CPUCell* cell, bool UNUSED bold, bool UNUSED italic, bool emoji_presentation, FONTS_DATA_HANDLE fg UNUSED) {
CTFace *self = (CTFace*)base_face;
CTFontRef new_font;
if (emoji_presentation) new_font = CTFontCreateWithName((CFStringRef)@"AppleColorEmoji", self->scaled_point_sz, NULL);

View File

@ -136,7 +136,7 @@ END_ALLOW_CASE_RANGE
}
void
apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *params, unsigned int count) {
apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, unsigned int *params, unsigned int count) {
#define RANGE for(unsigned c = 0; c < cell_count; c++, cell++)
#define SET(shift) RANGE { cell->attrs |= (1 << shift); } break;
#define RESET(shift) RANGE { cell->attrs &= ~(1 << shift); } break;
@ -148,7 +148,7 @@ apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *para
unsigned int i = 0, attr;
if (!count) { params[0] = 0; count = 1; }
while (i < count) {
Cell *cell = first_cell;
GPUCell *cell = first_cell;
attr = params[i++];
switch(attr) {
case 0:

View File

@ -80,7 +80,7 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape;
(c)->reverse = (a >> REVERSE_SHIFT) & 1; (c)->strikethrough = (a >> STRIKE_SHIFT) & 1; (c)->dim = (a >> DIM_SHIFT) & 1;
#define COPY_CELL(src, s, dest, d) \
(dest)->cells[d] = (src)->cells[s];
(dest)->cpu_cells[d] = (src)->cpu_cells[s]; (dest)->gpu_cells[d] = (src)->gpu_cells[s];
#define COPY_SELF_CELL(s, d) COPY_CELL(self, s, self, d)
@ -140,15 +140,19 @@ typedef struct {
color_type fg, bg, decoration_fg;
sprite_index sprite_x, sprite_y, sprite_z;
attrs_type attrs;
// The following are only needed on the CPU, not the GPU
} GPUCell;
typedef struct {
char_type ch;
combining_type cc_idx[2];
} Cell;
} CPUCell;
typedef struct {
PyObject_HEAD
Cell *cells;
GPUCell *gpu_cells;
CPUCell *cpu_cells;
index_type xnum, ynum;
bool continued, needs_free, has_dirty_text;
} Line;
@ -157,14 +161,16 @@ typedef struct {
typedef struct {
PyObject_HEAD
Cell *buf;
GPUCell *gpu_cell_buf;
CPUCell *cpu_cell_buf;
index_type xnum, ynum, *line_map, *scratch;
line_attrs_type *line_attrs;
Line *line;
} LineBuf;
typedef struct {
Cell *cells;
GPUCell *gpu_cells;
CPUCell *cpu_cells;
line_attrs_type *line_attrs;
} HistoryBufSegment;
@ -226,10 +232,10 @@ typedef struct {FONTS_DATA_HEAD} *FONTS_DATA_HANDLE;
for(index_type __i__ = (at); __i__ < (line)->xnum - (num); __i__++) { \
COPY_CELL(line, __i__ + (num), line, __i__) \
} \
if ((((line)->cells[(at)].attrs) & WIDTH_MASK) != 1) { \
(line)->cells[(at)].ch = BLANK_CHAR; \
(line)->cells[(at)].attrs = BLANK_CHAR ? 1 : 0; \
clear_sprite_position((line)->cells[(at)]); \
if ((((line)->gpu_cells[(at)].attrs) & WIDTH_MASK) != 1) { \
(line)->cpu_cells[(at)].ch = BLANK_CHAR; \
(line)->gpu_cells[(at)].attrs = BLANK_CHAR ? 1 : 0; \
clear_sprite_position((line)->gpu_cells[(at)]); \
}\
}
@ -260,7 +266,7 @@ Cursor* cursor_copy(Cursor*);
void cursor_copy_to(Cursor *src, Cursor *dest);
void cursor_reset_display_attrs(Cursor*);
void cursor_from_sgr(Cursor *self, unsigned int *params, unsigned int count);
void apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *params, unsigned int count);
void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, unsigned int *params, unsigned int count);
const char* cursor_as_sgr(Cursor*, Cursor*);
double monotonic();

View File

@ -194,7 +194,7 @@ end:
}
PyObject*
create_fallback_face(PyObject UNUSED *base_face, Cell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg) {
create_fallback_face(PyObject UNUSED *base_face, CPUCell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg) {
PyObject *ans = NULL;
FcPattern *pat = FcPatternCreate();
if (pat == NULL) return PyErr_NoMemory();

View File

@ -420,12 +420,12 @@ face_has_codepoint(PyObject* face, char_type cp) {
}
static inline bool
has_emoji_presentation(Cell *cell) {
return (cell->attrs & WIDTH_MASK) == 2 && is_emoji(cell->ch) && cell->cc_idx[0] != VS15;
has_emoji_presentation(CPUCell *cpu_cell, GPUCell *gpu_cell) {
return (gpu_cell->attrs & WIDTH_MASK) == 2 && is_emoji(cpu_cell->ch) && cpu_cell->cc_idx[0] != VS15;
}
static inline bool
has_cell_text(Font *self, Cell *cell) {
has_cell_text(Font *self, CPUCell *cell) {
if (!face_has_codepoint(self->face, cell->ch)) return false;
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
combining_type cc_idx = cell->cc_idx[i];
@ -436,7 +436,7 @@ has_cell_text(Font *self, Cell *cell) {
}
static inline void
output_cell_fallback_data(Cell *cell, bool bold, bool italic, bool emoji_presentation, PyObject *face, bool new_face) {
output_cell_fallback_data(CPUCell *cell, bool bold, bool italic, bool emoji_presentation, PyObject *face, bool new_face) {
printf("U+%x ", cell->ch);
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
printf("U+%x ", codepoint_for_mark(cell->cc_idx[i]));
@ -450,7 +450,7 @@ output_cell_fallback_data(Cell *cell, bool bold, bool italic, bool emoji_present
}
static inline ssize_t
load_fallback_font(FontGroup *fg, Cell *cell, bool bold, bool italic, bool emoji_presentation) {
load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool emoji_presentation) {
if (fg->fallback_fonts_count > 100) { log_error("Too many fallback fonts"); return MISSING_FONT; }
ssize_t f;
@ -475,21 +475,21 @@ load_fallback_font(FontGroup *fg, Cell *cell, bool bold, bool italic, bool emoji
}
static inline ssize_t
fallback_font(FontGroup *fg, Cell *cell) {
bool bold = (cell->attrs >> BOLD_SHIFT) & 1;
bool italic = (cell->attrs >> ITALIC_SHIFT) & 1;
bool emoji_presentation = has_emoji_presentation(cell);
fallback_font(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
bool bold = (gpu_cell->attrs >> BOLD_SHIFT) & 1;
bool italic = (gpu_cell->attrs >> ITALIC_SHIFT) & 1;
bool emoji_presentation = has_emoji_presentation(cpu_cell, gpu_cell);
// Check if one of the existing fallback fonts has this text
for (size_t i = 0, j = fg->first_fallback_font_idx; i < fg->fallback_fonts_count; i++, j++) {
Font *ff = fg->fonts +j;
if (ff->bold == bold && ff->italic == italic && ff->emoji_presentation == emoji_presentation && has_cell_text(ff, cell)) {
if (global_state.debug_font_fallback) output_cell_fallback_data(cell, bold, italic, emoji_presentation, ff->face, false);
if (ff->bold == bold && ff->italic == italic && ff->emoji_presentation == emoji_presentation && has_cell_text(ff, cpu_cell)) {
if (global_state.debug_font_fallback) output_cell_fallback_data(cpu_cell, bold, italic, emoji_presentation, ff->face, false);
return j;
}
}
return load_fallback_font(fg, cell, bold, italic, emoji_presentation);
return load_fallback_font(fg, cpu_cell, bold, italic, emoji_presentation);
}
static inline ssize_t
@ -502,10 +502,10 @@ in_symbol_maps(FontGroup *fg, char_type ch) {
static ssize_t
font_for_cell(FontGroup *fg, Cell *cell) {
font_for_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
START_ALLOW_CASE_RANGE
ssize_t ans;
switch(cell->ch) {
switch(cpu_cell->ch) {
case 0:
case ' ':
return BLANK_FONT;
@ -517,9 +517,9 @@ START_ALLOW_CASE_RANGE
case 0xe0b6:
return BOX_FONT;
default:
ans = in_symbol_maps(fg, cell->ch);
ans = in_symbol_maps(fg, cpu_cell->ch);
if (ans > -1) return ans;
switch(BI_VAL(cell->attrs)) {
switch(BI_VAL(gpu_cell->attrs)) {
case 0:
ans = fg->medium_font_idx; break;
case 1:
@ -530,14 +530,14 @@ START_ALLOW_CASE_RANGE
ans = fg->bi_font_idx; break;
}
if (ans < 0) ans = fg->medium_font_idx;
if (!has_emoji_presentation(cell) && has_cell_text(fg->fonts + ans, cell)) return ans;
return fallback_font(fg, cell);
if (!has_emoji_presentation(cpu_cell, gpu_cell) && has_cell_text(fg->fonts + ans, cpu_cell)) return ans;
return fallback_font(fg, cpu_cell, gpu_cell);
}
END_ALLOW_CASE_RANGE
}
static inline void
set_sprite(Cell *cell, sprite_index x, sprite_index y, sprite_index z) {
set_sprite(GPUCell *cell, sprite_index x, sprite_index y, sprite_index z) {
cell->sprite_x = x; cell->sprite_y = y; cell->sprite_z = z;
}
@ -577,21 +577,21 @@ render_alpha_mask(uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *de
}
static void
render_box_cell(FontGroup *fg, Cell *cell) {
render_box_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
int error = 0;
glyph_index glyph = box_glyph_id(cell->ch);
glyph_index glyph = box_glyph_id(cpu_cell->ch);
static ExtraGlyphs extra_glyphs = {{0}};
SpritePosition *sp = sprite_position_for(fg, &fg->fonts[BOX_FONT], glyph, &extra_glyphs, false, &error);
if (sp == NULL) {
sprite_map_set_error(error); PyErr_Print();
set_sprite(cell, 0, 0, 0);
set_sprite(gpu_cell, 0, 0, 0);
return;
}
set_sprite(cell, sp->x, sp->y, sp->z);
set_sprite(gpu_cell, sp->x, sp->y, sp->z);
if (sp->rendered) return;
sp->rendered = true;
sp->colored = false;
PyObject *ret = PyObject_CallFunction(box_drawing_function, "IIId", cell->ch, fg->cell_width, fg->cell_height, (fg->logical_dpi_x + fg->logical_dpi_y) / 2.0);
PyObject *ret = PyObject_CallFunction(box_drawing_function, "IIId", cpu_cell->ch, fg->cell_width, fg->cell_height, (fg->logical_dpi_x + fg->logical_dpi_y) / 2.0);
if (ret == NULL) { PyErr_Print(); return; }
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0));
clear_canvas(fg);
@ -602,17 +602,17 @@ render_box_cell(FontGroup *fg, Cell *cell) {
}
static inline void
load_hb_buffer(Cell *first_cell, index_type num_cells) {
load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells) {
index_type num;
hb_buffer_clear_contents(harfbuzz_buffer);
while (num_cells) {
attrs_type prev_width = 0;
for (num = 0; num_cells && num < arraysz(shape_buffer) - 20 - arraysz(first_cell->cc_idx); first_cell++, num_cells--) {
for (num = 0; num_cells && num < arraysz(shape_buffer) - 20 - arraysz(first_cpu_cell->cc_idx); first_cpu_cell++, first_gpu_cell++, num_cells--) {
if (prev_width == 2) { prev_width = 0; continue; }
shape_buffer[num++] = first_cell->ch;
prev_width = first_cell->attrs & WIDTH_MASK;
for (unsigned i = 0; i < arraysz(first_cell->cc_idx) && first_cell->cc_idx[i]; i++) {
shape_buffer[num++] = codepoint_for_mark(first_cell->cc_idx[i]);
shape_buffer[num++] = first_cpu_cell->ch;
prev_width = first_gpu_cell->attrs & WIDTH_MASK;
for (unsigned i = 0; i < arraysz(first_cpu_cell->cc_idx) && first_cpu_cell->cc_idx[i]; i++) {
shape_buffer[num++] = codepoint_for_mark(first_cpu_cell->cc_idx[i]);
}
}
hb_buffer_add_utf32(harfbuzz_buffer, shape_buffer, num, 0, num);
@ -622,7 +622,7 @@ load_hb_buffer(Cell *first_cell, index_type num_cells) {
static inline void
set_cell_sprite(Cell *cell, SpritePosition *sp) {
set_cell_sprite(GPUCell *cell, SpritePosition *sp) {
cell->sprite_x = sp->x; cell->sprite_y = sp->y; cell->sprite_z = sp->z;
if (sp->colored) cell->sprite_z |= 0x4000;
}
@ -636,7 +636,7 @@ extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells)
}
static inline void
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs) {
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs) {
static SpritePosition* sprite_position[16];
int error = 0;
num_cells = MIN(sizeof(sprite_position)/sizeof(sprite_position[0]), num_cells);
@ -645,19 +645,19 @@ render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, Cel
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); return; }
}
if (sprite_position[0]->rendered) {
for (unsigned int i = 0; i < num_cells; i++) { set_cell_sprite(cells + i, sprite_position[i]); }
for (unsigned int i = 0; i < num_cells; i++) { set_cell_sprite(gpu_cells + i, sprite_position[i]); }
return;
}
clear_canvas(fg);
bool was_colored = (cells->attrs & WIDTH_MASK) == 2 && is_emoji(cells->ch);
bool was_colored = (gpu_cells->attrs & WIDTH_MASK) == 2 && is_emoji(cpu_cells->ch);
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg);
if (PyErr_Occurred()) PyErr_Print();
for (unsigned int i = 0; i < num_cells; i++) {
sprite_position[i]->rendered = true;
sprite_position[i]->colored = was_colored;
set_cell_sprite(cells + i, sprite_position[i]);
set_cell_sprite(gpu_cells + i, sprite_position[i]);
pixel *buf = num_cells == 1 ? fg->canvas : extract_cell_from_canvas(fg, i, num_cells);
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sprite_position[i]->x, sprite_position[i]->y, sprite_position[i]->z, buf);
}
@ -665,7 +665,8 @@ render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, Cel
}
typedef struct {
Cell *cell;
CPUCell *cpu_cell;
GPUCell *gpu_cell;
unsigned int num_codepoints;
unsigned int codepoints_consumed;
char_type current_codepoint;
@ -681,7 +682,8 @@ typedef struct {
CellData current_cell_data;
Group *groups;
size_t groups_capacity, group_idx, glyph_idx, cell_idx, num_cells, num_glyphs;
Cell *first_cell, *last_cell;
CPUCell *first_cpu_cell, *last_cpu_cell;
GPUCell *first_gpu_cell, *last_gpu_cell;
hb_glyph_info_t *info;
hb_glyph_position_t *positions;
} GroupState;
@ -689,14 +691,14 @@ typedef struct {
static GroupState group_state = {0};
static inline unsigned int
num_codepoints_in_cell(Cell *cell) {
num_codepoints_in_cell(CPUCell *cell) {
unsigned int ans = 1;
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) ans++;
return ans;
}
static inline void
shape(Cell *first_cell, index_type num_cells, hb_font_t *font) {
shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb_font_t *font) {
if (group_state.groups_capacity <= 2 * num_cells) {
group_state.groups_capacity = MAX(128, 2 * num_cells); // avoid unnecessary reallocs
group_state.groups = realloc(group_state.groups, sizeof(Group) * group_state.groups_capacity);
@ -705,15 +707,21 @@ shape(Cell *first_cell, index_type num_cells, hb_font_t *font) {
group_state.previous_cluster = UINT32_MAX;
group_state.prev_was_special = false;
group_state.prev_was_empty = false;
group_state.current_cell_data.cell = first_cell; group_state.current_cell_data.num_codepoints = num_codepoints_in_cell(first_cell); group_state.current_cell_data.codepoints_consumed = 0; group_state.current_cell_data.current_codepoint = first_cell->ch;
group_state.current_cell_data.cpu_cell = first_cpu_cell;
group_state.current_cell_data.gpu_cell = first_gpu_cell;
group_state.current_cell_data.num_codepoints = num_codepoints_in_cell(first_cpu_cell);
group_state.current_cell_data.codepoints_consumed = 0;
group_state.current_cell_data.current_codepoint = first_cpu_cell->ch;
memset(group_state.groups, 0, sizeof(Group) * group_state.groups_capacity);
group_state.group_idx = 0;
group_state.glyph_idx = 0;
group_state.cell_idx = 0;
group_state.num_cells = num_cells;
group_state.first_cell = first_cell;
group_state.last_cell = first_cell + (num_cells ? num_cells - 1 : 0);
load_hb_buffer(first_cell, num_cells);
group_state.first_cpu_cell = first_cpu_cell;
group_state.first_gpu_cell = first_gpu_cell;
group_state.last_cpu_cell = first_cpu_cell + (num_cells ? num_cells - 1 : 0);
group_state.last_gpu_cell = first_gpu_cell + (num_cells ? num_cells - 1 : 0);
load_hb_buffer(first_cpu_cell, first_gpu_cell, num_cells);
hb_shape(font, harfbuzz_buffer, NULL, 0);
unsigned int info_length, positions_length;
group_state.info = hb_buffer_get_glyph_infos(harfbuzz_buffer, &info_length);
@ -752,24 +760,25 @@ is_empty_glyph(glyph_index glyph_id, Font *font) {
}
static inline unsigned int
check_cell_consumed(CellData *cell_data, Cell *last_cell) {
check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell) {
cell_data->codepoints_consumed++;
if (cell_data->codepoints_consumed >= cell_data->num_codepoints) {
attrs_type width = cell_data->cell->attrs & WIDTH_MASK;
cell_data->cell += MAX(1, width);
attrs_type width = cell_data->gpu_cell->attrs & WIDTH_MASK;
cell_data->cpu_cell += MAX(1, width);
cell_data->gpu_cell += MAX(1, width);
cell_data->codepoints_consumed = 0;
if (cell_data->cell <= last_cell) {
cell_data->num_codepoints = num_codepoints_in_cell(cell_data->cell);
cell_data->current_codepoint = cell_data->cell->ch;
if (cell_data->cpu_cell <= last_cpu_cell) {
cell_data->num_codepoints = num_codepoints_in_cell(cell_data->cpu_cell);
cell_data->current_codepoint = cell_data->cpu_cell->ch;
} else cell_data->current_codepoint = 0;
return width;
} else {
switch(cell_data->codepoints_consumed) {
case 0:
cell_data->current_codepoint = cell_data->cell->ch;
cell_data->current_codepoint = cell_data->cpu_cell->ch;
break;
default:
cell_data->current_codepoint = codepoint_for_mark(cell_data->cell->cc_idx[cell_data->codepoints_consumed - 1]);
cell_data->current_codepoint = codepoint_for_mark(cell_data->cpu_cell->cc_idx[cell_data->codepoints_consumed - 1]);
break;
}
}
@ -778,8 +787,8 @@ check_cell_consumed(CellData *cell_data, Cell *last_cell) {
static inline void
shape_run(Cell *first_cell, index_type num_cells, Font *font) {
shape(first_cell, num_cells, harfbuzz_font_for_face(font->face));
shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, Font *font) {
shape(first_cpu_cell, first_gpu_cell, num_cells, harfbuzz_font_for_face(font->face));
#if 0
// You can also generate this easily using hb-shape --show-extents --cluster-level=1 --shapers=ot /path/to/font/file text
hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, group_state.num_glyphs, (char*)canvas, sizeof(pixel) * CELLS_IN_CANVAS * cell_width * cell_height, NULL, harfbuzz_font_for_face(font->face), HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS);
@ -853,7 +862,7 @@ shape_run(Cell *first_cell, index_type num_cells, Font *font) {
} else {
unsigned int num_cells_consumed = 0, start_cell_idx = G(cell_idx);
while (num_codepoints_used_by_glyph && G(cell_idx) < G(num_cells)) {
unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cell));
unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cpu_cell));
G(cell_idx) += w;
num_cells_consumed += w;
num_codepoints_used_by_glyph--;
@ -915,7 +924,7 @@ render_groups(FontGroup *fg, Font *font) {
int last = -1;
for (i = 1; i < MIN(arraysz(ed.data) + 1, group->num_glyphs); i++) { last = i - 1; ed.data[last] = G(info)[group->first_glyph_idx + i].codepoint; }
if ((size_t)(last + 1) < arraysz(ed.data)) ed.data[last + 1] = 0;
render_group(fg, group->num_cells, group->num_glyphs, G(first_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed);
render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed);
idx++;
}
}
@ -927,7 +936,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
int index = 0;
if(!PyArg_ParseTuple(args, "O!|zi", &Line_Type, &line, &path, &index)) return NULL;
index_type num = 0;
while(num < line->xnum && line->cells[num].ch) num += line->cells[num].attrs & WIDTH_MASK;
while(num < line->xnum && line->cpu_cells[num].ch) num += line->gpu_cells[num].attrs & WIDTH_MASK;
PyObject *face = NULL;
Font *font;
if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create at least one font group first"); return NULL; }
@ -940,7 +949,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
FontGroup *fg = font_groups;
font = fg->fonts + fg->medium_font_idx;
}
shape_run(line->cells, num, font);
shape_run(line->cpu_cells, line->gpu_cells, num, font);
PyObject *ans = PyList_New(0);
unsigned int idx = 0;
@ -961,21 +970,21 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
#undef G
static inline void
render_run(FontGroup *fg, Cell *first_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature) {
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature) {
switch(font_idx) {
default:
shape_run(first_cell, num_cells, &fg->fonts[font_idx]);
shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[font_idx]);
if (pua_space_ligature) merge_groups_for_pua_space_ligature();
render_groups(fg, &fg->fonts[font_idx]);
break;
case BLANK_FONT:
while(num_cells--) set_sprite(first_cell++, 0, 0, 0);
while(num_cells--) { set_sprite(first_gpu_cell, 0, 0, 0); first_cpu_cell++; first_gpu_cell++; }
break;
case BOX_FONT:
while(num_cells--) render_box_cell(fg, first_cell++);
while(num_cells--) { render_box_cell(fg, first_cpu_cell, first_gpu_cell); first_cpu_cell++; first_gpu_cell++; }
break;
case MISSING_FONT:
while(num_cells--) set_sprite(first_cell++, MISSING_GLYPH, 0, 0);
while(num_cells--) { set_sprite(first_gpu_cell, MISSING_GLYPH, 0, 0); first_cpu_cell++; first_gpu_cell++; }
break;
}
}
@ -987,32 +996,33 @@ is_private_use(char_type ch) {
void
render_line(FONTS_DATA_HANDLE fg_, Line *line) {
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(fg, line->cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false);
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(fg, line->cpu_cells + first_cell_in_run, line->gpu_cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false);
FontGroup *fg = (FontGroup*)fg_;
ssize_t run_font_idx = NO_FONT;
index_type first_cell_in_run, i;
attrs_type prev_width = 0;
for (i=0, first_cell_in_run=0; i < line->xnum; i++) {
if (prev_width == 2) { prev_width = 0; continue; }
Cell *cell = line->cells + i;
ssize_t cell_font_idx = font_for_cell(fg, cell);
if (is_private_use(cell->ch) && i + 1 < line->xnum && (line->cells[i+1].ch == ' ' || line->cells[i+1].ch == 0) && cell_font_idx != BOX_FONT && cell_font_idx != MISSING_FONT) {
CPUCell *cpu_cell = line->cpu_cells + i;
GPUCell *gpu_cell = line->gpu_cells + i;
ssize_t cell_font_idx = font_for_cell(fg, cpu_cell, gpu_cell);
if (is_private_use(cpu_cell->ch) && i + 1 < line->xnum && (line->cpu_cells[i+1].ch == ' ' || line->cpu_cells[i+1].ch == 0) && cell_font_idx != BOX_FONT && cell_font_idx != MISSING_FONT) {
// We have a private use char followed by a space char, render it as a two cell ligature.
Cell *space_cell = line->cells + i+1;
GPUCell *space_cell = line->gpu_cells + i+1;
// Ensure the space cell uses the foreground colors from the PUA cell
// This is needed because there are stupid applications like
// powerline that use PUA+space with different foreground colors
// for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467
space_cell->fg = cell->fg; space_cell->decoration_fg = cell->decoration_fg;
space_cell->fg = gpu_cell->fg; space_cell->decoration_fg = gpu_cell->decoration_fg;
RENDER;
render_run(fg, line->cells + i, 2, cell_font_idx, true);
render_run(fg, line->cpu_cells + i, line->gpu_cells + i, 2, cell_font_idx, true);
run_font_idx = NO_FONT;
first_cell_in_run = i + 2;
prev_width = line->cells[i+1].attrs & WIDTH_MASK;
prev_width = line->gpu_cells[i+1].attrs & WIDTH_MASK;
i++;
continue;
}
prev_width = cell->attrs & WIDTH_MASK;
prev_width = gpu_cell->attrs & WIDTH_MASK;
if (run_font_idx == NO_FONT) run_font_idx = cell_font_idx;
if (run_font_idx == cell_font_idx) continue;
RENDER;
@ -1259,15 +1269,16 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) {
PyObject *text;
int bold, italic;
if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL;
Cell cell = {0};
static Py_UCS4 char_buf[2 + arraysz(cell.cc_idx)];
CPUCell cpu_cell = {0};
GPUCell gpu_cell = {0};
static Py_UCS4 char_buf[2 + arraysz(cpu_cell.cc_idx)];
if (!PyUnicode_AsUCS4(text, char_buf, arraysz(char_buf), 1)) return NULL;
cell.ch = char_buf[0];
for (unsigned i = 0; i + 1 < (unsigned) PyUnicode_GetLength(text) && i < arraysz(cell.cc_idx); i++) cell.cc_idx[i] = mark_for_codepoint(char_buf[i + 1]);
if (bold) cell.attrs |= 1 << BOLD_SHIFT;
if (italic) cell.attrs |= 1 << ITALIC_SHIFT;
cpu_cell.ch = char_buf[0];
for (unsigned i = 0; i + 1 < (unsigned) PyUnicode_GetLength(text) && i < arraysz(cpu_cell.cc_idx); i++) cpu_cell.cc_idx[i] = mark_for_codepoint(char_buf[i + 1]);
if (bold) gpu_cell.attrs |= 1 << BOLD_SHIFT;
if (italic) gpu_cell.attrs |= 1 << ITALIC_SHIFT;
FontGroup *fg = font_groups;
ssize_t ans = fallback_font(fg, &cell);
ssize_t ans = fallback_font(fg, &cpu_cell, &gpu_cell);
if (ans < 0) { PyErr_SetString(PyExc_ValueError, "Too many fallback fonts"); return NULL; }
return fg->fonts[ans].face;
}

View File

@ -22,7 +22,7 @@ hb_font_t* harfbuzz_font_for_face(PyObject*);
bool set_size_for_face(PyObject*, unsigned int, bool, FONTS_DATA_HANDLE);
void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*);
bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE);
PyObject* create_fallback_face(PyObject *base_face, Cell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg);
PyObject* create_fallback_face(PyObject *base_face, CPUCell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg);
PyObject* specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE);
PyObject* face_from_path(const char *path, int index, FONTS_DATA_HANDLE);
PyObject* face_from_descriptor(PyObject*, FONTS_DATA_HANDLE);

View File

@ -18,9 +18,10 @@ add_segment(HistoryBuf *self) {
self->segments = PyMem_Realloc(self->segments, sizeof(HistoryBufSegment) * self->num_segments);
if (self->segments == NULL) fatal("Out of memory allocating new history buffer segment");
HistoryBufSegment *s = self->segments + self->num_segments - 1;
s->cells = PyMem_Calloc(self->xnum * SEGMENT_SIZE, sizeof(Cell));
s->cpu_cells = PyMem_Calloc(self->xnum * SEGMENT_SIZE, sizeof(CPUCell));
s->gpu_cells = PyMem_Calloc(self->xnum * SEGMENT_SIZE, sizeof(GPUCell));
s->line_attrs = PyMem_Calloc(SEGMENT_SIZE, sizeof(line_attrs_type));
if (s->cells == NULL || s->line_attrs == NULL) fatal("Out of memory allocating new history buffer segment");
if (s->cpu_cells == NULL || s->gpu_cells == NULL || s->line_attrs == NULL) fatal("Out of memory allocating new history buffer segment");
}
static inline index_type
@ -37,9 +38,14 @@ segment_for(HistoryBuf *self, index_type y) {
return self->segments[seg_num].which + y * stride; \
}
static inline Cell*
lineptr(HistoryBuf *self, index_type y) {
seg_ptr(cells, self->xnum);
static inline CPUCell*
cpu_lineptr(HistoryBuf *self, index_type y) {
seg_ptr(cpu_cells, self->xnum);
}
static inline GPUCell*
gpu_lineptr(HistoryBuf *self, index_type y) {
seg_ptr(gpu_cells, self->xnum);
}
@ -64,12 +70,12 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
if (self != NULL) {
self->xnum = xnum;
self->ynum = ynum;
self->line = alloc_line();
self->num_segments = 0;
add_segment(self);
self->line = alloc_line();
self->line->xnum = xnum;
for(index_type y = 0; y < self->ynum; y++) {
clear_chars_in_line(lineptr(self, y), self->xnum, BLANK_CHAR);
clear_chars_in_line(cpu_lineptr(self, y), gpu_lineptr(self, y), self->xnum, BLANK_CHAR);
}
}
@ -80,7 +86,8 @@ static void
dealloc(HistoryBuf* self) {
Py_CLEAR(self->line);
for (size_t i = 0; i < self->num_segments; i++) {
PyMem_Free(self->segments[i].cells);
PyMem_Free(self->segments[i].cpu_cells);
PyMem_Free(self->segments[i].gpu_cells);
PyMem_Free(self->segments[i].line_attrs);
}
PyMem_Free(self->segments);
@ -99,7 +106,8 @@ index_of(HistoryBuf *self, index_type lnum) {
static inline void
init_line(HistoryBuf *self, index_type num, Line *l) {
// Initialize the line l, setting its pointer to the offsets for the line at index (buffer position) num
l->cells = lineptr(self, num);
l->cpu_cells = cpu_lineptr(self, num);
l->gpu_cells = gpu_lineptr(self, num);
l->continued = *attrptr(self, num) & CONTINUED_MASK;
l->has_dirty_text = *attrptr(self, num) & TEXT_DIRTY_MASK ? true : false;
}
@ -286,7 +294,8 @@ void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other) {
if (other->xnum == self->xnum && other->ynum == self->ynum) {
// Fast path
for (index_type i = 0; i < self->num_segments; i++) {
memcpy(other->segments[i].cells, self->segments[i].cells, SEGMENT_SIZE * self->xnum * sizeof(Cell));
memcpy(other->segments[i].cpu_cells, self->segments[i].cpu_cells, SEGMENT_SIZE * self->xnum * sizeof(CPUCell));
memcpy(other->segments[i].gpu_cells, self->segments[i].gpu_cells, SEGMENT_SIZE * self->xnum * sizeof(GPUCell));
memcpy(other->segments[i].line_attrs, self->segments[i].line_attrs, SEGMENT_SIZE * sizeof(line_attrs_type));
}
other->count = self->count; other->start_of_data = self->start_of_data;

View File

@ -12,19 +12,25 @@
extern PyTypeObject Line_Type;
extern PyTypeObject HistoryBuf_Type;
static inline Cell*
lineptr(LineBuf *linebuf, index_type y) {
return linebuf->buf + y * linebuf->xnum;
static inline CPUCell*
cpu_lineptr(LineBuf *linebuf, index_type y) {
return linebuf->cpu_cell_buf + y * linebuf->xnum;
}
static inline GPUCell*
gpu_lineptr(LineBuf *linebuf, index_type y) {
return linebuf->gpu_cell_buf + y * linebuf->xnum;
}
static inline void
clear_chars_to(LineBuf* linebuf, index_type y, char_type ch) {
clear_chars_in_line(lineptr(linebuf, y), linebuf->xnum, ch);
clear_chars_in_line(cpu_lineptr(linebuf, y), gpu_lineptr(linebuf, y), linebuf->xnum, ch);
}
void
linebuf_clear(LineBuf *self, char_type ch) {
memset(self->buf, 0, self->xnum * self->ynum * sizeof(Cell));
memset(self->cpu_cell_buf, 0, self->xnum * self->ynum * sizeof(CPUCell));
memset(self->gpu_cell_buf, 0, self->xnum * self->ynum * sizeof(GPUCell));
memset(self->line_attrs, 0, self->ynum * sizeof(line_attrs_type));
for (index_type i = 0; i < self->ynum; i++) self->line_map[i] = i;
if (ch != 0) {
@ -73,14 +79,15 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
if (self != NULL) {
self->xnum = xnum;
self->ynum = ynum;
self->buf = PyMem_Calloc(xnum * ynum, sizeof(Cell));
self->cpu_cell_buf = PyMem_Calloc(xnum * ynum, sizeof(CPUCell));
self->gpu_cell_buf = PyMem_Calloc(xnum * ynum, sizeof(GPUCell));
self->line_map = PyMem_Calloc(ynum, sizeof(index_type));
self->scratch = PyMem_Calloc(ynum, sizeof(index_type));
self->line_attrs = PyMem_Calloc(ynum, sizeof(line_attrs_type));
self->line = alloc_line();
if (self->buf == NULL || self->line_map == NULL || self->scratch == NULL || self->line_attrs == NULL || self->line == NULL) {
if (self->cpu_cell_buf == NULL || self->gpu_cell_buf == NULL || self->line_map == NULL || self->scratch == NULL || self->line_attrs == NULL || self->line == NULL) {
PyErr_NoMemory();
PyMem_Free(self->buf); PyMem_Free(self->line_map); PyMem_Free(self->line_attrs); Py_CLEAR(self->line);
PyMem_Free(self->cpu_cell_buf); PyMem_Free(self->gpu_cell_buf); PyMem_Free(self->line_map); PyMem_Free(self->line_attrs); Py_CLEAR(self->line);
Py_CLEAR(self);
} else {
self->line->xnum = xnum;
@ -96,7 +103,8 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
static void
dealloc(LineBuf* self) {
PyMem_Free(self->buf);
PyMem_Free(self->cpu_cell_buf);
PyMem_Free(self->gpu_cell_buf);
PyMem_Free(self->line_map);
PyMem_Free(self->line_attrs);
PyMem_Free(self->scratch);
@ -106,7 +114,8 @@ dealloc(LineBuf* self) {
static inline void
init_line(LineBuf *lb, Line *l, index_type ynum) {
l->cells = lineptr(lb, ynum);
l->cpu_cells = cpu_lineptr(lb, ynum);
l->gpu_cells = gpu_lineptr(lb, ynum);
}
void
@ -133,13 +142,13 @@ line(LineBuf *self, PyObject *y) {
unsigned int
linebuf_char_width_at(LineBuf *self, index_type x, index_type y) {
return (lineptr(self, self->line_map[y])[x].attrs) & WIDTH_MASK;
return (gpu_lineptr(self, self->line_map[y])[x].attrs) & WIDTH_MASK;
}
void
linebuf_set_attribute(LineBuf *self, unsigned int shift, unsigned int val) {
for (index_type y = 0; y < self->ynum; y++) {
set_attribute_on_line(lineptr(self, y), shift, val, self->xnum);
set_attribute_on_line(gpu_lineptr(self, y), shift, val, self->xnum);
self->line_attrs[y] |= TEXT_DIRTY_MASK;
}
}
@ -181,12 +190,14 @@ dirty_lines(LineBuf *self, PyObject *a UNUSED) {
static inline bool
allocate_line_storage(Line *line, bool initialize) {
if (initialize) {
line->cells = PyMem_Calloc(line->xnum, sizeof(Cell));
if (line->cells == NULL) { PyErr_NoMemory(); return false; }
if (BLANK_CHAR != 0) clear_chars_in_line(line->cells, line->xnum, BLANK_CHAR);
line->cpu_cells = PyMem_Calloc(line->xnum, sizeof(CPUCell));
line->gpu_cells = PyMem_Calloc(line->xnum, sizeof(GPUCell));
if (line->cpu_cells == NULL || line->gpu_cells) { PyErr_NoMemory(); return false; }
if (BLANK_CHAR != 0) clear_chars_in_line(line->cpu_cells, line->gpu_cells, line->xnum, BLANK_CHAR);
} else {
line->cells = PyMem_Malloc(line->xnum * sizeof(Cell));
if (line->cells == NULL) { PyErr_NoMemory(); return false; }
line->cpu_cells = PyMem_Malloc(line->xnum * sizeof(CPUCell));
line->gpu_cells = PyMem_Malloc(line->xnum * sizeof(GPUCell));
if (line->cpu_cells == NULL || line->gpu_cells == NULL) { PyErr_NoMemory(); return false; }
}
line->needs_free = 1;
return true;
@ -232,8 +243,9 @@ copy_line_to(LineBuf *self, PyObject *args) {
static inline void
clear_line_(Line *l, index_type xnum) {
memset(l->cells, 0, xnum * sizeof(Cell));
if (BLANK_CHAR != 0) clear_chars_in_line(l->cells, xnum, BLANK_CHAR);
memset(l->cpu_cells, 0, xnum * sizeof(CPUCell));
memset(l->gpu_cells, 0, xnum * sizeof(GPUCell));
if (BLANK_CHAR != 0) clear_chars_in_line(l->cpu_cells, l->gpu_cells, xnum, BLANK_CHAR);
l->has_dirty_text = false;
}
@ -515,7 +527,8 @@ linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *num_content_lines_befo
if (other->xnum == self->xnum && other->ynum == self->ynum) {
memcpy(other->line_map, self->line_map, sizeof(index_type) * self->ynum);
memcpy(other->line_attrs, self->line_attrs, sizeof(bool) * self->ynum);
memcpy(other->buf, self->buf, self->xnum * self->ynum * sizeof(Cell));
memcpy(other->cpu_cell_buf, self->cpu_cell_buf, self->xnum * self->ynum * sizeof(CPUCell));
memcpy(other->gpu_cell_buf, self->gpu_cell_buf, self->xnum * self->ynum * sizeof(GPUCell));
*num_content_lines_before = self->ynum; *num_content_lines_after = self->ynum;
return;
}
@ -524,7 +537,7 @@ linebuf_rewrap(LineBuf *self, LineBuf *other, index_type *num_content_lines_befo
first = self->ynum;
do {
first--;
Cell *cells = lineptr(self, self->line_map[first]);
CPUCell *cells = cpu_lineptr(self, self->line_map[first]);
for(i = 0; i < self->xnum; i++) {
if ((cells[i].ch) != BLANK_CHAR) { is_empty = false; break; }
}

View File

@ -20,7 +20,8 @@ new(PyTypeObject UNUSED *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
static void
dealloc(Line* self) {
if (self->needs_free) {
PyMem_Free(self->cells);
PyMem_Free(self->cpu_cells);
PyMem_Free(self->gpu_cells);
}
Py_TYPE(self)->tp_free((PyObject*)self);
}
@ -29,13 +30,13 @@ unsigned int
line_length(Line *self) {
index_type last = self->xnum - 1;
for (index_type i = 0; i < self->xnum; i++) {
if ((self->cells[last - i].ch) != BLANK_CHAR) return self->xnum - i;
if ((self->cpu_cells[last - i].ch) != BLANK_CHAR) return self->xnum - i;
}
return 0;
}
PyObject*
cell_text(Cell *cell) {
cell_text(CPUCell *cell) {
PyObject *ans;
unsigned num = 1;
static Py_UCS4 buf[arraysz(cell->cc_idx) + 1];
@ -59,7 +60,7 @@ find_colon_slash(Line *self, index_type x, index_type limit) {
limit = MAX(2, limit);
if (pos < limit) return 0;
do {
char_type ch = self->cells[pos].ch;
char_type ch = self->cpu_cells[pos].ch;
if (!is_url_char(ch)) return false;
switch(state) {
case ANY:
@ -83,7 +84,7 @@ prefix_matches(Line *self, index_type at, const char* prefix, index_type prefix_
if (prefix_len > at) return false;
index_type p, i;
for (p = at - prefix_len, i = 0; i < prefix_len && p < self->xnum; i++, p++) {
if ((self->cells[p].ch) != (unsigned char)prefix[i]) return false;
if ((self->cpu_cells[p].ch) != (unsigned char)prefix[i]) return false;
}
return i == prefix_len;
}
@ -108,7 +109,7 @@ static inline bool
has_url_beyond(Line *self, index_type x) {
if (self->xnum <= x + MIN_URL_LEN + 3) return false;
for (index_type i = x; i < MIN(x + MIN_URL_LEN + 3, self->xnum); i++) {
if (!is_url_char(self->cells[i].ch)) return false;
if (!is_url_char(self->cpu_cells[i].ch)) return false;
}
return true;
}
@ -134,9 +135,9 @@ index_type
line_url_end_at(Line *self, index_type x, bool check_short) {
index_type ans = x;
if (x >= self->xnum || (check_short && self->xnum <= MIN_URL_LEN + 3)) return 0;
while (ans < self->xnum && is_url_char(self->cells[ans].ch)) ans++;
while (ans < self->xnum && is_url_char(self->cpu_cells[ans].ch)) ans++;
if (ans) ans--;
while (ans > x && can_strip_from_end_of_url(self->cells[ans].ch)) ans--;
while (ans > x && can_strip_from_end_of_url(self->cpu_cells[ans].ch)) ans--;
return ans;
}
@ -158,11 +159,11 @@ static PyObject*
text_at(Line* self, Py_ssize_t xval) {
#define text_at_doc "[x] -> Return the text in the specified cell"
if ((unsigned)xval >= self->xnum) { PyErr_SetString(PyExc_IndexError, "Column number out of bounds"); return NULL; }
return cell_text(self->cells + xval);
return cell_text(self->cpu_cells + xval);
}
size_t
cell_as_unicode(Cell *cell, bool include_cc, Py_UCS4 *buf, char_type zero_char) {
cell_as_unicode(CPUCell *cell, bool include_cc, Py_UCS4 *buf, char_type zero_char) {
size_t n = 1;
buf[0] = cell->ch ? cell->ch : zero_char;
if (include_cc) {
@ -172,7 +173,7 @@ cell_as_unicode(Cell *cell, bool include_cc, Py_UCS4 *buf, char_type zero_char)
}
size_t
cell_as_utf8(Cell *cell, bool include_cc, char *buf, char_type zero_char) {
cell_as_utf8(CPUCell *cell, bool include_cc, char *buf, char_type zero_char) {
size_t n = encode_utf8(cell->ch ? cell->ch : zero_char, buf);
if (include_cc) {
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) n += encode_utf8(codepoint_for_mark(cell->cc_idx[i]), buf + n);
@ -188,13 +189,13 @@ unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc
static Py_UCS4 buf[4096];
if (leading_char) buf[n++] = leading_char;
char_type previous_width = 0;
for(index_type i = start; i < limit && n < arraysz(buf) - 2 - arraysz(self->cells->cc_idx); i++) {
char_type ch = self->cells[i].ch;
for(index_type i = start; i < limit && n < arraysz(buf) - 2 - arraysz(self->cpu_cells->cc_idx); i++) {
char_type ch = self->cpu_cells[i].ch;
if (ch == 0) {
if (previous_width == 2) { previous_width = 0; continue; };
}
n += cell_as_unicode(self->cells + i, include_cc, buf + n, ' ');
previous_width = self->cells[i].attrs & WIDTH_MASK;
n += cell_as_unicode(self->cpu_cells + i, include_cc, buf + n, ' ');
previous_width = self->gpu_cells[i].attrs & WIDTH_MASK;
}
return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n);
}
@ -209,7 +210,7 @@ sprite_at(Line* self, PyObject *x) {
#define sprite_at_doc "[x] -> Return the sprite in the specified cell"
unsigned long xval = PyLong_AsUnsignedLong(x);
if (xval >= self->xnum) { PyErr_SetString(PyExc_IndexError, "Column number out of bounds"); return NULL; }
Cell *c = self->cells + xval;
GPUCell *c = self->gpu_cells + xval;
return Py_BuildValue("HHH", c->sprite_x, c->sprite_y, c->sprite_z);
}
@ -236,21 +237,21 @@ line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen) {
Cursor *cursor = &c1, *prev_cursor = &c2, *t;
for (index_type pos=0; pos < limit; pos++) {
char_type attrs = self->cells[pos].attrs, ch = self->cells[pos].ch;
char_type attrs = self->gpu_cells[pos].attrs, ch = self->cpu_cells[pos].ch;
if (ch == 0) {
if (previous_width == 2) { previous_width = 0; continue; }
ch = ' ';
}
ATTRS_TO_CURSOR(attrs, cursor);
cursor->fg = self->cells[pos].fg; cursor->bg = self->cells[pos].bg;
cursor->decoration_fg = self->cells[pos].decoration_fg & COL_MASK;
cursor->fg = self->gpu_cells[pos].fg; cursor->bg = self->gpu_cells[pos].bg;
cursor->decoration_fg = self->gpu_cells[pos].decoration_fg & COL_MASK;
const char *sgr = cursor_as_sgr(cursor, prev_cursor);
t = prev_cursor; prev_cursor = cursor; cursor = t;
if (*sgr) WRITE_SGR(sgr);
WRITE_CH(ch);
for(unsigned c = 0; c < arraysz(self->cells[pos].cc_idx) && self->cells[pos].cc_idx[c]; c++) {
WRITE_CH(codepoint_for_mark(self->cells[pos].cc_idx[c]));
for(unsigned c = 0; c < arraysz(self->cpu_cells[pos].cc_idx) && self->cpu_cells[pos].cc_idx[c]; c++) {
WRITE_CH(codepoint_for_mark(self->cpu_cells[pos].cc_idx[c]));
}
previous_width = attrs & WIDTH_MASK;
}
@ -293,13 +294,13 @@ width(Line *self, PyObject *val) {
#define width_doc "width(x) -> the width of the character at x"
unsigned long x = PyLong_AsUnsignedLong(val);
if (x >= self->xnum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; }
char_type attrs = self->cells[x].attrs;
char_type attrs = self->gpu_cells[x].attrs;
return PyLong_FromUnsignedLong((unsigned long) (attrs & WIDTH_MASK));
}
void
line_add_combining_char(Line *self, uint32_t ch, unsigned int x) {
Cell *cell = self->cells + x;
CPUCell *cell = self->cpu_cells + x;
if (!cell->ch) return; // dont allow adding combining chars to a null cell
for (unsigned i = 0; i < arraysz(cell->cc_idx); i++) {
if (!cell->cc_idx[i]) { cell->cc_idx[i] = mark_for_codepoint(ch); return; }
@ -349,12 +350,12 @@ set_text(Line* self, PyObject *args) {
color_type dfg = cursor->decoration_fg & COL_MASK;
for (index_type i = cursor->x; offset < limit && i < self->xnum; i++, offset++) {
self->cells[i].ch = (PyUnicode_READ(kind, buf, offset));
self->cells[i].attrs = attrs;
self->cells[i].fg = fg;
self->cells[i].bg = bg;
self->cells[i].decoration_fg = dfg;
memset(self->cells[i].cc_idx, 0, sizeof(self->cells[i].cc_idx));
self->cpu_cells[i].ch = (PyUnicode_READ(kind, buf, offset));
self->gpu_cells[i].attrs = attrs;
self->gpu_cells[i].fg = fg;
self->gpu_cells[i].bg = bg;
self->gpu_cells[i].decoration_fg = dfg;
memset(self->cpu_cells[i].cc_idx, 0, sizeof(self->cpu_cells[i].cc_idx));
}
Py_RETURN_NONE;
@ -373,10 +374,10 @@ cursor_from(Line* self, PyObject *args) {
ans = alloc_cursor();
if (ans == NULL) { PyErr_NoMemory(); return NULL; }
ans->x = x; ans->y = y;
char_type attrs = self->cells[x].attrs;
char_type attrs = self->gpu_cells[x].attrs;
ATTRS_TO_CURSOR(attrs, ans);
ans->fg = self->cells[x].fg; ans->bg = self->cells[x].bg;
ans->decoration_fg = self->cells[x].decoration_fg & COL_MASK;
ans->fg = self->gpu_cells[x].fg; ans->bg = self->gpu_cells[x].bg;
ans->decoration_fg = self->gpu_cells[x].decoration_fg & COL_MASK;
return (PyObject*)ans;
}
@ -386,8 +387,8 @@ line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch) {
attrs_type width = ch ? 1 : 0;
#define PREFIX \
for (index_type i = at; i < MIN(self->xnum, at + num); i++) { \
self->cells[i].ch = ch; memset(self->cells[i].cc_idx, 0, sizeof(self->cells[i].cc_idx)); \
self->cells[i].attrs = (self->cells[i].attrs & ATTRS_MASK_WITHOUT_WIDTH) | width; \
self->cpu_cells[i].ch = ch; memset(self->cpu_cells[i].cc_idx, 0, sizeof(self->cpu_cells[i].cc_idx)); \
self->gpu_cells[i].attrs = (self->gpu_cells[i].attrs & ATTRS_MASK_WITHOUT_WIDTH) | width; \
}
if (CHAR_IS_BLANK(ch)) {
PREFIX
@ -415,16 +416,16 @@ line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num,
for (index_type i = at; i < self->xnum && i < at + num; i++) {
if (clear_char) {
self->cells[i].ch = BLANK_CHAR;
memset(self->cells[i].cc_idx, 0, sizeof(self->cells[i].cc_idx));
self->cells[i].attrs = attrs;
clear_sprite_position(self->cells[i]);
self->cpu_cells[i].ch = BLANK_CHAR;
memset(self->cpu_cells[i].cc_idx, 0, sizeof(self->cpu_cells[i].cc_idx));
self->gpu_cells[i].attrs = attrs;
clear_sprite_position(self->gpu_cells[i]);
} else {
attrs_type w = self->cells[i].attrs & WIDTH_MASK;
self->cells[i].attrs = attrs | w;
attrs_type w = self->gpu_cells[i].attrs & WIDTH_MASK;
self->gpu_cells[i].attrs = attrs | w;
}
self->cells[i].fg = fg; self->cells[i].bg = bg;
self->cells[i].decoration_fg = dfg;
self->gpu_cells[i].fg = fg; self->gpu_cells[i].bg = bg;
self->gpu_cells[i].decoration_fg = dfg;
}
}
@ -444,11 +445,11 @@ void line_right_shift(Line *self, unsigned int at, unsigned int num) {
COPY_SELF_CELL(i - num, i)
}
// Check if a wide character was split at the right edge
char_type w = (self->cells[self->xnum - 1].attrs) & WIDTH_MASK;
char_type w = (self->gpu_cells[self->xnum - 1].attrs) & WIDTH_MASK;
if (w != 1) {
self->cells[self->xnum - 1].ch = BLANK_CHAR;
self->cells[self->xnum - 1].attrs = BLANK_CHAR ? 1 : 0;
clear_sprite_position(self->cells[self->xnum - 1]);
self->cpu_cells[self->xnum - 1].ch = BLANK_CHAR;
self->gpu_cells[self->xnum - 1].attrs = BLANK_CHAR ? 1 : 0;
clear_sprite_position(self->gpu_cells[self->xnum - 1]);
}
}
@ -483,15 +484,15 @@ left_shift(Line *self, PyObject *args) {
void
line_set_char(Line *self, unsigned int at, uint32_t ch, unsigned int width, Cursor *cursor, bool UNUSED is_second) {
if (cursor == NULL) {
self->cells[at].attrs = (self->cells[at].attrs & ATTRS_MASK_WITHOUT_WIDTH) | width;
self->gpu_cells[at].attrs = (self->gpu_cells[at].attrs & ATTRS_MASK_WITHOUT_WIDTH) | width;
} else {
self->cells[at].attrs = CURSOR_TO_ATTRS(cursor, width & WIDTH_MASK);
self->cells[at].fg = (cursor->fg & COL_MASK);
self->cells[at].bg = (cursor->bg & COL_MASK);
self->cells[at].decoration_fg = cursor->decoration_fg & COL_MASK;
self->gpu_cells[at].attrs = CURSOR_TO_ATTRS(cursor, width & WIDTH_MASK);
self->gpu_cells[at].fg = (cursor->fg & COL_MASK);
self->gpu_cells[at].bg = (cursor->bg & COL_MASK);
self->gpu_cells[at].decoration_fg = cursor->decoration_fg & COL_MASK;
}
self->cells[at].ch = ch;
memset(self->cells[at].cc_idx, 0, sizeof(self->cells[at].cc_idx));
self->cpu_cells[at].ch = ch;
memset(self->cpu_cells[at].cc_idx, 0, sizeof(self->cpu_cells[at].cc_idx));
}
static PyObject*
@ -516,7 +517,7 @@ set_attribute(Line *self, PyObject *args) {
unsigned int shift, val;
if (!PyArg_ParseTuple(args, "II", &shift, &val)) return NULL;
if (shift < DECORATION_SHIFT || shift > DIM_SHIFT) { PyErr_SetString(PyExc_ValueError, "Unknown attribute"); return NULL; }
set_attribute_on_line(self->cells, shift, val, self->xnum);
set_attribute_on_line(self->gpu_cells, shift, val, self->xnum);
Py_RETURN_NONE;
}
@ -525,8 +526,9 @@ __len__(PyObject *self) {
return (Py_ssize_t)(((Line*)self)->xnum);
}
static int __eq__(Line *a, Line *b) {
return a->xnum == b->xnum && memcmp(a->cells, b->cells, sizeof(Cell) * a->xnum) == 0;
static int
__eq__(Line *a, Line *b) {
return a->xnum == b->xnum && memcmp(a->cpu_cells, b->cpu_cells, sizeof(CPUCell) * a->xnum) == 0 && memcmp(a->gpu_cells, b->gpu_cells, sizeof(GPUCell) * a->xnum) == 0;
}
// Boilerplate {{{

View File

@ -9,7 +9,7 @@
#include "data-types.h"
static inline void
set_attribute_on_line(Cell *cells, uint32_t shift, uint32_t val, index_type xnum) {
set_attribute_on_line(GPUCell *cells, uint32_t shift, uint32_t val, index_type xnum) {
// Set a single attribute on all cells in the line
attrs_type mask = shift == DECORATION_SHIFT ? 3 : 1;
attrs_type aval = (val & mask) << shift;
@ -17,21 +17,18 @@ set_attribute_on_line(Cell *cells, uint32_t shift, uint32_t val, index_type xnum
for (index_type i = 0; i < xnum; i++) cells[i].attrs = (cells[i].attrs & mask) | aval;
}
static inline void
copy_cells(const Cell *src, Cell *dest, index_type xnum) {
memcpy(dest, src, sizeof(Cell) * xnum);
}
static inline void
copy_line(const Line *src, Line *dest) {
copy_cells(src->cells, dest->cells, MIN(src->xnum, dest->xnum));
memcpy(dest->cpu_cells, src->cpu_cells, sizeof(CPUCell) * MIN(src->xnum, dest->xnum));
memcpy(dest->gpu_cells, src->gpu_cells, sizeof(GPUCell) * MIN(src->xnum, dest->xnum));
}
static inline void
clear_chars_in_line(Cell *cells, index_type xnum, char_type ch) {
clear_chars_in_line(CPUCell *cpu_cells, GPUCell *gpu_cells, index_type xnum, char_type ch) {
// Clear only the char part of each cell, the rest must have been cleared by a memset or similar
if (ch) {
for (index_type i = 0; i < xnum; i++) { cells[i].ch = ch; cells[i].attrs = 1; }
for (index_type i = 0; i < xnum; i++) { cpu_cells[i].ch = ch; gpu_cells[i].attrs = 1; }
}
}
@ -39,7 +36,7 @@ static inline index_type
xlimit_for_line(Line *line) {
index_type xlimit = line->xnum;
if (BLANK_CHAR == 0) {
while (xlimit > 0 && (line->cells[xlimit - 1].ch) == BLANK_CHAR) xlimit--;
while (xlimit > 0 && (line->cpu_cells[xlimit - 1].ch) == BLANK_CHAR) xlimit--;
}
return xlimit;
}
@ -54,8 +51,8 @@ index_type line_url_start_at(Line *self, index_type x);
index_type line_url_end_at(Line *self, index_type x, bool);
index_type line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen);
unsigned int line_length(Line *self);
size_t cell_as_unicode(Cell *cell, bool include_cc, Py_UCS4 *buf, char_type);
size_t cell_as_utf8(Cell *cell, bool include_cc, char *buf, char_type);
size_t cell_as_unicode(CPUCell *cell, bool include_cc, Py_UCS4 *buf, char_type);
size_t cell_as_utf8(CPUCell *cell, bool include_cc, char *buf, char_type);
PyObject* unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char);
PyObject* line_as_unicode(Line *);

View File

@ -43,7 +43,8 @@
static inline void
copy_range(Line *src, index_type src_at, Line* dest, index_type dest_at, index_type num) {
memcpy(dest->cells + dest_at, src->cells + src_at, num * sizeof(Cell));
memcpy(dest->cpu_cells + dest_at, src->cpu_cells + src_at, num * sizeof(CPUCell));
memcpy(dest->gpu_cells + dest_at, src->gpu_cells + src_at, num * sizeof(GPUCell));
}
@ -60,7 +61,7 @@ rewrap_inner(BufType *src, BufType *dest, const index_type src_limit, HistoryBuf
src_x_limit = src->xnum;
if (!src_line_is_continued) {
// Trim trailing blanks since there is a hard line break at the end of this line
while(src_x_limit && (src->line->cells[src_x_limit - 1].ch) == BLANK_CHAR) src_x_limit--;
while(src_x_limit && (src->line->cpu_cells[src_x_limit - 1].ch) == BLANK_CHAR) src_x_limit--;
}
if (is_tracked_line && *track_x >= src_x_limit) *track_x = MAX(1, src_x_limit) - 1;

View File

@ -280,9 +280,10 @@ screen_designate_charset(Screen *self, uint32_t which, uint32_t as) {
}
static inline void
move_widened_char(Screen *self, Cell* cell, index_type xpos, index_type ypos) {
move_widened_char(Screen *self, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type xpos, index_type ypos) {
self->cursor->x = xpos; self->cursor->y = ypos;
Cell src = *cell, *dest;
CPUCell src_cpu = *cpu_cell, *dest_cpu;
GPUCell src_gpu = *gpu_cell, *dest_gpu;
line_clear_text(self->linebuf->line, xpos, 1, BLANK_CHAR);
if (self->modes.mDECAWM) { // overflow goes onto next line
@ -290,14 +291,17 @@ move_widened_char(Screen *self, Cell* cell, index_type xpos, index_type ypos) {
screen_linefeed(self);
self->linebuf->line_attrs[self->cursor->y] |= CONTINUED_MASK;
linebuf_init_line(self->linebuf, self->cursor->y);
dest = self->linebuf->line->cells;
dest_cpu = self->linebuf->line->cpu_cells;
dest_gpu = self->linebuf->line->gpu_cells;
self->cursor->x = MIN(2, self->columns);
linebuf_mark_line_dirty(self->linebuf, self->cursor->y);
} else {
dest = cell - 1;
dest_cpu = cpu_cell - 1;
dest_gpu = gpu_cell - 1;
self->cursor->x = self->columns;
}
*dest = src;
*dest_cpu = src_cpu;
*dest_gpu = src_gpu;
}
static inline void
@ -320,10 +324,11 @@ draw_combining_char(Screen *self, char_type ch) {
self->is_dirty = true;
linebuf_mark_line_dirty(self->linebuf, ypos);
if (ch == 0xfe0f) { // emoji presentation variation marker makes default text presentation emoji (narrow emoji) into wide emoji
Cell *cell = self->linebuf->line->cells + xpos;
if ((cell->attrs & WIDTH_MASK) != 2 && cell->cc_idx[0] == VS16 && is_emoji_presentation_base(cell->ch)) {
cell->attrs = (cell->attrs & !WIDTH_MASK) | 2;
if (xpos == self->columns - 1) move_widened_char(self, cell, xpos, ypos);
CPUCell *cpu_cell = self->linebuf->line->cpu_cells + xpos;
GPUCell *gpu_cell = self->linebuf->line->gpu_cells + xpos;
if ((gpu_cell->attrs & WIDTH_MASK) != 2 && cpu_cell->cc_idx[0] == VS16 && is_emoji_presentation_base(cpu_cell->ch)) {
gpu_cell->attrs = (gpu_cell->attrs & !WIDTH_MASK) | 2;
if (xpos == self->columns - 1) move_widened_char(self, cpu_cell, gpu_cell, xpos, ypos);
else self->cursor->x++;
}
}
@ -409,7 +414,7 @@ select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count,
num = MIN(num, self->columns - x);
for (index_type y = region.top; y < MIN(region.bottom + 1, self->lines); y++) {
linebuf_init_line(self->linebuf, y);
apply_sgr_to_cells(self->linebuf->line->cells + x, num, params, count);
apply_sgr_to_cells(self->linebuf->line->gpu_cells + x, num, params, count);
}
} else {
index_type x, num;
@ -418,7 +423,7 @@ select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count,
else if (y == region.bottom) { x = 0; num = MIN(region.right + 1, self->columns); }
else { x = 0; num = self->columns; }
linebuf_init_line(self->linebuf, y);
apply_sgr_to_cells(self->linebuf->line->cells + x, num, params, count);
apply_sgr_to_cells(self->linebuf->line->gpu_cells + x, num, params, count);
}
}
} else cursor_from_sgr(self->cursor, params, count);
@ -1280,8 +1285,8 @@ screen_request_capabilities(Screen *self, char c, PyObject *q) {
// Rendering {{{
static inline void
update_line_data(Line *line, unsigned int dest_y, uint8_t *data) {
size_t base = dest_y * line->xnum * sizeof(Cell);
memcpy(data + base, line->cells, line->xnum * sizeof(Cell));
size_t base = dest_y * line->xnum * sizeof(GPUCell);
memcpy(data + base, line->gpu_cells, line->xnum * sizeof(GPUCell));
}
@ -1718,8 +1723,8 @@ screen_selection_range_for_line(Screen *self, index_type y, index_type *start, i
if (y >= self->lines) { return false; }
Line *line = visual_line_(self, y);
index_type xlimit = line->xnum, xstart = 0;
while (xlimit > 0 && CHAR_IS_BLANK(line->cells[xlimit - 1].ch)) xlimit--;
while (xstart < xlimit && CHAR_IS_BLANK(line->cells[xstart].ch)) xstart++;
while (xlimit > 0 && CHAR_IS_BLANK(line->cpu_cells[xlimit - 1].ch)) xlimit--;
while (xstart < xlimit && CHAR_IS_BLANK(line->cpu_cells[xstart].ch)) xstart++;
*start = xstart; *end = xlimit;
return true;
}
@ -1738,7 +1743,7 @@ screen_selection_range_for_word(Screen *self, index_type x, index_type *y1, inde
index_type start, end;
Line *line = visual_line_(self, *y1);
*y2 = *y1;
#define is_ok(x) (is_word_char((line->cells[x].ch)) || is_opt_word_char(line->cells[x].ch))
#define is_ok(x) (is_word_char((line->cpu_cells[x].ch)) || is_opt_word_char(line->cpu_cells[x].ch))
if (!is_ok(x)) {
start = x; end = x + 1;
} else {

View File

@ -183,7 +183,7 @@ create_cell_vao() {
#define A(name, size, dtype, offset, stride) \
add_attribute_to_vao(CELL_PROGRAM, vao_idx, #name, \
/*size=*/size, /*dtype=*/dtype, /*stride=*/stride, /*offset=*/offset, /*divisor=*/1);
#define A1(name, size, dtype, offset) A(name, size, dtype, (void*)(offsetof(Cell, offset)), sizeof(Cell))
#define A1(name, size, dtype, offset) A(name, size, dtype, (void*)(offsetof(GPUCell, offset)), sizeof(GPUCell))
add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER);
A1(sprite_coords, 4, GL_UNSIGNED_SHORT, sprite_x);
@ -267,7 +267,7 @@ cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloa
ensure_sprite_map(fonts_data);
if (screen->scroll_changed || screen->is_dirty) {
sz = sizeof(Cell) * screen->lines * screen->columns;
sz = sizeof(GPUCell) * screen->lines * screen->columns;
address = alloc_and_map_vao_buffer(vao_idx, sz, cell_data_buffer, GL_STREAM_DRAW, GL_WRITE_ONLY);
screen_update_cell_data(screen, address, sz, fonts_data);
unmap_vao_buffer(vao_idx, cell_data_buffer); address = NULL;