diff --git a/kitty/boss.py b/kitty/boss.py index 11436b156..5cfb3ee5d 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -235,8 +235,7 @@ class Boss(Thread): def apply_update_screen(self): self.pending_update_screen = None - changes = self.screen.consolidate_changes() - self.char_grid.update_cell_data(changes) + self.char_grid.update_cell_data() glfw.glfwPostEmptyEvent() def title_changed(self, new_title): diff --git a/kitty/char_grid.py b/kitty/char_grid.py index 982b54e0a..671d16340 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -141,6 +141,10 @@ class RenderData: empty_cell = (' ', 0) +def color_as_int(val): + return val[0] << 16 | val[1] << 8 | val[2] + + class CharGrid: def __init__(self, screen, opts, window_width, window_height): @@ -161,7 +165,7 @@ class CharGrid: self.last_render_data = RenderData() self.default_cursor = Cursor(0, 0, False, opts.cursor_shape, opts.cursor, opts.cursor_blink) self.render_queue.put(RenderData( - viewport=Size(self.width, self.height), clear_color=self.original_bg, + viewport=Size(self.width, self.height), clear_color=color_as_int(self.original_bg), cursor=self.default_cursor)) self.sprites.ensure_state() self.clear_count = 4 @@ -170,7 +174,8 @@ class CharGrid: self.sprites.destroy() def initialize(self): - self.default_bg, self.default_fg = self.original_bg, self.original_fg + self.default_bg = color_as_int(self.original_bg) + self.default_fg = color_as_int(self.original_fg) self.apply_opts(self.opts) def apply_opts(self, opts): @@ -202,51 +207,30 @@ class CharGrid: for which, val in changes.items(): if which in ('fg', 'bg'): if not val: - setattr(self, 'default_' + which, getattr(self, 'original_' + which)) + setattr(self, 'default_' + which, color_as_int(getattr(self, 'original_' + which))) dirtied = True else: val = to_color(val) if val is not None: - setattr(self, 'default_' + which, val) + setattr(self, 'default_' + which, color_as_int(val)) dirtied = True if dirtied: self.render_queue.put(RenderData(clear_color=self.default_bg)) self.clear_count = 4 - def update_cell_data(self, changes=None, add_viewport_data=False): + def update_cell_data(self, add_viewport_data=False): rd = RenderData(sprite_layout=self.sprites.layout) if add_viewport_data: rd.viewport = Size(self.width, self.height) rd.screen_geometry = self.screen_geometry - if changes is None: - changes = {'screen': True} - sg = self.screen_geometry - cell_data_changed = changes['screen'] or changes['cells'] or changes['lines'] - if cell_data_changed: - if changes['screen']: - lines = range(sg.ynum) - cell_ranges = {} - else: - lines = changes['lines'] - cell_ranges = changes['cells'] + ptr = addressof(self.sprite_map) + with self.lock: + cursor_changed = self.screen.update_cell_data( + self.sprites.backend, self.color_profile, ptr, self.default_fg, self.default_bg, add_viewport_data) - dfbg = self.default_bg - dffg = self.default_fg - dfbg = dfbg[0] << 16 | dfbg[1] << 8 | dfbg[2] - dffg = dffg[0] << 16 | dffg[1] << 8 | dffg[2] - ptr = addressof(self.sprite_map) - - with self.lock: - for y in lines: - self.update_line(y, [(0, sg.xnum - 1)], dffg, dfbg, ptr) - - for y, ranges in cell_ranges.items(): - self.update_line(y, ranges, dffg, dfbg, ptr) - - rd.cell_data = copy(self.sprite_map) - rd.sprite_layout = self.sprites.layout - c = changes.get('cursor') - if c: + rd.cell_data = copy(self.sprite_map) + rd.sprite_layout = self.sprites.layout + if cursor_changed: c = self.screen.cursor rd.cursor = Cursor(c.x, c.y, c.hidden, c.shape, c.color, c.blink) self.render_queue.put(rd) @@ -287,7 +271,7 @@ class CharGrid: cell_data_changed |= rd.cell_data is not None if rd.clear_color is not None: bg = rd.clear_color - glClearColor(bg[0] / 255, bg[1] / 255, bg[2] / 255, 1) + glClearColor((bg >> 16) / 255, ((bg >> 8) & 0xff) / 255, (bg & 0xff) / 255, 1) if rd.viewport is not None: glViewport(0, 0, self.width, self.height) data.update(rd) diff --git a/kitty/line-buf.c b/kitty/line-buf.c index 7f314d0c4..48b049bcb 100644 --- a/kitty/line-buf.c +++ b/kitty/line-buf.c @@ -90,6 +90,13 @@ dealloc(LineBuf* self) { (l)->decoration_fg = (lb)->decoration_fg + (ynum) * (lb)->xnum; \ (l)->combining_chars = (lb)->combining_chars + (ynum) * (lb)->xnum; +void linebuf_init_line(LineBuf *self, index_type idx) { + self->line->ynum = idx; + self->line->xnum = self->xnum; + self->line->continued = self->continued_map[idx]; + INIT_LINE(self, self->line, self->line_map[idx]); +} + static PyObject* line(LineBuf *self, PyObject *y) { #define line_doc "Return the specified line as a Line object. Note the Line Object is a live view into the underlying buffer. And only a single line object can be used at a time." @@ -98,10 +105,7 @@ line(LineBuf *self, PyObject *y) { PyErr_SetString(PyExc_IndexError, "Line number too large"); return NULL; } - self->line->ynum = idx; - self->line->xnum = self->xnum; - self->line->continued = self->continued_map[idx]; - INIT_LINE(self, self->line, self->line_map[idx]); + linebuf_init_line(self, idx); Py_INCREF(self->line); return (PyObject*)self->line; } @@ -351,7 +355,7 @@ static PyMemberDef members[] = { {NULL} /* Sentinel */ }; -static PyTypeObject LineBuf_Type = { +PyTypeObject LineBuf_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.LineBuf", .tp_basicsize = sizeof(LineBuf), diff --git a/kitty/screen.py b/kitty/screen.py index 7c4e02af0..4147026f9 100644 --- a/kitty/screen.py +++ b/kitty/screen.py @@ -87,6 +87,9 @@ class Screen: def line(self, i): return self.linebuf.line(i) + def update_cell_data(self, *a): + return self.tracker.update_cell_data(self.linebuf, *a) + def __repr__(self): return ("{0}({1}, {2})".format(self.__class__.__name__, self.columns, self.lines)) diff --git a/kitty/shaders.py b/kitty/shaders.py index 5b5add8d8..ed57363b8 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -46,12 +46,10 @@ class Sprites: self.texture_id = self.buffer_id = self.buffer_texture_id = None self.last_num_of_layers = 1 self.last_ynum = -1 - self.update_cell_data = lambda *a: None def initialize(self): self.texture_unit = GL_TEXTURE0 self.backend = SpriteMap(glGetIntegerv(GL_MAX_TEXTURE_SIZE), glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS)) - self.update_cell_data = self.backend.update_cell_data self.do_layout(getattr(self, 'cell_width', 1), getattr(self, 'cell_height', 1)) def do_layout(self, cell_width=1, cell_height=1): diff --git a/kitty/sprites.c b/kitty/sprites.c index 22d34c071..477a491eb 100644 --- a/kitty/sprites.c +++ b/kitty/sprites.c @@ -133,32 +133,22 @@ position_for(SpriteMap *self, PyObject *args) { } -static PyObject* -update_cell_data(SpriteMap *self, PyObject *args) { +bool +update_cell_range_data(SpriteMap *self, Line *line, unsigned int xstart, unsigned int xmax, ColorProfile *color_profile, const uint32_t default_bg, const uint32_t default_fg, unsigned int *data) { #define update_cell_data_doc "update_cell_data(line, xstart, xmax, color_profile, default_bg, default_fg, data_pointer) -> Update the range [xstart, xmax] in data_pointer with the data from line" - Line *line; - unsigned int xstart, xlimit; SpritePosition *sp; - PyObject *dp; char_type previous_ch=0, ch; color_type color; uint32_t bg, fg; uint8_t previous_width = 0; - ColorProfile *color_profile; - unsigned long default_bg, default_fg; int err = 0; - if (!PyArg_ParseTuple(args, "O!IIO!kkO!", &Line_Type, &line, &xstart, &xlimit, &ColorProfile_Type, &color_profile, &default_bg, &default_fg, &PyLong_Type, &dp)) return NULL; - - unsigned int *data = PyLong_AsVoidPtr(dp); size_t base = line->ynum * line->xnum * 9; - default_fg &= COL_MASK; - default_bg &= COL_MASK; - for (size_t i = xstart, offset = base + xstart * 9; i <= xlimit; i++, offset += 9) { + for (size_t i = xstart, offset = base + xstart * 9; i <= xmax; i++, offset += 9) { ch = line->chars[i]; if (previous_width == 2) sp = sprite_position_for(self, previous_ch, 0, true, &err); else sp = sprite_position_for(self, ch, line->combining_chars[i], false, &err); - if (sp == NULL) { set_sprite_error(err); return NULL; } + if (sp == NULL) { set_sprite_error(err); return false; } data[offset] = sp->x; data[offset+1] = sp->y; data[offset+2] = sp->z; @@ -170,8 +160,7 @@ update_cell_data(SpriteMap *self, PyObject *args) { PACK_COL(offset + 3, fg); PACK_COL(offset + 6, bg); } - - Py_RETURN_NONE; + return true; } static PyObject* @@ -222,13 +211,12 @@ static PyMemberDef members[] = { static PyMethodDef methods[] = { METHOD(layout, METH_VARARGS) METHOD(position_for, METH_VARARGS) - METHOD(update_cell_data, METH_VARARGS) METHOD(render_dirty_cells, METH_VARARGS) {NULL} /* Sentinel */ }; -static PyTypeObject SpriteMap_Type = { +PyTypeObject SpriteMap_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "fast_data_types.SpriteMap", .tp_basicsize = sizeof(SpriteMap), diff --git a/kitty/tracker.c b/kitty/tracker.c index a8855bd16..b553f4f6f 100644 --- a/kitty/tracker.c +++ b/kitty/tracker.c @@ -7,7 +7,11 @@ #include "data-types.h" #include "tracker.h" -#include +extern PyTypeObject SpriteMap_Type; +extern PyTypeObject ColorProfile_Type; +extern PyTypeObject LineBuf_Type; +extern bool update_cell_range_data(SpriteMap *, Line *, unsigned int, unsigned int, ColorProfile *, const uint32_t, const uint32_t, unsigned int *); +extern void linebuf_init_line(LineBuf *, index_type); #define RESET_STATE_VARS(self) \ self->screen_changed = false; self->cursor_changed = false; self->dirty = false; self->history_line_added_count = 0; @@ -104,6 +108,62 @@ update_cell_range(ChangeTracker *self, PyObject *args) { Py_RETURN_NONE; } +static PyObject* +update_cell_data(ChangeTracker *self, PyObject *args) { +#define update_cell_data_doc "update_cell_data(line_buf, sprite_map, color_profile, data_ptr, default_fg, default_bg, force_screen_refresh)" + SpriteMap *spm; + LineBuf *lb; + ColorProfile *color_profile; + PyObject *dp; + unsigned int *data, y; + unsigned long default_bg, default_fg; + Py_ssize_t start; + int force_screen_refresh; + if (!PyArg_ParseTuple(args, "O!O!O!O!kkp", &LineBuf_Type, &lb, &SpriteMap_Type, &spm, &ColorProfile_Type, &color_profile, &PyLong_Type, &dp, &default_fg, &default_bg, &force_screen_refresh)) return NULL; + data = PyLong_AsVoidPtr(dp); + default_fg &= COL_MASK; + default_bg &= COL_MASK; + +#define UPDATE_RANGE(xstart, xmax) \ + linebuf_init_line(lb, y); \ + if (!update_cell_range_data(spm, lb->line, (xstart), (xmax), color_profile, default_bg, default_fg, data)) return NULL; + + if (self->screen_changed || force_screen_refresh) { + for (y = 0; y < self->ynum; y++) { + UPDATE_RANGE(0, self->xnum - 1); + } + } else { + for (y = 0; y < self->ynum; y++) { + if (self->changed_lines[y]) { + UPDATE_RANGE(0, self->xnum - 1); + } else if (self->lines_with_changed_cells[y]) { + start = -1; + bool *line = self->changed_cells + y * self->xnum; + for (unsigned int i = 0; i < self->xnum; i++) { + if (line[i]) { + if (start == -1) { + start = i; + } + } else { + if (start != -1) { + UPDATE_RANGE(start, i - 1); + start = -1; + } + } + } + if (start != -1) { + UPDATE_RANGE(start, self->xnum - 1); + } + } + } + } + + PyObject *cursor_changed = self->cursor_changed ? Py_True : Py_False; + reset_inner(self); + Py_INCREF(cursor_changed); + return cursor_changed; +} + static inline PyObject* get_ranges(bool *line, unsigned int xnum) { PyObject *ans = PyList_New(0), *t; @@ -199,6 +259,7 @@ static PyGetSetDef getseters[] = { static PyMethodDef methods[] = { METHOD(resize, METH_VARARGS) + METHOD(update_cell_data, METH_VARARGS) METHOD(reset, METH_NOARGS) METHOD(cursor_changed, METH_NOARGS) METHOD(consolidate_changes, METH_NOARGS)