diff --git a/kitty/border_vertex.glsl b/kitty/border_vertex.glsl index 5078cc1c3..560021ee1 100644 --- a/kitty/border_vertex.glsl +++ b/kitty/border_vertex.glsl @@ -1,7 +1,7 @@ #version GLSL_VERSION -uniform vec3 colors[3]; uniform uvec2 viewport; in uvec4 rect; // left, top, right, bottom +in uint rect_color; out vec3 color; // indices into the rect vector @@ -17,10 +17,16 @@ const uvec2 pos_map[] = uvec2[4]( uvec2(LEFT, TOP) ); -float to_opengl(uint val, uint sz) { return -1.0 + 2.0 * (float(val) / float(sz)); } +float to_opengl(uint val, uint sz) { + return -1.0 + 2.0 * (float(val) / float(sz)); +} + +float to_color(uint c) { + return float(c & uint(0xff)) / 255.0; +} void main() { uvec2 pos = pos_map[gl_VertexID]; gl_Position = vec4(to_opengl(rect[pos.x], viewport.x), to_opengl(rect[pos.y], viewport.y), 0, 1); - color = vec3(1, 0, 0); + color = vec3(to_color(rect_color >> 16), to_color(rect_color >> 8), to_color(rect_color)); } diff --git a/kitty/borders.py b/kitty/borders.py index 5f5f76ce2..6f5487a7a 100644 --- a/kitty/borders.py +++ b/kitty/borders.py @@ -2,84 +2,49 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal -from ctypes import addressof from functools import partial from itertools import chain -from .constants import GLfloat, GLuint, viewport_size +from .constants import viewport_size from .fast_data_types import ( - GL_STATIC_DRAW, GL_TRIANGLE_FAN, glDrawArraysInstanced, glUniform3fv, - BORDERS_PROGRAM, GL_UNSIGNED_INT, glUniform2ui + BORDERS_PROGRAM, add_borders_rect, compile_program, draw_borders, + init_borders_program, send_borders_rects ) -from .shaders import ShaderProgram, load_shaders -from .utils import pt_to_px +from .shaders import load_shaders +from .utils import color_as_int, pt_to_px -def as_color(c): - return c[0] / 255, c[1] / 255, c[2] / 255 +def vertical_edge(color, width, top, bottom, left): + add_borders_rect(left, top, left + width, bottom, color) -def to_opengl(val, sz): - return -1 + 2 * val / sz +def horizontal_edge(color, height, left, right, top): + add_borders_rect(left, top, right, top + height, color) -def as_rect(left, top, right, bottom, color=0): - yield left - yield top - yield right - yield bottom +def edge(func, color, sz, a, b): + return partial(func, color, sz, a, b) -class BordersProgram(ShaderProgram): - - def __init__(self): - ShaderProgram.__init__(self, BORDERS_PROGRAM, *load_shaders('border')) - with self.array_object_creator() as add_attribute: - self.vao_id = add_attribute.vao_id - add_attribute('rect', size=4, dtype=GL_UNSIGNED_INT, divisor=1) - - def send_data(self, data): - self.send_vertex_data(self.vao_id, data, usage=GL_STATIC_DRAW) - - def set_colors(self, color_buf): - glUniform3fv(self.uniform_location('colors'), 3, addressof(color_buf)) - glUniform2ui(self.uniform_location('viewport'), viewport_size.width, viewport_size.height) - - -def border_maker(rects): - ' Create a function that will add all the rectangles for drawing a border to rects ' - - def r(l, t, b, r, color): - rects.extend(as_rect(l, t, b, r, color)) - - def vertical_edge(color, width, top, bottom, left): - r(left, top, left + width, bottom, color) - - def horizontal_edge(color, height, left, right, top): - r(left, top, right, top + height, color) - - def edge(func, color, sz, a, b): - return partial(func, color, sz, a, b) - - def border(color, sz, left, top, right, bottom): - horz = edge(horizontal_edge, color, sz, left, right) - horz(top), horz(bottom - sz) # top, bottom edges - vert = edge(vertical_edge, color, sz, top, bottom) - vert(left), vert(right - sz) # left, right edges - - return border +def border(color, sz, left, top, right, bottom): + horz = edge(horizontal_edge, color, sz, left, right) + horz(top), horz(bottom - sz) # top, bottom edges + vert = edge(vertical_edge, color, sz, top, bottom) + vert(left), vert(right - sz) # left, right edges class Borders: def __init__(self, opts): self.is_dirty = False - self.can_render = False self.border_width = pt_to_px(opts.window_border_width) self.padding_width = pt_to_px(opts.window_padding_width) - self.color_buf = (GLfloat * 9)( - *as_color(opts.background), *as_color(opts.active_border_color), - *as_color(opts.inactive_border_color)) + compile_program(BORDERS_PROGRAM, *load_shaders('border')) + init_borders_program() + self.background = color_as_int(opts.background) + self.active_border = color_as_int(opts.active_border_color) + self.inactive_border = color_as_int(opts.inactive_border_color) + self.dirty = False def __call__( self, @@ -89,44 +54,30 @@ class Borders: extra_blank_rects, draw_window_borders=True ): - rects = [] + add_borders_rect(0, 0, 0, 0, 0) for br in chain(current_layout.blank_rects, extra_blank_rects): - rects.extend(as_rect(*br)) + add_borders_rect(*br, self.background) bw, pw = self.border_width, self.padding_width fw = bw + pw - border = border_maker(rects) if fw > 0: for w in windows: g = w.geometry if bw > 0 and draw_window_borders: # Draw the border rectangles - color = 1 if w is active_window else 2 + color = self.active_border if w is active_window else self.inactive_border border( color, bw, g.left - fw, g.top - fw, g.right + fw, - g.bottom + fw) + g.bottom + fw + ) if pw > 0: # Draw the background rectangles over the padding region - color = 0 + color = self.background border( color, pw, g.left - pw, g.top - pw, g.right + pw, - g.bottom + pw) + g.bottom + pw + ) + send_borders_rects(viewport_size.width, viewport_size.height) - self.num_of_rects = len(rects) // 4 - self.rects = (GLuint * len(rects))() - for i, x in enumerate(rects): - self.rects[i] = x - self.is_dirty = True - self.can_render = True - - def render(self, program): - if not self.can_render: - return - with program: - if self.is_dirty: - program.send_data(self.rects) - program.set_colors(self.color_buf) - self.is_dirty = False - with program.bound_vertex_array(program.vao_id): - glDrawArraysInstanced( - GL_TRIANGLE_FAN, 0, 4, self.num_of_rects) + def render(self): + draw_borders() diff --git a/kitty/boss.py b/kitty/boss.py index 9e8cc333f..14f56154b 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -6,7 +6,6 @@ from gettext import gettext as _ from time import monotonic from weakref import WeakValueDictionary -from .borders import BordersProgram from .char_grid import load_shader_programs from .config import MINIMUM_FONT_SIZE from .constants import ( @@ -121,7 +120,6 @@ class Boss: self.sprites = Sprites() self.sprites.do_layout(cell_size.width, cell_size.height) self.cell_program = load_shader_programs() - self.borders_program = BordersProgram() glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) self.glfw_window.set_click_cursor(False) self.show_mouse_cursor() diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 86d91e0cf..4e6982223 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -427,7 +427,7 @@ render(ChildMonitor *self, double *timeout) { double time_since_last_render = now - last_render_at; if (time_since_last_render > self->repaint_delay) { ret = PyObject_CallFunctionObjArgs(self->render_func, NULL); - if (ret == NULL) return false; + if (ret == NULL) { PyErr_Print(); return false; } else Py_DECREF(ret); glfwSwapBuffers(glfw_window_id); last_render_at = now; diff --git a/kitty/shaders.c b/kitty/shaders.c index 0863382d4..ed9719b10 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -201,10 +201,11 @@ create_buffer(GLenum usage) { glGenBuffers(1, &buffer_id); if (set_error_from_gl()) return -1; for (size_t i = 0; i < sizeof(buffers)/sizeof(buffers[0]); i++) { - if (!buffers[i].id) { + if (buffers[i].id == 0) { buffers[i].id = buffer_id; buffers[i].size = 0; buffers[i].usage = usage; + return i; } } glDeleteBuffers(1, &buffer_id); @@ -283,13 +284,13 @@ create_vao() { } static bool -add_buffer_to_vao(ssize_t vao_idx, GLenum usage) { +add_buffer_to_vao(ssize_t vao_idx) { VAO* vao = vaos + vao_idx; if (vao->num_buffers >= sizeof(vao->buffers) / sizeof(vao->buffers[0])) { set_local_error("too many buffers in a single VAO"); return false; } - ssize_t buf = create_buffer(usage); + ssize_t buf = create_buffer(GL_ARRAY_BUFFER); if (buf < 0) return false; vao->buffers[vao->num_buffers++] = buf; return true; @@ -351,12 +352,11 @@ unbind_vertex_array() { } static void* -map_vao_buffer(ssize_t vao_idx, size_t bufnum, GLsizeiptr size, GLenum usage, GLenum access) { +map_vao_buffer(ssize_t vao_idx, GLsizeiptr size, size_t bufnum, GLenum usage, GLenum access) { ssize_t buf_idx = vaos[vao_idx].buffers[bufnum]; bind_buffer(buf_idx); alloc_buffer(buf_idx, size, usage); void *ans = map_buffer(buf_idx, access); - unbind_buffer(buf_idx); return ans; } @@ -364,6 +364,7 @@ static void unmap_vao_buffer(ssize_t vao_idx, size_t bufnum) { ssize_t buf_idx = vaos[vao_idx].buffers[bufnum]; unmap_buffer(buf_idx); + unbind_buffer(buf_idx); } // }}} @@ -387,6 +388,7 @@ init_cursor_program() { else { set_local_error("Unknown uniform in cursor program"); return false; } } if (left) { set_local_error("Left over uniforms in cursor program"); return false; } +#undef SET_LOC return true; } @@ -403,6 +405,75 @@ draw_cursor(bool semi_transparent, bool is_focused, color_type color, float alph } // }}} +// Borders {{{ +enum BorderUniforms { BORDER_viewport, NUM_BORDER_UNIFORMS }; +static GLint border_uniform_locations[NUM_BORDER_UNIFORMS] = {0}; +static ssize_t border_vertex_array; +static GLsizei num_border_rects = 0; +static GLuint rect_buf[5 * 1024]; +static GLuint *rect_pos = NULL; + +static bool +init_borders_program() { + Program *p = programs + BORDERS_PROGRAM; + int left = NUM_BORDER_UNIFORMS; + border_vertex_array = create_vao(); + if (set_error_from_gl()) return false; + 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 + SET_LOC(viewport); + else { set_local_error("Unknown uniform in borders program"); return false; } + } + if (left) { set_local_error("Left over uniforms in borders program"); return false; } +#undef SET_LOC + add_buffer_to_vao(border_vertex_array); + if (set_error_from_gl()) return false; + add_attribute_to_vao(BORDERS_PROGRAM, border_vertex_array, "rect", + /*size=*/4, /*dtype=*/GL_UNSIGNED_INT, /*stride=*/sizeof(GLuint)*5, /*offset=*/0, /*divisor=*/1); + if (set_error_from_gl()) return false; + add_attribute_to_vao(BORDERS_PROGRAM, border_vertex_array, "rect_color", + /*size=*/1, /*dtype=*/GL_UNSIGNED_INT, /*stride=*/sizeof(GLuint)*5, /*offset=*/(void*)(sizeof(GLuint)*4), /*divisor=*/1); + if (set_error_from_gl()) return false; + return true; +} + +static void +draw_borders() { + if (num_border_rects) { + bind_program(BORDERS_PROGRAM); + bind_vertex_array(border_vertex_array); + glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, num_border_rects); + unbind_vertex_array(); + unbind_program(); + } +} + +static void +add_borders_rect(GLuint left, GLuint top, GLuint right, GLuint bottom, GLuint color) { + if (!left && !top && !right && !bottom) { num_border_rects = 0; rect_pos = rect_buf; return; } + num_border_rects++; + *(rect_pos++) = left; + *(rect_pos++) = top; + *(rect_pos++) = right; + *(rect_pos++) = bottom; + *(rect_pos++) = color; +} + +static void +send_borders_rects(GLuint vw, GLuint vh) { + if (num_border_rects) { + size_t sz = sizeof(GLuint) * 5 * num_border_rects; + void *borders_buf_address = map_vao_buffer(border_vertex_array, sz, 0, GL_STATIC_DRAW, GL_WRITE_ONLY); + if (borders_buf_address) memcpy(borders_buf_address, rect_buf, sz); + unmap_vao_buffer(border_vertex_array, 0); + } + bind_program(BORDERS_PROGRAM); + printf(gl_strerror(glGetError())); + glUniform2ui(border_uniform_locations[BORDER_viewport], vw, vh); + unbind_program(); +} +// }}} + // Python API {{{ static PyObject* enable_automatic_opengl_error_checking(PyObject UNUSED *self, PyObject *val) { @@ -450,13 +521,14 @@ compile_program(PyObject UNUSED *self, PyObject *args) { end: if (vertex_shader_id != 0) glDeleteShader(vertex_shader_id); if (fragment_shader_id != 0) glDeleteShader(fragment_shader_id); + set_error_from_gl(); translate_error(); if (PyErr_Occurred()) { glDeleteProgram(programs[which].id); programs[which].id = 0; return NULL;} return Py_BuildValue("I", programs[which].id); Py_RETURN_NONE; } -#define CHECK_ERROR_ALWAYS { translate_error(); if (PyErr_Occurred()) return NULL; } +#define CHECK_ERROR_ALWAYS { set_error_from_gl(); translate_error(); if (PyErr_Occurred()) return NULL; } #define CHECK_ERROR if (_enable_error_checking) CHECK_ERROR_ALWAYS #define PYWRAP0(name) static PyObject* py##name(PyObject UNUSED *self) #define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args) @@ -466,6 +538,7 @@ end: #define TWO_INT(name) PYWRAP1(name) { int a, b; PA("ii", &a, &b); name(a, b); CHECK_ERROR; Py_RETURN_NONE; } #define NO_ARG(name) PYWRAP0(name) { name(); CHECK_ERROR; Py_RETURN_NONE; } #define NO_ARG_CHECK(name) PYWRAP0(name) { name(); CHECK_ERROR_ALWAYS; Py_RETURN_NONE; } +#define ONE_INT_CHECK(name) PYWRAP1(name) { name(PyLong_AsSsize_t(args)); CHECK_ERROR_ALWAYS; Py_RETURN_NONE; } ONE_INT(bind_program) NO_ARG(unbind_program) @@ -477,29 +550,29 @@ PYWRAP0(create_vao) { } ONE_INT(remove_vao) - -PYWRAP1(add_buffer_to_vao) { - int vao_idx, usage; - PA("ii", &vao_idx, &usage); - if (!add_buffer_to_vao(vao_idx, usage)) return NULL; - Py_RETURN_NONE; -} +ONE_INT_CHECK(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; + PyObject *offset = NULL; static char* keywords[] = {"program", "vao", "name", "size", "dtype", "stride", "offset", "divisor", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kw, "i i s | i i I O! I", keywords, &program, &vao, &name, &size, &data_type, &stride, &offset, &PyLong_Type, &divisor)) return NULL; - if (!add_attribute_to_vao(program, vao, name, size, data_type, stride, PyLong_AsVoidPtr(offset), divisor)) return NULL; + if (!PyArg_ParseTupleAndKeywords(args, kw, "iis|iiIO!I", keywords, &program, &vao, &name, &size, &data_type, &stride, &PyLong_Type, &offset, &divisor)) return NULL; + if (!add_attribute_to_vao(program, vao, name, size, data_type, stride, offset ? PyLong_AsVoidPtr(offset) : NULL, divisor)) { translate_error(); return NULL; } Py_RETURN_NONE; } ONE_INT(bind_vertex_array) NO_ARG(unbind_vertex_array) TWO_INT(unmap_vao_buffer) -PYWRAP1(map_vao_buffer) { int a,b,c,d,e; PA("iiiii", &a, &b, &c, &d, &e); void *ans = map_vao_buffer(a, b, c, d, e); CHECK_ERROR; return PyLong_FromVoidPtr(ans); } +PYWRAP1(map_vao_buffer) { + int vao_idx, bufnum=0, size, usage=GL_STREAM_DRAW, access=GL_WRITE_ONLY; + PA("ii|iii", &vao_idx, &size, &bufnum, &usage, &access); + void *ans = map_vao_buffer(vao_idx, size, bufnum, usage, access); + CHECK_ERROR; + return PyLong_FromVoidPtr(ans); +} NO_ARG_CHECK(init_cursor_program) PYWRAP1(draw_cursor) { @@ -512,6 +585,11 @@ PYWRAP1(draw_cursor) { Py_RETURN_NONE; } +NO_ARG_CHECK(init_borders_program) +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); CHECK_ERROR; Py_RETURN_NONE; } +TWO_INT(send_borders_rects) + #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[] = { @@ -520,8 +598,8 @@ 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_VARARGS), - MW(add_attribute_to_vao, METH_VARARGS), + 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), @@ -530,6 +608,10 @@ static PyMethodDef module_methods[] = { MW(unbind_program, METH_NOARGS), MW(init_cursor_program, METH_NOARGS), MW(draw_cursor, METH_VARARGS), + MW(init_borders_program, METH_NOARGS), + MW(draw_borders, METH_NOARGS), + MW(add_borders_rect, METH_VARARGS), + MW(send_borders_rects, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/tabs.py b/kitty/tabs.py index 099ebecc8..67a2ce5bf 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -20,6 +20,7 @@ from .utils import color_as_int from .window import Window TabbarData = namedtuple('TabbarData', 'title is_active is_last') +borders = None def SpecialWindow(cmd, stdin=None, override_title=None): @@ -29,11 +30,13 @@ def SpecialWindow(cmd, stdin=None, override_title=None): class Tab: def __init__(self, opts, args, on_title_change, session_tab=None, special_window=None): + global borders self.opts, self.args = opts, args self.name = getattr(session_tab, 'name', '') self.on_title_change = on_title_change self.enabled_layouts = list(getattr(session_tab, 'enabled_layouts', None) or opts.enabled_layouts) - self.borders = Borders(opts) + if borders is None: + borders = Borders(opts) self.windows = deque() self.active_window_idx = 0 for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()): @@ -41,7 +44,7 @@ class Tab: if session_tab is None: self.cwd = args.directory l = self.enabled_layouts[0] - self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows) + self.current_layout = all_layouts[l](opts, borders.border_width, self.windows) if special_window is None: self.new_window() else: @@ -49,7 +52,7 @@ class Tab: else: self.cwd = session_tab.cwd or args.directory l = session_tab.layout - self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows) + self.current_layout = all_layouts[l](opts, borders.border_width, self.windows) self.startup(session_tab) def startup(self, session_tab): @@ -85,8 +88,8 @@ class Tab: def relayout_borders(self): tm = get_boss().tab_manager - self.borders(self.windows, self.active_window, self.current_layout, tm.blank_rects, - self.current_layout.needs_window_borders and len(self.windows) > 1) + borders(self.windows, self.active_window, self.current_layout, tm.blank_rects, + self.current_layout.needs_window_borders and len(self.windows) > 1) def next_layout(self): if len(self.opts.enabled_layouts) > 1: @@ -95,7 +98,7 @@ class Tab: except Exception: idx = -1 nl = self.opts.enabled_layouts[(idx + 1) % len(self.opts.enabled_layouts)] - self.current_layout = all_layouts[nl](self.opts, self.borders.border_width, self.windows) + self.current_layout = all_layouts[nl](self.opts, borders.border_width, self.windows) for w in self.windows: w.is_visible_in_layout = True self.relayout() @@ -193,7 +196,7 @@ class Tab: self.windows = deque() def render(self): - self.borders.render(get_boss().borders_program) + borders.render() def __repr__(self): return 'Tab(title={}, id={})'.format(self.name or self.title, hex(id(self))) @@ -278,7 +281,7 @@ class TabBar: 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, 0, True) + 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)