diff --git a/kitty/borders.py b/kitty/borders.py index 56e7853b8..4a226179c 100644 --- a/kitty/borders.py +++ b/kitty/borders.py @@ -10,8 +10,7 @@ from .fast_data_types import ( BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program, send_borders_rects ) -from .shaders import load_shaders -from .utils import color_as_int, pt_to_px +from .utils import color_as_int, pt_to_px, load_shaders def vertical_edge(color, width, top, bottom, left): diff --git a/kitty/boss.py b/kitty/boss.py index 1048e89df..06e61163f 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -115,11 +115,11 @@ class Boss: glfw_window.scroll_callback = self.on_mouse_scroll glfw_window.cursor_pos_callback = self.on_mouse_move glfw_window.window_focus_callback = self.on_focus + load_shader_programs() self.tab_manager = TabManager(opts, args) self.tab_manager.init(startup_session) self.sprites = Sprites() self.sprites.do_layout(cell_size.width, cell_size.height) - self.cell_program = load_shader_programs() glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) self.glfw_window.set_click_cursor(False) self.show_mouse_cursor() @@ -376,11 +376,10 @@ class Boss: with self.sprites: self.sprites.render_dirty_sprites() draw_borders() - with self.cell_program: - self.tab_manager.render(self.cell_program, self.sprites) - for window in tab.visible_windows(): - if not window.needs_layout: - window.render_cells(self.cell_program, self.sprites) + self.tab_manager.render() + for window in tab.visible_windows(): + if not window.needs_layout: + window.render_cells() active = self.active_window if active is not None: draw_cursor = True @@ -396,7 +395,7 @@ class Boss: active.char_grid.render_cursor(self.window_is_focused) def gui_close_window(self, window): - window.char_grid.destroy(self.cell_program) + window.char_grid.destroy() for tab in self.tab_manager: if window in tab: break diff --git a/kitty/char_grid.py b/kitty/char_grid.py index f04c6814a..01831cb3d 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -4,24 +4,18 @@ import re from collections import namedtuple -from ctypes import sizeof from enum import Enum from .config import build_ansi_color_table -from .constants import ( - GLfloat, GLuint, ScreenGeometry, cell_size, viewport_size -) +from .constants import ScreenGeometry, cell_size, viewport_size from .fast_data_types import ( - CELL, CELL_PROGRAM, CURSOR_BEAM, CURSOR_BLOCK, CURSOR_PROGRAM, - CURSOR_UNDERLINE, GL_FLOAT, GL_STATIC_DRAW, GL_TRIANGLE_FAN, - GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, compile_program, draw_cursor, - glDrawArraysInstanced, glUniform1i, glUniform2f, glUniform2i, glUniform2ui, - glUniform4f, glUniform4ui, init_cursor_program + CELL_PROGRAM, CURSOR_BEAM, CURSOR_BLOCK, CURSOR_PROGRAM, CURSOR_UNDERLINE, + compile_program, create_cell_vao, draw_cells, draw_cursor, + init_cell_program, init_cursor_program, remove_vao ) from .rgb import to_color -from .shaders import ShaderProgram, load_shaders from .utils import ( - color_as_int, get_logical_dpi, open_url, + color_as_int, get_logical_dpi, load_shaders, open_url, set_primary_selection ) @@ -32,34 +26,11 @@ class DynamicColor(Enum): default_fg, default_bg, cursor_color, highlight_fg, highlight_bg = range(1, 6) -class CellProgram(ShaderProgram): # {{{ - - def send_color_table(self, color_profile): - if color_profile.ubo is None: - color_profile.ubo = self.init_uniform_block('ColorTable', 'color_table') - ubo = color_profile.ubo - offset = ubo.offsets['color_table'] // sizeof(GLuint) - stride = ubo.size // (256 * sizeof(GLuint)) - with self.mapped_uniform_data(ubo, usage=GL_STATIC_DRAW) as address: - color_profile.copy_color_table(address, offset, stride) - - def create_sprite_map(self): - with self.array_object_creator() as add_attribute: - stride = CELL['size'] - add_attribute('text_attrs', size=1, dtype=GL_UNSIGNED_INT, offset=CELL['ch'], stride=stride, divisor=1) - add_attribute('sprite_coords', size=3, dtype=GL_UNSIGNED_SHORT, offset=CELL['sprite_x'], stride=stride, divisor=1) - add_attribute('colors', size=3, dtype=GL_UNSIGNED_INT, stride=stride, offset=CELL['fg'], divisor=1) - add_attribute.newbuf() - add_attribute('is_selected', size=1, dtype=GL_FLOAT, stride=sizeof(GLfloat), divisor=1) - return add_attribute.vao_id - - def load_shader_programs(): - cell = CellProgram(CELL_PROGRAM, *load_shaders('cell')) + compile_program(CELL_PROGRAM, *load_shaders('cell')) + init_cell_program() compile_program(CURSOR_PROGRAM, *load_shaders('cursor')) init_cursor_program() - return cell -# }}} def calculate_gl_geometry(window_geometry, viewport_width, viewport_height, cell_width, cell_height): @@ -71,21 +42,8 @@ def calculate_gl_geometry(window_geometry, viewport_width, viewport_height, cell return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy) -def render_cells(vao_id, sg, cell_program, sprites, color_profile, invert_colors=False, screen_reversed=False): - if color_profile.dirty: - cell_program.send_color_table(color_profile) - color_profile.dirty = False - ul = cell_program.uniform_location - glUniform2ui(ul('dimensions'), sg.xnum, sg.ynum) - glUniform4ui(ul('default_colors'), color_profile.default_fg, color_profile.default_bg, color_profile.highlight_fg, color_profile.highlight_bg) - inverted = invert_colors or screen_reversed - glUniform2i(ul('color_indices'), 1 if inverted else 0, 0 if inverted else 1) - glUniform4f(ul('steps'), sg.xstart, sg.ystart, sg.dx, sg.dy) - glUniform1i(ul('sprites'), sprites.sampler_num) - glUniform1i(ul('screen_reversed'), 1 if screen_reversed else 0) - glUniform2f(ul('sprite_layout'), *(sprites.layout)) - with cell_program.bound_vertex_array(vao_id), cell_program.bound_uniform_buffer(color_profile.ubo): - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, sg.xnum * sg.ynum) +def render_cells(vao_id, sg, screen, invert_colors=False): + draw_cells(vao_id, sg.xstart, sg.ystart, sg.dx, sg.dy, invert_colors, screen) class CharGrid: @@ -93,23 +51,21 @@ class CharGrid: url_pat = re.compile('(?:http|https|file|ftp)://\S+', re.IGNORECASE) def __init__(self, screen, opts): - self.vao_id = None + self.vao_id = create_cell_vao() self.screen_reversed = False - self.data_buffer_size = None self.screen = screen self.opts = opts self.screen.color_profile.update_ansi_color_table(build_ansi_color_table(opts)) self.screen.color_profile.set_configured_colors(*map(color_as_int, ( opts.foreground, opts.background, opts.cursor, opts.selection_foreground, opts.selection_background))) - self.screen.color_profile.dirty = True self.dpix, self.dpiy = get_logical_dpi() self.opts = opts - self.default_cursor = self.current_cursor = Cursor(0, 0, opts.cursor_shape, opts.cursor_blink_interval > 0) + self.default_cursor = Cursor(0, 0, opts.cursor_shape, opts.cursor_blink_interval > 0) self.opts = opts - def destroy(self, cell_program): + def destroy(self): if self.vao_id is not None: - cell_program.remove_vertex_array(self.vao_id) + remove_vao(self.vao_id) self.vao_id = None def update_position(self, window_geometry): @@ -117,9 +73,6 @@ class CharGrid: def resize(self, window_geometry): self.update_position(window_geometry) - self.data_buffer_size = self.screen_geometry.ynum * self.screen_geometry.xnum * CELL['size'] - self.selection_buffer_size = self.screen_geometry.ynum * self.screen_geometry.xnum * sizeof(GLfloat) - self.screen.clear_selection() def change_colors(self, changes): dirtied = False @@ -139,16 +92,6 @@ class CharGrid: if dirtied: self.screen.mark_as_dirty() - def update_cell_data(self, cell_program): - if self.data_buffer_size is None: - return - with cell_program.mapped_vertex_data(self.vao_id, self.data_buffer_size) as address: - cursor_changed, self.screen_reversed = self.screen.update_cell_data( - address, False) - if cursor_changed: - c = self.screen.cursor - self.current_cursor = Cursor(c.x, c.y, c.shape, c.blink) - def cell_for_pos(self, x, y): x, y = int(x // cell_size.width), int(y // cell_size.height) if 0 <= x < self.screen.columns and 0 <= y < self.screen.lines: @@ -229,23 +172,15 @@ class CharGrid: def text_for_selection(self): return ''.join(self.screen.text_for_selection()) - def render_cells(self, cell_program, sprites, invert_colors=False): - if self.vao_id is None: - self.vao_id = cell_program.create_sprite_map() - if self.screen.scroll_changed or self.screen.is_dirty: - self.update_cell_data(cell_program) - if self.screen.is_selection_dirty(): - with cell_program.mapped_vertex_data(self.vao_id, self.selection_buffer_size, bufnum=1) as address: - self.screen.apply_selection(address, self.selection_buffer_size) + def render_cells(self, invert_colors=False): render_cells( - self.vao_id, self.screen_geometry, cell_program, sprites, - self.screen.color_profile, invert_colors=invert_colors, - screen_reversed=self.screen_reversed) + self.vao_id, self.screen_geometry, + self.screen, invert_colors=invert_colors) def render_cursor(self, is_focused): - cursor = self.current_cursor if not self.screen.cursor_visible or self.screen.scrolled_by: return + cursor = self.screen.cursor def width(w=2, vert=True): dpi = self.dpix if vert else self.dpiy diff --git a/kitty/colors.c b/kitty/colors.c index 867a8cc69..e160a59e8 100644 --- a/kitty/colors.c +++ b/kitty/colors.c @@ -60,16 +60,13 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) { if (FG_BG_256[255] == 0) create_256_color_table(); memcpy(self->color_table, FG_BG_256, sizeof(FG_BG_256)); memcpy(self->orig_color_table, FG_BG_256, sizeof(FG_BG_256)); - self->dirty = Py_True; Py_INCREF(Py_True); - self->ubo = Py_None; Py_INCREF(Py_None); + self->dirty = true; } return (PyObject*) self; } static void dealloc(ColorProfile* self) { - Py_DECREF(self->dirty); - Py_DECREF(self->ubo); Py_TYPE(self)->tp_free((PyObject*)self); } @@ -90,6 +87,7 @@ update_ansi_color_table(ColorProfile *self, PyObject *val) { self->color_table[i] = PyLong_AsUnsignedLong(PyList_GET_ITEM(val, i)); self->orig_color_table[i] = self->color_table[i]; } + self->dirty = true; Py_RETURN_NONE; } @@ -136,6 +134,7 @@ static PyObject* reset_color_table(ColorProfile *self) { #define reset_color_table_doc "Reset all customized colors back to defaults" memcpy(self->color_table, self->orig_color_table, sizeof(FG_BG_256)); + self->dirty = true; Py_RETURN_NONE; } @@ -144,6 +143,7 @@ reset_color(ColorProfile *self, PyObject *val) { #define reset_color_doc "Reset the specified color" uint8_t i = PyLong_AsUnsignedLong(val) & 0xff; self->color_table[i] = self->orig_color_table[i]; + self->dirty = true; Py_RETURN_NONE; } @@ -154,6 +154,7 @@ set_color(ColorProfile *self, PyObject *args) { unsigned long val; if (!PyArg_ParseTuple(args, "Bk", &i, &val)) return NULL; self->color_table[i] = val; + self->dirty = true; Py_RETURN_NONE; } @@ -161,21 +162,19 @@ static PyObject* set_configured_colors(ColorProfile *self, PyObject *args) { #define set_configured_colors_doc "Set the configured colors" if (!PyArg_ParseTuple(args, "II|III", &(self->configured.default_fg), &(self->configured.default_bg), &(self->configured.cursor_color), &(self->configured.highlight_fg), &(self->configured.highlight_bg))) return NULL; + self->dirty = true; Py_RETURN_NONE; } -static PyObject* -copy_color_table(ColorProfile *self, PyObject *args) { -#define copy_color_table_doc "copy_color_table(address, offset, stride) -> copy the color table into the memory at address, using the specified offset and stride." - unsigned int offset, stride, i; - PyObject *ptr; - if (!PyArg_ParseTuple(args, "OII", &ptr, &offset, &stride)) return NULL; - color_type *buf = (color_type*)PyLong_AsVoidPtr(ptr); +void +copy_color_table_to_buffer(ColorProfile *self, void *address, int offset, size_t stride) { + size_t i; + color_type *buf = address; stride = MAX(1, stride); for (i = 0, buf = buf + offset; i < sizeof(self->color_table)/sizeof(self->color_table[0]); i++, buf += stride) { *buf = self->color_table[i]; } - Py_RETURN_NONE; + self->dirty = false; } static PyObject* @@ -189,7 +188,7 @@ color_table_address(ColorProfile *self) { #define CGETSET(name) \ static PyObject* name##_get(ColorProfile *self, void UNUSED *closure) { return PyLong_FromUnsignedLong(colorprofile_to_color(self, self->overridden.name, self->configured.name)); } \ - static int name##_set(ColorProfile *self, PyObject *val, void UNUSED *closure) { if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } self->overridden.name = (color_type) PyLong_AsUnsignedLong(val); return 0; } + static int name##_set(ColorProfile *self, PyObject *val, void UNUSED *closure) { if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } self->overridden.name = (color_type) PyLong_AsUnsignedLong(val); self->dirty = true; return 0; } CGETSET(default_fg) CGETSET(default_bg) @@ -208,8 +207,6 @@ static PyGetSetDef getsetters[] = { static PyMemberDef members[] = { - {"dirty", T_OBJECT_EX, offsetof(ColorProfile, dirty), 0, "dirty"}, - {"ubo", T_OBJECT_EX, offsetof(ColorProfile, ubo), 0, "ubo"}, {NULL} }; @@ -221,7 +218,6 @@ static PyMethodDef methods[] = { METHOD(reset_color, METH_O) METHOD(set_color, METH_VARARGS) METHOD(set_configured_colors, METH_VARARGS) - METHOD(copy_color_table, METH_VARARGS) {NULL} /* Sentinel */ }; diff --git a/kitty/data-types.h b/kitty/data-types.h index c55fd9db6..44b542a5e 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -187,7 +187,7 @@ typedef struct { typedef struct { PyObject_HEAD - PyObject *dirty, *ubo; + bool dirty; uint32_t color_table[256]; uint32_t orig_color_table[256]; DynamicColor configured, overridden; @@ -237,10 +237,10 @@ typedef struct { uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset; Selection selection; SelectionBoundary last_rendered_selection_start, last_rendered_selection_end; - bool use_latin1, selection_updated_once; + bool use_latin1, selection_updated_once, is_dirty, scroll_changed; Cursor *cursor; SavepointBuffer main_savepoints, alt_savepoints; - PyObject *callbacks, *is_dirty, *cursor_changed, *scroll_changed; + PyObject *callbacks; LineBuf *linebuf, *main_linebuf, *alt_linebuf; HistoryBuf *historybuf; unsigned int history_line_added_count; @@ -326,6 +326,8 @@ PyObject* cm_thread_write(PyObject *self, PyObject *args); bool set_iutf8(int, bool); color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval); +void copy_color_table_to_buffer(ColorProfile *self, void *address, int offset, size_t stride); +void sprite_map_current_layout(unsigned int *x, unsigned int *y, unsigned int*); unsigned int safe_wcwidth(uint32_t ch); void change_wcwidth(bool use9); diff --git a/kitty/screen.c b/kitty/screen.c index c71931760..0508feee7 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -67,8 +67,8 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { self->columns = columns; self->lines = lines; self->write_buf = NULL; self->modes = empty_modes; - self->cursor_changed = Py_True; self->is_dirty = Py_True; - self->scroll_changed = Py_False; + self->is_dirty = true; + self->scroll_changed = false; self->margin_top = 0; self->margin_bottom = self->lines - 1; self->history_line_added_count = 0; RESET_CHARSETS; @@ -104,8 +104,7 @@ screen_reset(Screen *self) { init_tabstops(self->main_tabstops, self->columns); init_tabstops(self->alt_tabstops, self->columns); cursor_reset(self->cursor); - self->cursor_changed = Py_True; - self->is_dirty = Py_True; + self->is_dirty = true; screen_cursor_position(self, 1, 1); set_dynamic_color(self, 110, NULL); set_dynamic_color(self, 111, NULL); @@ -170,7 +169,8 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) { self->tabstops = self->main_tabstops; init_tabstops(self->main_tabstops, self->columns); init_tabstops(self->alt_tabstops, self->columns); - self->cursor_changed = Py_True; self->is_dirty = Py_True; + self->is_dirty = true; + self->selection = EMPTY_SELECTION; if (index_after_resize) screen_index(self); return true; @@ -263,7 +263,6 @@ void screen_draw(Screen *self, uint32_t och) { if (is_ignored_char(och)) return; uint32_t ch = och < 256 ? self->g_charset[och] : och; - unsigned int x = self->cursor->x, y = self->cursor->y; unsigned int char_width = safe_wcwidth(ch); if (self->columns - self->cursor->x < char_width) { if (self->modes.mDECAWM) { @@ -285,19 +284,18 @@ screen_draw(Screen *self, uint32_t och) { line_set_char(self->linebuf->line, self->cursor->x, 0, 0, self->cursor); self->cursor->x++; } - self->is_dirty = Py_True; + self->is_dirty = true; } else if (is_combining_char(ch)) { if (self->cursor->x > 0) { linebuf_init_line(self->linebuf, self->cursor->y); line_add_combining_char(self->linebuf->line, ch, self->cursor->x - 1); - self->is_dirty = Py_True; + self->is_dirty = true; } else if (self->cursor->y > 0) { linebuf_init_line(self->linebuf, self->cursor->y - 1); line_add_combining_char(self->linebuf->line, ch, self->columns - 1); - self->is_dirty = Py_True; + self->is_dirty = true; } } - if (x != self->cursor->x || y != self->cursor->y) self->cursor_changed = Py_True; } void @@ -419,7 +417,7 @@ screen_toggle_screen_buffer(Screen *self) { screen_restore_cursor(self); } CALLBACK("buf_toggled", "O", self->linebuf == self->main_linebuf ? Py_True : Py_False); - self->is_dirty = Py_True; + self->is_dirty = true; } void screen_normal_keypad_mode(Screen UNUSED *self) {} // Not implemented as this is handled by the GUI @@ -458,13 +456,12 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) { break; case DECTCEM: self->modes.mDECTCEM = val; - self->cursor_changed = Py_True; break; case DECSCNM: // Render screen in reverse video if (self->modes.mDECSCNM != val) { self->modes.mDECSCNM = val; - self->is_dirty = Py_True; + self->is_dirty = true; } break; case DECOM: @@ -484,7 +481,6 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) { break; case CONTROL_CURSOR_BLINK: self->cursor->blink = val; - self->cursor_changed = Py_True; break; case ALTERNATE_SCREEN: if (val && self->linebuf == self->main_linebuf) screen_toggle_screen_buffer(self); @@ -526,7 +522,6 @@ screen_tab(Screen *self) { if (!found) found = self->columns - 1; if (found != self->cursor->x) { self->cursor->x = found; - self->cursor_changed = Py_True; } } @@ -534,7 +529,6 @@ void screen_backtab(Screen *self, unsigned int count) { // Move back count tabs if (!count) count = 1; - unsigned int before = self->cursor->x; int i; while (count > 0 && self->cursor->x > 0) { count--; @@ -543,7 +537,6 @@ screen_backtab(Screen *self, unsigned int count) { } if (i <= 0) self->cursor->x = 0; } - if (before != self->cursor->x) self->cursor_changed = Py_True; } void @@ -571,12 +564,10 @@ screen_set_tab_stop(Screen *self) { void screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/) { - unsigned int x = self->cursor->x; if (count == 0) count = 1; if (move_direction < 0 && count > self->cursor->x) self->cursor->x = 0; else self->cursor->x += move_direction * count; screen_ensure_bounds(self, false); - if (x != self->cursor->x) self->cursor_changed = Py_True; } void @@ -586,13 +577,11 @@ screen_cursor_forward(Screen *self, unsigned int count/*=1*/) { void screen_cursor_up(Screen *self, unsigned int count/*=1*/, bool do_carriage_return/*=false*/, int move_direction/*=-1*/) { - unsigned int x = self->cursor->x, y = self->cursor->y; if (count == 0) count = 1; if (move_direction < 0 && count > self->cursor->y) self->cursor->y = 0; else self->cursor->y += move_direction * count; screen_ensure_bounds(self, true); if (do_carriage_return) self->cursor->x = 0; - if (x != self->cursor->x || y != self->cursor->y) self->cursor_changed = Py_True; } void @@ -616,7 +605,6 @@ screen_cursor_to_column(Screen *self, unsigned int column) { if (x != self->cursor->x) { self->cursor->x = x; screen_ensure_bounds(self, false); - self->cursor_changed = Py_True; } } @@ -629,7 +617,7 @@ screen_cursor_to_column(Screen *self, unsigned int column) { self->history_line_added_count++; \ } \ linebuf_clear_line(self->linebuf, bottom); \ - self->is_dirty = Py_True; + self->is_dirty = true; void screen_index(Screen *self) { @@ -654,7 +642,7 @@ screen_scroll(Screen *self, unsigned int count) { #define INDEX_DOWN \ linebuf_reverse_index(self->linebuf, top, bottom); \ linebuf_clear_line(self->linebuf, top); \ - self->is_dirty = Py_True; + self->is_dirty = true; void screen_reverse_index(Screen *self) { @@ -681,7 +669,6 @@ void screen_carriage_return(Screen *self) { if (self->cursor->x != 0) { self->cursor->x = 0; - self->cursor_changed = Py_True; } } @@ -732,7 +719,6 @@ screen_restore_cursor(Screen *self) { Savepoint *sp = savepoints_pop(pts); if (sp == NULL) { screen_cursor_position(self, 1, 1); - self->cursor_changed = Py_True; screen_reset_mode(self, DECOM); RESET_CHARSETS; screen_reset_mode(self, DECSCNM); @@ -766,10 +752,8 @@ screen_cursor_position(Screen *self, unsigned int line, unsigned int column) { line += self->margin_top; line = MAX(self->margin_top, MIN(line, self->margin_bottom)); } - unsigned int x = self->cursor->x, y = self->cursor->y; self->cursor->x = column; self->cursor->y = line; screen_ensure_bounds(self, false); - if (x != self->cursor->x || y != self->cursor->y) self->cursor_changed = Py_True; } void @@ -816,7 +800,7 @@ void screen_erase_in_line(Screen *self, unsigned int how, bool private) { } else { line_apply_cursor(self->linebuf->line, self->cursor, s, n, true); } - self->is_dirty = Py_True; + self->is_dirty = true; } } @@ -853,7 +837,7 @@ void screen_erase_in_display(Screen *self, unsigned int how, bool private) { line_apply_cursor(self->linebuf->line, self->cursor, 0, self->columns, true); } } - self->is_dirty = Py_True; + self->is_dirty = true; } if (how != 2) { screen_erase_in_line(self, how, private); @@ -865,7 +849,7 @@ void screen_insert_lines(Screen *self, unsigned int count) { if (count == 0) count = 1; if (top <= self->cursor->y && self->cursor->y <= bottom) { linebuf_insert_lines(self->linebuf, count, self->cursor->y, bottom); - self->is_dirty = Py_True; + self->is_dirty = true; screen_carriage_return(self); } } @@ -875,7 +859,7 @@ void screen_delete_lines(Screen *self, unsigned int count) { if (count == 0) count = 1; if (top <= self->cursor->y && self->cursor->y <= bottom) { linebuf_delete_lines(self->linebuf, count, self->cursor->y, bottom); - self->is_dirty = Py_True; + self->is_dirty = true; screen_carriage_return(self); } } @@ -889,7 +873,7 @@ void screen_insert_characters(Screen *self, unsigned int count) { linebuf_init_line(self->linebuf, self->cursor->y); line_right_shift(self->linebuf->line, x, num); line_apply_cursor(self->linebuf->line, self->cursor, x, num, true); - self->is_dirty = Py_True; + self->is_dirty = true; } } @@ -903,7 +887,7 @@ void screen_delete_characters(Screen *self, unsigned int count) { linebuf_init_line(self->linebuf, self->cursor->y); left_shift_line(self->linebuf->line, x, num); line_apply_cursor(self->linebuf->line, self->cursor, self->columns - num, num, true); - self->is_dirty = Py_True; + self->is_dirty = true; } } @@ -914,7 +898,7 @@ void screen_erase_characters(Screen *self, unsigned int count) { unsigned int num = MIN(self->columns - x, count); linebuf_init_line(self->linebuf, self->cursor->y); line_apply_cursor(self->linebuf->line, self->cursor, x, num, true); - self->is_dirty = Py_True; + self->is_dirty = true; } // }}} @@ -1037,7 +1021,6 @@ screen_set_cursor(Screen *self, unsigned int mode, uint8_t secondary) { } if (shape != self->cursor->shape || blink != self->cursor->blink) { self->cursor->shape = shape; self->cursor->blink = blink; - self->cursor_changed = Py_True; } break; } @@ -1068,6 +1051,102 @@ void screen_request_capabilities(Screen *self, 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)); +} + + +static inline void +screen_reset_dirty(Screen *self) { + self->is_dirty = false; + self->history_line_added_count = 0; +} + +void +screen_update_cell_data(Screen *self, void *address, size_t UNUSED sz) { + unsigned int history_line_added_count = self->history_line_added_count; + bool selection_must_be_cleared = self->is_dirty ? true : false; + if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count); + screen_reset_dirty(self); + self->scroll_changed = false; + for (index_type y = 0; y < MIN(self->lines, self->scrolled_by); y++) { + historybuf_init_line(self->historybuf, self->scrolled_by - 1 - y, self->historybuf->line); + update_line_data(self->historybuf->line, y, address); + } + for (index_type y = self->scrolled_by; y < self->lines; y++) { + linebuf_init_line(self->linebuf, y - self->scrolled_by); + update_line_data(self->linebuf->line, y, address); + } + if (selection_must_be_cleared) self->selection = EMPTY_SELECTION; +} + +static inline bool +is_selection_empty(Screen *self, unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) { + return (start_x >= self->columns || start_y >= self->lines || end_x >= self->columns || end_y >= self->lines || (start_x == end_x && start_y == end_y)) ? true : false; +} + +static inline void +selection_coord(Screen *self, unsigned int x, unsigned int y, unsigned int ydelta, SelectionBoundary *ans) { + if (y + self->scrolled_by < ydelta) { + ans->x = 0; ans->y = 0; + } else { + y = y - ydelta + self->scrolled_by; + if (y >= self->lines) { + ans->x = self->columns - 1; ans->y = self->lines - 1; + } else { + ans->x = x; ans->y = y; + } + } +} + +static inline void +selection_limits_(Screen *self, SelectionBoundary *left, SelectionBoundary *right) { + SelectionBoundary a, b; + selection_coord(self, self->selection.start_x, self->selection.start_y, self->selection.start_scrolled_by, &a); + selection_coord(self, self->selection.end_x, self->selection.end_y, self->selection.end_scrolled_by, &b); + if (a.y < b.y || (a.y == b.y && a.x <= b.x)) { *left = a; *right = b; } + else { *left = b; *right = a; } +} + +static inline Line* +visual_line_(Screen *self, index_type y) { + if (self->scrolled_by) { + if (y < self->scrolled_by) { + historybuf_init_line(self->historybuf, self->scrolled_by - 1 - y, self->historybuf->line); + return self->historybuf->line; + } + y -= self->scrolled_by; + } + linebuf_init_line(self->linebuf, y); + return self->linebuf->line; +} + +void +screen_apply_selection(Screen *self, void *address, size_t size) { +#define start (self->last_rendered_selection_start) +#define end (self->last_rendered_selection_end) + float *data = address; + memset(data, 0, size); + selection_limits_(self, &start, &end); + self->last_selection_scrolled_by = self->scrolled_by; + self->selection_updated_once = true; + if (is_selection_empty(self, start.x, start.y, end.x, end.y)) { return; } + for (index_type y = start.y; y <= end.y; y++) { + Line *line = visual_line_(self, y); + index_type xlimit = xlimit_for_line(line); + if (y == end.y) xlimit = MIN(end.x + 1, xlimit); + float *line_start = data + self->columns * y; + for (index_type x = (y == start.y ? start.x : 0); x < xlimit; x++) line_start[x] = 1.0; + } +#undef start +#undef end +} + +// }}} + // Python interface {{{ #define WRAP0(name) static PyObject* name(Screen *self) { screen_##name(self); Py_RETURN_NONE; } #define WRAP0x(name) static PyObject* xxx_##name(Screen *self) { screen_##name(self); Py_RETURN_NONE; } @@ -1086,19 +1165,6 @@ line(Screen *self, PyObject *val) { return (PyObject*) self->linebuf->line; } -static inline Line* -visual_line_(Screen *self, index_type y) { - if (self->scrolled_by) { - if (y < self->scrolled_by) { - historybuf_init_line(self->historybuf, self->scrolled_by - 1 - y, self->historybuf->line); - return self->historybuf->line; - } - y -= self->scrolled_by; - } - linebuf_init_line(self->linebuf, y); - return self->linebuf->line; -} - static PyObject* visual_line(Screen *self, PyObject *args) { // The line corresponding to the yth visual line, taking into account scrolling @@ -1149,9 +1215,7 @@ set_mode(Screen *self, PyObject *args) { static PyObject* reset_dirty(Screen *self) { - self->is_dirty = Py_False; - self->cursor_changed = Py_False; - self->history_line_added_count = 0; + screen_reset_dirty(self); Py_RETURN_NONE; } @@ -1210,91 +1274,6 @@ change_scrollback_size(Screen *self, PyObject *args) { Py_RETURN_NONE; } -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)); -} - - -static PyObject* -update_cell_data(Screen *self, PyObject *args) { - PyObject *dp; - uint8_t *data; - int force_screen_refresh; - unsigned int history_line_added_count = self->history_line_added_count; - if (!PyArg_ParseTuple(args, "O!p", &PyLong_Type, &dp, &force_screen_refresh)) return NULL; - data = PyLong_AsVoidPtr(dp); - PyObject *cursor_changed = self->cursor_changed; - bool selection_must_be_cleared = self->is_dirty == Py_True ? true : false; - if (self->scrolled_by) self->scrolled_by = MIN(self->scrolled_by + history_line_added_count, self->historybuf->count); - reset_dirty(self); - self->scroll_changed = Py_False; - for (index_type y = 0; y < MIN(self->lines, self->scrolled_by); y++) { - historybuf_init_line(self->historybuf, self->scrolled_by - 1 - y, self->historybuf->line); - update_line_data(self->historybuf->line, y, data); - } - for (index_type y = self->scrolled_by; y < self->lines; y++) { - linebuf_init_line(self->linebuf, y - self->scrolled_by); - update_line_data(self->linebuf->line, y, data); - } - if (selection_must_be_cleared) self->selection = EMPTY_SELECTION; - return Py_BuildValue("OO", cursor_changed, self->modes.mDECSCNM ? Py_True : Py_False); -} - -static inline bool -is_selection_empty(Screen *self, unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) { - return (start_x >= self->columns || start_y >= self->lines || end_x >= self->columns || end_y >= self->lines || (start_x == end_x && start_y == end_y)) ? true : false; -} - -static inline void -selection_coord(Screen *self, unsigned int x, unsigned int y, unsigned int ydelta, SelectionBoundary *ans) { - if (y + self->scrolled_by < ydelta) { - ans->x = 0; ans->y = 0; - } else { - y = y - ydelta + self->scrolled_by; - if (y >= self->lines) { - ans->x = self->columns - 1; ans->y = self->lines - 1; - } else { - ans->x = x; ans->y = y; - } - } -} - -static inline void -selection_limits_(Screen *self, SelectionBoundary *left, SelectionBoundary *right) { - SelectionBoundary a, b; - selection_coord(self, self->selection.start_x, self->selection.start_y, self->selection.start_scrolled_by, &a); - selection_coord(self, self->selection.end_x, self->selection.end_y, self->selection.end_scrolled_by, &b); - if (a.y < b.y || (a.y == b.y && a.x <= b.x)) { *left = a; *right = b; } - else { *left = b; *right = a; } -} - -static PyObject* -apply_selection(Screen *self, PyObject *args) { - unsigned int size; - PyObject *l; -#define start (self->last_rendered_selection_start) -#define end (self->last_rendered_selection_end) - if (!PyArg_ParseTuple(args, "O!I", &PyLong_Type, &l, &size)) return NULL; - float *data = PyLong_AsVoidPtr(l); - memset(data, 0, size); - selection_limits_(self, &start, &end); - self->last_selection_scrolled_by = self->scrolled_by; - self->selection_updated_once = true; - if (is_selection_empty(self, start.x, start.y, end.x, end.y)) { Py_RETURN_NONE; } - for (index_type y = start.y; y <= end.y; y++) { - Line *line = visual_line_(self, y); - index_type xlimit = xlimit_for_line(line); - if (y == end.y) xlimit = MIN(end.x + 1, xlimit); - float *line_start = data + self->columns * y; - for (index_type x = (y == start.y ? start.x : 0); x < xlimit; x++) line_start[x] = 1.0; - } - Py_RETURN_NONE; -#undef start -#undef end -} - static PyObject* text_for_selection(Screen *self) { SelectionBoundary start, end; @@ -1383,19 +1362,13 @@ scroll(Screen *self, PyObject *args) { unsigned int new_scroll = MIN(self->scrolled_by + amt, self->historybuf->count); if (new_scroll != self->scrolled_by) { self->scrolled_by = new_scroll; - self->scroll_changed = Py_True; + self->scroll_changed = true; Py_RETURN_TRUE; } } Py_RETURN_FALSE; } -static PyObject* -clear_selection(Screen *self) { - self->selection = EMPTY_SELECTION; - Py_RETURN_NONE; -} - static PyObject* is_selection_in_progress(Screen *self) { PyObject *ans = self->selection.in_progress ? Py_True : Py_False; @@ -1403,12 +1376,12 @@ is_selection_in_progress(Screen *self) { return ans; } -static PyObject* -is_selection_dirty(Screen *self) { +bool +screen_is_selection_dirty(Screen *self) { SelectionBoundary start, end; selection_limits_(self, &start, &end); - if (self->last_selection_scrolled_by != self->scrolled_by || start.x != self->last_rendered_selection_start.x || start.y != self->last_rendered_selection_start.y || end.x != self->last_rendered_selection_end.x || end.y != self->last_rendered_selection_end.y || !self->selection_updated_once) { Py_RETURN_TRUE; } - Py_RETURN_FALSE; + if (self->last_selection_scrolled_by != self->scrolled_by || start.x != self->last_rendered_selection_start.x || start.y != self->last_rendered_selection_start.y || end.x != self->last_rendered_selection_end.x || end.y != self->last_rendered_selection_end.y || !self->selection_updated_once) return true; + return false; } static PyObject* @@ -1433,7 +1406,7 @@ update_selection(Screen *self, PyObject *args) { static PyObject* mark_as_dirty(Screen *self) { - self->is_dirty = Py_True; + self->is_dirty = true; Py_RETURN_NONE; } @@ -1515,19 +1488,15 @@ static PyMethodDef methods[] = { MND(mark_as_dirty, METH_NOARGS) MND(resize, METH_VARARGS) MND(set_margins, METH_VARARGS) - MND(apply_selection, METH_VARARGS) MND(selection_range_for_line, METH_VARARGS) MND(selection_range_for_word, METH_VARARGS) MND(text_for_selection, METH_NOARGS) - MND(clear_selection, METH_NOARGS) MND(is_selection_in_progress, METH_NOARGS) - MND(is_selection_dirty, METH_NOARGS) MND(start_selection, METH_VARARGS) MND(update_selection, METH_VARARGS) MND(scroll, METH_VARARGS) MND(toggle_alt_screen, METH_NOARGS) MND(reset_callbacks, METH_NOARGS) - {"update_cell_data", (PyCFunction)update_cell_data, METH_VARARGS, ""}, {"select_graphic_rendition", (PyCFunction)_select_graphic_rendition, METH_VARARGS, ""}, {NULL} /* Sentinel */ @@ -1553,9 +1522,6 @@ static PyGetSetDef getsetters[] = { static PyMemberDef members[] = { {"callbacks", T_OBJECT_EX, offsetof(Screen, callbacks), 0, "callbacks"}, - {"cursor_changed", T_OBJECT, offsetof(Screen, cursor_changed), 0, "cursor_changed"}, - {"scroll_changed", T_OBJECT, offsetof(Screen, scroll_changed), 0, "scroll_changed"}, - {"is_dirty", T_OBJECT, offsetof(Screen, is_dirty), 0, "is_dirty"}, {"cursor", T_OBJECT_EX, offsetof(Screen, cursor), READONLY, "cursor"}, {"color_profile", T_OBJECT_EX, offsetof(Screen, color_profile), READONLY, "color_profile"}, {"linebuf", T_OBJECT_EX, offsetof(Screen, linebuf), READONLY, "linebuf"}, diff --git a/kitty/screen.h b/kitty/screen.h index 1520e5306..57b8946f5 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -59,6 +59,9 @@ void report_device_attributes(Screen *self, unsigned int UNUSED mode, char start void select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count); void report_device_status(Screen *self, unsigned int which, bool UNUSED); void report_mode_status(Screen *self, unsigned int which, bool); +void screen_apply_selection(Screen *self, void *address, size_t size); +bool screen_is_selection_dirty(Screen *self); +void screen_update_cell_data(Screen *self, void *address, size_t sz); #define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen); DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(backspace) diff --git a/kitty/shaders.c b/kitty/shaders.c index 93cf14889..7dc0ea049 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -6,6 +6,7 @@ */ #include "data-types.h" +#include "screen.h" #ifdef __APPLE__ #include #include @@ -44,7 +45,7 @@ check_for_gl_error(int line) { case GL_INVALID_VALUE: f("An numeric value is invalid (GL_INVALID_VALUE)"); case GL_INVALID_OPERATION: - f("This operation is not allowed in the current state (GL_INVALID_OPERATION)"); + f("This operation is invalid (GL_INVALID_OPERATION)"); case GL_INVALID_FRAMEBUFFER_OPERATION: f("The framebuffer object is not complete (GL_INVALID_FRAMEBUFFER_OPERATION)"); case GL_OUT_OF_MEMORY: @@ -87,7 +88,7 @@ enum ProgramNames { CELL_PROGRAM, CURSOR_PROGRAM, BORDERS_PROGRAM, NUM_PROGRAMS typedef struct { char name[256]; - GLint size, id; + GLint size, location, idx; GLenum type; } Uniform; @@ -130,7 +131,8 @@ init_uniforms(int program) { Uniform *u = p->uniforms + i; glGetActiveUniform(p->id, (GLuint)i, sizeof(u->name)/sizeof(u->name[0]), NULL, &(u->size), &(u->type), u->name); check_gl(); - u->id = i; + u->location = glGetUniformLocation(p->id, u->name); + u->idx = i; } } @@ -142,6 +144,32 @@ attrib_location(int program, const char *name) { return ans; } +static inline GLuint +block_index(int program, const char *name) { + GLuint ans = glGetUniformBlockIndex(programs[program].id, name); + check_gl(); + if (ans == GL_INVALID_INDEX) { fatal("Could not find block index"); } + return ans; +} + + +static inline GLint +block_size(int program, GLuint block_index) { + GLint ans; + glGetActiveUniformBlockiv(programs[program].id, block_index, GL_UNIFORM_BLOCK_DATA_SIZE, &ans); + check_gl(); + return ans; +} + +static GLint +block_offset(int program, GLuint uniform_idx) { + GLint program_id = programs[program].id; + GLint ans; + glGetActiveUniformsiv(program_id, 1, &uniform_idx, GL_UNIFORM_OFFSET, &ans); + check_gl(); + return ans; +} + static void bind_program(int program) { glUseProgram(programs[program].id); @@ -192,10 +220,11 @@ delete_buffer(ssize_t buf_idx) { buffers[buf_idx].size = 0; } -static void +static GLuint bind_buffer(ssize_t buf_idx) { glBindBuffer(buffers[buf_idx].usage, buffers[buf_idx].id); check_gl(); + return buffers[buf_idx].id; } static void @@ -258,13 +287,13 @@ create_vao() { } static void -add_buffer_to_vao(ssize_t vao_idx) { +add_buffer_to_vao(ssize_t vao_idx, GLenum usage) { VAO* vao = vaos + vao_idx; if (vao->num_buffers >= sizeof(vao->buffers) / sizeof(vao->buffers[0])) { fatal("too many buffers in a single VAO"); return; } - ssize_t buf = create_buffer(GL_ARRAY_BUFFER); + ssize_t buf = create_buffer(usage); vao->buffers[vao->num_buffers++] = buf; } @@ -333,6 +362,13 @@ map_vao_buffer(ssize_t vao_idx, GLsizeiptr size, size_t bufnum, GLenum usage, GL return ans; } +static void +bind_vao_uniform_buffer(ssize_t vao_idx, size_t bufnum, GLuint block_index) { + ssize_t buf_idx = vaos[vao_idx].buffers[bufnum]; + glBindBufferBase(GL_UNIFORM_BUFFER, block_index, buffers[buf_idx].id); + check_gl(); +} + static void unmap_vao_buffer(ssize_t vao_idx, size_t bufnum) { ssize_t buf_idx = vaos[vao_idx].buffers[bufnum]; @@ -342,6 +378,106 @@ unmap_vao_buffer(ssize_t vao_idx, size_t bufnum) { // }}} +// Cell {{{ + +enum CellUniforms { CELL_dimensions, CELL_default_colors, CELL_color_indices, CELL_steps, CELL_sprites, CELL_sprite_layout, CELL_color_table, NUM_CELL_UNIFORMS }; +static GLint cell_uniform_locations[NUM_CELL_UNIFORMS] = {0}; +static GLint cell_color_table_stride = 0, cell_color_table_offset = 0, cell_color_table_size = 0, cell_color_table_block_index = 0; + +static void +init_cell_program() { + Program *p = programs + CELL_PROGRAM; + int left = NUM_CELL_UNIFORMS; + GLint ctable_idx = 0; + for (int i = 0; i < p->num_of_uniforms; i++, left--) { +#define SET_LOC(which) if (strcmp(p->uniforms[i].name, #which) == 0) cell_uniform_locations[CELL_##which] = p->uniforms[i].location + SET_LOC(dimensions); + else SET_LOC(default_colors); + else SET_LOC(color_indices); + else SET_LOC(steps); + else SET_LOC(sprites); + else SET_LOC(sprite_layout); + else if (strcmp(p->uniforms[i].name, "color_table[0]") == 0) { ctable_idx = i; cell_uniform_locations[CELL_color_table] = p->uniforms[i].location; } + else { fatal("Unknown uniform in cell program: %s", p->uniforms[i].name); } + } + if (left) { fatal("Left over uniforms in cell program"); } + cell_color_table_block_index = block_index(CELL_PROGRAM, "ColorTable"); + cell_color_table_size = block_size(CELL_PROGRAM, cell_color_table_block_index); + cell_color_table_stride = cell_color_table_size / (256 * sizeof(GLuint)); + cell_color_table_offset = block_offset(CELL_PROGRAM, ctable_idx); +#undef SET_LOC +} + +static ssize_t +create_cell_vao() { + ssize_t vao_idx = create_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)) + + add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER); + A1(text_attrs, 1, GL_UNSIGNED_INT, ch); + A1(sprite_coords, 3, GL_UNSIGNED_SHORT, sprite_x); + A1(colors, 3, GL_UNSIGNED_INT, fg); + add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER); + A(is_selected, 1, GL_FLOAT, NULL, 0); + add_buffer_to_vao(vao_idx, GL_UNIFORM_BUFFER); + bind_vao_uniform_buffer(vao_idx, 2, cell_color_table_block_index); + return vao_idx; +#undef A +#undef A1 +} + +static void +draw_cells(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, bool inverted, Screen *screen) { + size_t sz; + void *address; + if (screen->modes.mDECSCNM) inverted = inverted ? false : true; + if (screen->scroll_changed || screen->is_dirty) { + sz = sizeof(Cell) * screen->lines * screen->columns; + address = map_vao_buffer(vao_idx, sz, 0, GL_STREAM_DRAW, GL_WRITE_ONLY); + screen_update_cell_data(screen, address, sz); + unmap_vao_buffer(vao_idx, 0); + } + if (screen_is_selection_dirty(screen)) { + sz = sizeof(GLfloat) * screen->lines * screen->columns; + address = map_vao_buffer(vao_idx, sz, 1, GL_STREAM_DRAW, GL_WRITE_ONLY); + screen_apply_selection(screen, address, sz); + unmap_vao_buffer(vao_idx, 1); + } + if (UNLIKELY(screen->color_profile->dirty)) { + address = map_vao_buffer(vao_idx, cell_color_table_size, 2, GL_STATIC_DRAW, GL_WRITE_ONLY); + copy_color_table_to_buffer(screen->color_profile, address, cell_color_table_offset, cell_color_table_stride); + unmap_vao_buffer(vao_idx, 2); + } +#define UL(name) cell_uniform_locations[CELL_##name] + bind_program(CELL_PROGRAM); + glUniform2ui(UL(dimensions), screen->columns, screen->lines); + check_gl(); + glUniform4f(UL(steps), xstart, ystart, dx, dy); + check_gl(); + glUniform2i(UL(color_indices), inverted & 1, 1 - (inverted & 1)); + check_gl(); +#define COLOR(name) colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name) + glUniform4ui(UL(default_colors), COLOR(default_fg), COLOR(default_bg), COLOR(highlight_fg), COLOR(highlight_bg)); + check_gl(); +#undef COLOR + glUniform1i(UL(sprites), 0); + check_gl(); + unsigned int x, y, z; + sprite_map_current_layout(&x, &y, &z); + glUniform2f(UL(sprite_layout), 1.0 / (float)x, 1.0 / (float)y); + check_gl(); + bind_vertex_array(vao_idx); + glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); + check_gl(); + unbind_vertex_array(); + unbind_program(); +#undef UL +} +// }}} + // Cursor {{{ enum CursorUniforms { CURSOR_color, CURSOR_xpos, CURSOR_ypos, NUM_CURSOR_UNIFORMS }; static GLint cursor_uniform_locations[NUM_CURSOR_UNIFORMS] = {0}; @@ -353,7 +489,7 @@ init_cursor_program() { int left = NUM_CURSOR_UNIFORMS; cursor_vertex_array = create_vao(); for (int i = 0; i < p->num_of_uniforms; i++, left--) { -#define SET_LOC(which) if (strcmp(p->uniforms[i].name, #which) == 0) cursor_uniform_locations[CURSOR_##which] = p->uniforms[i].id +#define SET_LOC(which) if (strcmp(p->uniforms[i].name, #which) == 0) cursor_uniform_locations[CURSOR_##which] = p->uniforms[i].location SET_LOC(color); else SET_LOC(xpos); else SET_LOC(ypos); @@ -394,13 +530,13 @@ init_borders_program() { int left = NUM_BORDER_UNIFORMS; border_vertex_array = create_vao(); for (int i = 0; i < p->num_of_uniforms; i++, left--) { -#define SET_LOC(which) if (strcmp(p->uniforms[i].name, #which) == 0) border_uniform_locations[BORDER_##which] = p->uniforms[i].id +#define SET_LOC(which) if (strcmp(p->uniforms[i].name, #which) == 0) border_uniform_locations[BORDER_##which] = p->uniforms[i].location SET_LOC(viewport); else { fatal("Unknown uniform in borders program"); return; } } if (left) { fatal("Left over uniforms in borders program"); return; } #undef SET_LOC - add_buffer_to_vao(border_vertex_array); + add_buffer_to_vao(border_vertex_array, GL_ARRAY_BUFFER); add_attribute_to_vao(BORDERS_PROGRAM, border_vertex_array, "rect", /*size=*/4, /*dtype=*/GL_UNSIGNED_INT, /*stride=*/sizeof(GLuint)*5, /*offset=*/0, /*divisor=*/1); add_attribute_to_vao(BORDERS_PROGRAM, border_vertex_array, "rect_color", @@ -497,6 +633,7 @@ end: #define ONE_INT(name) PYWRAP1(name) { name(PyLong_AsSsize_t(args)); Py_RETURN_NONE; } #define TWO_INT(name) PYWRAP1(name) { int a, b; PA("ii", &a, &b); name(a, b); Py_RETURN_NONE; } #define NO_ARG(name) PYWRAP0(name) { name(); Py_RETURN_NONE; } +#define NO_ARG_INT(name) PYWRAP0(name) { return PyLong_FromSsize_t(name()); } ONE_INT(bind_program) NO_ARG(unbind_program) @@ -508,18 +645,6 @@ PYWRAP0(create_vao) { } ONE_INT(remove_vao) -ONE_INT(add_buffer_to_vao) - -PYWRAP2(add_attribute_to_vao) { - int program, vao, data_type = GL_FLOAT, size = 3; - char *name; - unsigned int stride = 0, divisor = 0; - PyObject *offset = NULL; - static char* keywords[] = {"program", "vao", "name", "size", "dtype", "stride", "offset", "divisor", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kw, "iis|iiIO!I", keywords, &program, &vao, &name, &size, &data_type, &stride, &PyLong_Type, &offset, &divisor)) return NULL; - add_attribute_to_vao(program, vao, name, size, data_type, stride, offset ? PyLong_AsVoidPtr(offset) : NULL, divisor); - Py_RETURN_NONE; -} ONE_INT(bind_vertex_array) NO_ARG(unbind_vertex_array) @@ -546,6 +671,18 @@ NO_ARG(draw_borders) PYWRAP1(add_borders_rect) { unsigned int a, b, c, d, e; PA("IIIII", &a, &b, &c, &d, &e); add_borders_rect(a, b, c, d, e); Py_RETURN_NONE; } TWO_INT(send_borders_rects) +NO_ARG(init_cell_program) +NO_ARG_INT(create_cell_vao) +PYWRAP1(draw_cells) { + float xstart, ystart, dx, dy; + int vao_idx, inverted; + Screen *screen; + + PA("iffffpO", &vao_idx, &xstart, &ystart, &dx, &dy, &inverted, &screen); + draw_cells(vao_idx, xstart, ystart, dx, dy, inverted & 1, screen); + Py_RETURN_NONE; +} + #define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL} #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} static PyMethodDef module_methods[] = { @@ -554,8 +691,6 @@ static PyMethodDef module_methods[] = { M(compile_program, METH_VARARGS), MW(create_vao, METH_NOARGS), MW(remove_vao, METH_O), - MW(add_buffer_to_vao, METH_O), - MW(add_attribute_to_vao, METH_VARARGS | METH_KEYWORDS), MW(bind_vertex_array, METH_O), MW(unbind_vertex_array, METH_NOARGS), MW(map_vao_buffer, METH_VARARGS), @@ -568,6 +703,9 @@ static PyMethodDef module_methods[] = { MW(draw_borders, METH_NOARGS), MW(add_borders_rect, METH_VARARGS), MW(send_borders_rects, METH_VARARGS), + MW(init_cell_program, METH_NOARGS), + MW(create_cell_vao, METH_NOARGS), + MW(draw_cells, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/shaders.py b/kitty/shaders.py index 7ebe3fb0c..cdb9c2926 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -2,114 +2,25 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal -import os import sys -from collections import namedtuple -from contextlib import contextmanager -from ctypes import addressof, c_char, sizeof -from functools import lru_cache +from ctypes import addressof from .fast_data_types import ( - GL_ARRAY_BUFFER, GL_CLAMP_TO_EDGE, GL_FLOAT, GL_MAX_ARRAY_TEXTURE_LAYERS, - GL_MAX_TEXTURE_SIZE, GL_NEAREST, GL_R8, GL_RED, GL_STREAM_DRAW, - GL_TEXTURE0, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, - GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, - GL_UNIFORM_BUFFER, GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_WRITE_ONLY, - GLSL_VERSION, compile_program, copy_image_sub_data, - get_uniform_block_offsets, get_uniform_block_size, glActiveTexture, - glBindBuffer, glBindBufferBase, glBindTexture, glBindVertexArray, - glCopyImageSubData, glDeleteBuffer, glDeleteTexture, glDeleteVertexArray, - glEnableVertexAttribArray, glGenBuffers, glGenTextures, glGenVertexArrays, - glGetAttribLocation, glGetBufferSubData, glGetIntegerv, - glGetUniformBlockIndex, glGetUniformLocation, glMapBuffer, glPixelStorei, - glTexParameteri, glTexStorage3D, glTexSubImage3D, glUnmapBuffer, - glUseProgram, glVertexAttribDivisor, glVertexAttribPointer, - render_dirty_sprites, replace_or_create_buffer, sprite_map_current_layout, - sprite_map_free, sprite_map_increment, sprite_map_set_layout, - sprite_map_set_limits + GL_CLAMP_TO_EDGE, GL_MAX_ARRAY_TEXTURE_LAYERS, GL_MAX_TEXTURE_SIZE, + GL_NEAREST, GL_R8, GL_RED, GL_TEXTURE0, GL_TEXTURE_2D_ARRAY, + GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S, + GL_TEXTURE_WRAP_T, GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, + copy_image_sub_data, glActiveTexture, glBindTexture, glCopyImageSubData, + glDeleteTexture, glGenTextures, glGetIntegerv, glPixelStorei, + glTexParameteri, glTexStorage3D, glTexSubImage3D, render_dirty_sprites, + sprite_map_current_layout, sprite_map_free, sprite_map_increment, + sprite_map_set_layout, sprite_map_set_limits ) from .fonts.render import render_cell from .utils import safe_print -UBO = namedtuple('UBO', 'size index offsets buf_id') -BASE = os.path.dirname(os.path.abspath(__file__)) - -def load_shaders(name): - vert = open(os.path.join(BASE, '{}_vertex.glsl'.format(name))).read().replace('GLSL_VERSION', str(GLSL_VERSION), 1) - frag = open(os.path.join(BASE, '{}_fragment.glsl'.format(name))).read().replace('GLSL_VERSION', str(GLSL_VERSION), 1) - return vert, frag - - -class BufferManager: # {{{ - - def __init__(self): - self.sizes = {} - self.types = {} - self.ctypes_types = {} - self.name_count = 0 - - def create(self, for_use=GL_ARRAY_BUFFER): - buf_id = glGenBuffers(1) - self.types[buf_id] = for_use - self.sizes.pop(buf_id, None) - self.ctypes_types.pop(buf_id, None) - return buf_id - - def delete(self, buf_id): - if buf_id in self.types: - glDeleteBuffer(buf_id) - self.sizes.pop(buf_id, None) - self.types.pop(buf_id) - self.ctypes_types.pop(buf_id, None) - - def set_data(self, buf_id, data, usage=GL_STREAM_DRAW, verify=False): - prev_sz = self.sizes.get(buf_id, 0) - new_sz = sizeof(data) - replace_or_create_buffer(buf_id, new_sz, prev_sz, addressof(data), usage, self.types[buf_id]) - self.sizes[buf_id] = new_sz - self.ctypes_types[buf_id] = type(data) - if verify: - verify_data = self.get_data(buf_id) - if list(data) != list(verify_data): - raise RuntimeError('OpenGL failed to upload to buffer') - - def get_data(self, buf_id): - verify_data = self.ctypes_types[buf_id]() - glGetBufferSubData(self.types[buf_id], buf_id, self.sizes[buf_id], 0, addressof(verify_data)) - return verify_data - - def bind(self, buf_id): - glBindBuffer(self.types[buf_id], buf_id) - - def unbind(self, buf_id): - glBindBuffer(self.types[buf_id], 0) - - @contextmanager - def bound_buffer(self, buf_id): - self.bind(buf_id) - yield - self.unbind(buf_id) - - @contextmanager - def mapped_buffer(self, buf_id, buf_sz, usage=GL_STREAM_DRAW, access=GL_WRITE_ONLY): - prev_sz = self.sizes.get(buf_id, 0) - buf_type = self.types[buf_id] - if prev_sz != buf_sz: - replace_or_create_buffer(buf_id, buf_sz, 0, 0, usage, buf_type) - self.sizes[buf_id] = buf_sz - self.ctypes_types[buf_id] = c_char - with self.bound_buffer(buf_id): - address = glMapBuffer(buf_type, access) - yield address - glUnmapBuffer(buf_type) - - -buffer_manager = BufferManager() -# }}} - - -class Sprites: # {{{ +class Sprites: ''' Maintain sprite sheets of all rendered characters on the GPU as a texture array with each texture being a sprite sheet. ''' @@ -149,11 +60,6 @@ class Sprites: # {{{ if send(strikethrough=True) != 3: raise RuntimeError('Available OpenGL texture size is too small') - @property - def layout(self): - xnum, ynum, znum = sprite_map_current_layout() - return 1 / xnum, 1 / ynum - def render_cell(self, text, bold, italic, is_second): first, second = render_cell(text, bold, italic) ans = second if is_second else first @@ -231,121 +137,3 @@ class Sprites: # {{{ def __exit__(self, *a): glBindTexture(GL_TEXTURE_2D_ARRAY, 0) - -# }}} - - -class ShaderProgram: # {{{ - """ Helper class for using GLSL shader programs """ - - def __init__(self, which, vertex, fragment): - """ - Create a shader program. - - """ - self.program_id = compile_program(which, vertex, fragment) - self.vertex_arrays = {} - - @contextmanager - def array_object_creator(self): - vao_id = glGenVertexArrays(1) - self.vertex_arrays[vao_id] = buffers = [] - buf_id = None - - def newbuf(): - nonlocal buf_id - buf_id = buffer_manager.create(for_use=GL_ARRAY_BUFFER) - buffers.append(buf_id) - return buf_id - - def add_attribute(name, size=3, dtype=GL_FLOAT, normalized=False, stride=0, offset=0, divisor=0): - nonlocal buf_id - aid = self.attribute_location(name) - if aid > -1: - if buf_id is None: - buf_id = newbuf() - with buffer_manager.bound_buffer(buf_id): - glEnableVertexAttribArray(aid) - glVertexAttribPointer(aid, size, dtype, normalized, stride, offset) - if divisor > 0: - glVertexAttribDivisor(aid, divisor) - - add_attribute.newbuf = newbuf - add_attribute.vao_id = vao_id - with self.bound_vertex_array(vao_id): - yield add_attribute - - @contextmanager - def bound_vertex_array(self, vao_id): - glBindVertexArray(vao_id) - yield - glBindVertexArray(0) - - def remove_vertex_array(self, vao_id): - buffers = self.vertex_arrays.pop(vao_id, None) - if buffers is not None: - glDeleteVertexArray(vao_id) - for buf_id in buffers: - buffer_manager.delete(buf_id) - - def send_vertex_data(self, vao_id, data, usage=GL_STREAM_DRAW, bufnum=0): - bufid = self.vertex_arrays[vao_id][bufnum] - buffer_manager.set_data(bufid, data, usage=usage) - - def mapped_vertex_data(self, vao_id, buf_sz, usage=GL_STREAM_DRAW, bufnum=0, access=GL_WRITE_ONLY): - bufid = self.vertex_arrays[vao_id][bufnum] - return buffer_manager.mapped_buffer(bufid, buf_sz, usage=usage, access=access) - - def get_vertex_data(self, vao_id, bufnum=0): - bufid = self.vertex_arrays[vao_id][bufnum] - return buffer_manager.get_data(bufid) - - def __hash__(self) -> int: - return self.program_id - - def __eq__(self, other) -> bool: - return isinstance(other, ShaderProgram) and other.program_id == self.program_id - - def __ne__(self, other) -> bool: - return not self.__eq__(other) - - @lru_cache(maxsize=2**6) - def uniform_location(self, name: str) -> int: - ' Return the id for the uniform variable `name` or -1 if not found. ' - return glGetUniformLocation(self.program_id, name) - - @lru_cache(maxsize=2**6) - def uniform_block_index(self, name: str) -> int: - ' Return the block index for the specified uniform block raises ValueError if not found. ' - return glGetUniformBlockIndex(self.program_id, name) - - def uniform_block_size(self, block_index): - return get_uniform_block_size(self.program_id, block_index) - - def init_uniform_block(self, name, *variables): - idx = self.uniform_block_index(name) - size = self.uniform_block_size(idx) - offsets = get_uniform_block_offsets(self.program_id, variables) - offsets = dict(zip(variables, offsets)) - buf_id = buffer_manager.create(GL_UNIFORM_BUFFER) - return UBO(size=size, index=idx, offsets=offsets, buf_id=buf_id) - - def mapped_uniform_data(self, ubo, usage=GL_STREAM_DRAW, access=GL_WRITE_ONLY): - return buffer_manager.mapped_buffer(ubo.buf_id, ubo.size, usage=usage, access=access) - - @contextmanager - def bound_uniform_buffer(self, ubo): - glBindBufferBase(GL_UNIFORM_BUFFER, ubo.index, ubo.buf_id) - yield - - @lru_cache(maxsize=2**6) - def attribute_location(self, name: str) -> int: - ' Return the id for the attribute variable `name` or -1 if not found. ' - return glGetAttribLocation(self.program_id, name) - - def __enter__(self): - glUseProgram(self.program_id) - - def __exit__(self, *args): - glUseProgram(0) -# }}} diff --git a/kitty/sprites.c b/kitty/sprites.c index 8105279ad..1300b1e60 100644 --- a/kitty/sprites.c +++ b/kitty/sprites.c @@ -161,8 +161,13 @@ sprite_map_set_layout(PyObject UNUSED *s_, PyObject *args) { Py_RETURN_NONE; } -PyObject* -sprite_map_current_layout(PyObject UNUSED *s) { +void +sprite_map_current_layout(unsigned int *x, unsigned int *y, unsigned int *z) { + *x = sprite_map.xnum; *y = sprite_map.ynum; *z = sprite_map.z; +} + +static PyObject* +current_layout(PyObject UNUSED *self) { return Py_BuildValue("III", sprite_map.xnum, sprite_map.ynum, sprite_map.z); } @@ -211,11 +216,11 @@ render_dirty_sprites(PyObject UNUSED *s_) { static PyMethodDef module_methods[] = { {"sprite_map_set_limits", (PyCFunction)sprite_map_set_limits, METH_VARARGS, ""}, \ {"sprite_map_set_layout", (PyCFunction)sprite_map_set_layout, METH_VARARGS, ""}, \ - {"sprite_map_current_layout", (PyCFunction)sprite_map_current_layout, METH_NOARGS, ""}, \ {"sprite_map_free", (PyCFunction)sprite_map_free, METH_NOARGS, ""}, \ {"sprite_map_increment", (PyCFunction)sprite_map_increment, METH_NOARGS, ""}, \ {"sprite_position_for", (PyCFunction)sprite_position_for, METH_VARARGS, ""}, \ {"render_dirty_sprites", (PyCFunction)render_dirty_sprites, METH_NOARGS, ""}, \ + {"sprite_map_current_layout", (PyCFunction)current_layout, METH_NOARGS, ""}, \ {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/tabs.py b/kitty/tabs.py index 850c15f6d..6c5a7e65f 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -3,7 +3,6 @@ # License: GPL v3 Copyright: 2016, Kovid Goyal from collections import deque, namedtuple -from ctypes import memset, sizeof from functools import partial from .borders import Borders @@ -11,10 +10,12 @@ from .char_grid import calculate_gl_geometry, render_cells from .child import Child from .config import build_ansi_color_table from .constants import ( - GLfloat, WindowGeometry, appname, cell_size, get_boss, shell_path, + WindowGeometry, appname, cell_size, get_boss, shell_path, viewport_size ) -from .fast_data_types import CELL, DECAWM, Screen, glfw_post_empty_event +from .fast_data_types import ( + DECAWM, Screen, create_cell_vao, glfw_post_empty_event +) from .layout import Rect, all_layouts from .utils import color_as_int from .window import Window @@ -205,7 +206,7 @@ class TabBar: self.num_tabs = 1 self.cell_width = 1 self.data_buffer_size = 0 - self.vao_id = None + self.vao_id = create_cell_vao() self.layout_changed = None self.dirty = True self.screen = s = Screen(None, 1, 10) @@ -214,7 +215,6 @@ class TabBar: color_as_int(opts.inactive_tab_foreground), color_as_int(opts.inactive_tab_background) ) - s.color_profile.dirty = True self.blank_rects = () def as_rgb(x): @@ -229,8 +229,6 @@ class TabBar: ncells = viewport_width // cell_width s.resize(1, ncells) s.reset_mode(DECAWM) - self.selection_buf_size = sizeof(GLfloat) * s.lines * s.columns - self.data_buffer_size = s.lines * s.columns * CELL['size'] self.layout_changed = True margin = (viewport_width - ncells * cell_width) // 2 self.window_geometry = g = WindowGeometry( @@ -269,22 +267,11 @@ class TabBar: break s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab self.cell_ranges = cr - self.dirty = True glfw_post_empty_event() - def render(self, cell_program, sprites): + def render(self): if self.layout_changed is not None: - if self.vao_id is None: - self.vao_id = cell_program.create_sprite_map() - if self.dirty: - with cell_program.mapped_vertex_data(self.vao_id, self.data_buffer_size) as address: - self.screen.update_cell_data(address, True) - if self.layout_changed: - with cell_program.mapped_vertex_data(self.vao_id, self.selection_buf_size, bufnum=1) as address: - memset(address, 0, self.selection_buf_size) - self.layout_changed = False - self.dirty = False - render_cells(self.vao_id, self.screen_geometry, cell_program, sprites, self.screen.color_profile) + render_cells(self.vao_id, self.screen_geometry, self.screen) def tab_at(self, x): x = (x - self.window_geometry.left) // self.cell_width @@ -394,7 +381,7 @@ class TabManager: def blank_rects(self): return self.tab_bar.blank_rects if len(self.tabs) > 1 else () - def render(self, cell_program, sprites): + def render(self): if len(self.tabs) < 2: return - self.tab_bar.render(cell_program, sprites) + self.tab_bar.render() diff --git a/kitty/utils.py b/kitty/utils.py index f57721129..bbed88e26 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -13,9 +13,17 @@ from functools import lru_cache from time import monotonic from .constants import isosx -from .fast_data_types import glfw_get_physical_dpi, wcwidth as wcwidth_impl, redirect_std_streams +from .fast_data_types import glfw_get_physical_dpi, wcwidth as wcwidth_impl, redirect_std_streams, GLSL_VERSION from .rgb import Color, to_color +BASE = os.path.dirname(os.path.abspath(__file__)) + + +def load_shaders(name): + vert = open(os.path.join(BASE, '{}_vertex.glsl'.format(name))).read().replace('GLSL_VERSION', str(GLSL_VERSION), 1) + frag = open(os.path.join(BASE, '{}_fragment.glsl'.format(name))).read().replace('GLSL_VERSION', str(GLSL_VERSION), 1) + return vert, frag + def safe_print(*a, **k): try: diff --git a/kitty/window.py b/kitty/window.py index 41bf494cb..7d3acb53b 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -283,13 +283,13 @@ class Window: def buf_toggled(self, is_main_linebuf): self.screen.scroll(SCROLL_FULL, False) - def render_cells(self, program, sprites): + def render_cells(self): invert_colors = False if self.start_visual_bell_at is not None: invert_colors = monotonic() - self.start_visual_bell_at <= self.opts.visual_bell_duration if not invert_colors: self.start_visual_bell_at = None - self.char_grid.render_cells(program, sprites, invert_colors) + self.char_grid.render_cells(invert_colors) # actions {{{