From 03e0b9de0429842b232b9a737d07adea463f0095 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 22 Aug 2017 08:55:29 +0530 Subject: [PATCH] Use vertex buffers instead of texture buffers Apparently, integer samplers for textFetch() are broken on some macOS Intel drivers. Fixes #101 --- kitty/borders.py | 10 ++--- kitty/boss.py | 7 ++-- kitty/cell_vertex.glsl | 16 +++----- kitty/char_grid.py | 53 +++++++++++++++---------- kitty/gl.h | 26 ++++++++---- kitty/shaders.py | 90 +++++++++++++++++------------------------- kitty/tabs.py | 32 +++++++++------ 7 files changed, 121 insertions(+), 113 deletions(-) diff --git a/kitty/borders.py b/kitty/borders.py index a198e8637..fc0d4a624 100644 --- a/kitty/borders.py +++ b/kitty/borders.py @@ -145,12 +145,12 @@ class Borders: if not self.can_render: return with program: - program.bind_vertex_array(program.vao_id) if self.is_dirty: program.send_data(self.rects) program.set_colors(self.color_buf) self.is_dirty = False - glMultiDrawArrays( - GL_TRIANGLE_FAN, - addressof(self.starts), - addressof(self.counts), self.num_of_rects) + with program.bound_vertex_array(program.vao_id): + glMultiDrawArrays( + GL_TRIANGLE_FAN, + addressof(self.starts), + addressof(self.counts), self.num_of_rects) diff --git a/kitty/boss.py b/kitty/boss.py index 36ee2db21..d4a36c6eb 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -422,7 +422,8 @@ class Boss(Thread): with self.sprites: self.sprites.render_dirty_cells() tab.render() - render_data = {window: window.char_grid.prepare_for_render(self.sprites) for window in tab.visible_windows() if not window.needs_layout} + render_data = {window: window.char_grid.prepare_for_render(self.cell_program) + for window in tab.visible_windows() if not window.needs_layout} with self.cell_program: self.tab_manager.render(self.cell_program, self.sprites) for window, rd in render_data.items(): @@ -444,9 +445,7 @@ class Boss(Thread): active.char_grid.render_cursor(rd, self.cursor_program, self.window_is_focused) def gui_close_window(self, window): - if window.char_grid.buffer_id is not None: - self.sprites.destroy_sprite_map(window.char_grid.buffer_id) - window.char_grid.buffer_id = None + window.char_grid.destroy(self.cell_program) for tab in self.tab_manager: if window in tab: break diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index d7845141d..f123bd33d 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -1,8 +1,9 @@ uniform uvec2 dimensions; // xnum, ynum uniform vec4 steps; // xstart, ystart, dx, dy uniform vec2 sprite_layout; // dx, dy -uniform usamplerBuffer sprite_map; // gl_InstanceID -> x, y, z uniform ivec2 color_indices; // which color to use as fg and which as bg +in uvec3 sprite_coords; +in uvec3 colors; out vec3 sprite_pos; out vec3 underline_pos; out vec3 strike_pos; @@ -46,15 +47,10 @@ void main() { uvec2 pos = pos_map[gl_VertexID]; gl_Position = vec4(xpos[pos[0]], ypos[pos[1]], 0, 1); - int sprite_id = gl_InstanceID * STRIDE; - uint x = texelFetch(sprite_map, sprite_id).r; - uint y = texelFetch(sprite_map, sprite_id + 1).r; - uint z = texelFetch(sprite_map, sprite_id + 2).r; - sprite_pos = to_sprite_pos(pos, x, y, z); - sprite_id += 3; - uint fg = texelFetch(sprite_map, sprite_id + color_indices[0]).r; - uint bg = texelFetch(sprite_map, sprite_id + color_indices[1]).r; - uint decoration = texelFetch(sprite_map, sprite_id + 2).r; + sprite_pos = to_sprite_pos(pos, sprite_coords.x, sprite_coords.y, sprite_coords.z); + uint fg = colors[color_indices[0]]; + uint bg = colors[color_indices[1]]; + uint decoration = colors[2]; foreground = to_color(fg); background = to_color(bg); decoration_fg = to_color(decoration); diff --git a/kitty/char_grid.py b/kitty/char_grid.py index ef546cd19..7d3b9dfb3 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -4,9 +4,9 @@ import re import sys -from enum import Enum from collections import namedtuple from ctypes import addressof, memmove, sizeof +from enum import Enum from threading import Lock from .config import build_ansi_color_table, defaults @@ -15,12 +15,12 @@ from .constants import ( ) from .fast_data_types import ( CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, DATA_CELL_SIZE, GL_BLEND, - GL_LINE_LOOP, GL_TRIANGLE_FAN, ColorProfile, glDisable, glDrawArrays, - glDrawArraysInstanced, glEnable, glUniform1i, glUniform2f, glUniform2ui, - glUniform4f, glUniform2i + GL_LINE_LOOP, GL_STREAM_DRAW, GL_TRIANGLE_FAN, GL_UNSIGNED_INT, + ColorProfile, glDisable, glDrawArrays, glDrawArraysInstanced, glEnable, + glUniform1i, glUniform2f, glUniform2i, glUniform2ui, glUniform4f ) from .rgb import to_color -from .shaders import load_shaders, ShaderProgram +from .shaders import ShaderProgram, load_shaders from .utils import ( color_as_int, color_from_int, get_logical_dpi, open_url, safe_print, set_primary_selection @@ -33,12 +33,22 @@ class DynamicColor(Enum): default_fg, default_bg, cursor_color, highlight_fg, highlight_bg = range(1, 6) +class CellProgram(ShaderProgram): + + def create_sprite_map(self): + stride = DATA_CELL_SIZE * sizeof(GLuint) + size = DATA_CELL_SIZE // 2 + return self.add_vertex_arrays( + self.vertex_array('sprite_coords', size=size, dtype=GL_UNSIGNED_INT, stride=stride, divisor=1), + self.vertex_array('colors', size=size, dtype=GL_UNSIGNED_INT, stride=stride, offset=stride // 2, divisor=1), + ) + + def load_shader_programs(): vert, frag = load_shaders('cell') vert = vert.replace('STRIDE', str(DATA_CELL_SIZE)) - cell = ShaderProgram(vert, frag) + cell = CellProgram(vert, frag) cursor = ShaderProgram(*load_shaders('cursor')) - cell.vao_id = cell.add_vertex_arrays() cursor.vao_id = cursor.add_vertex_arrays() return cell, cursor @@ -112,17 +122,15 @@ def calculate_gl_geometry(window_geometry): return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy) -def render_cells(buffer_id, sg, cell_program, sprites, invert_colors=False): - sprites.bind_sprite_map(buffer_id) +def render_cells(vao_id, sg, cell_program, sprites, invert_colors=False): ul = cell_program.uniform_location glUniform2ui(ul('dimensions'), sg.xnum, sg.ynum) glUniform2i(ul('color_indices'), 1 if invert_colors else 0, 0 if invert_colors else 1) glUniform4f(ul('steps'), sg.xstart, sg.ystart, sg.dx, sg.dy) glUniform1i(ul('sprites'), sprites.sampler_num) - glUniform1i(ul('sprite_map'), sprites.buffer_sampler_num) glUniform2f(ul('sprite_layout'), *(sprites.layout)) - cell_program.bind_vertex_array(cell_program.vao_id) - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, sg.xnum * sg.ynum) + with cell_program.bound_vertex_array(vao_id): + glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, sg.xnum * sg.ynum) class CharGrid: @@ -131,7 +139,7 @@ class CharGrid: def __init__(self, screen, opts): self.buffer_lock = Lock() - self.buffer_id = None + self.vao_id = None self.current_selection = Selection() self.last_rendered_selection = self.current_selection.limits(0, screen.lines, screen.columns) self.render_buf_is_dirty = True @@ -159,6 +167,11 @@ class CharGrid: safe_print('Invalid characters in select_by_word_characters, ignoring', file=sys.stderr) self.word_pat = re.compile(r'[\w{}]'.format(escape(defaults.select_by_word_characters)), re.UNICODE) + def destroy(self, cell_program): + if self.vao_id is not None: + cell_program.remove_vertex_array(self.vao_id) + self.vao_id = None + def update_position(self, window_geometry): self.screen_geometry = calculate_gl_geometry(window_geometry) @@ -343,13 +356,13 @@ class CharGrid: s = sel or self.current_selection return s.text(self.screen.linebuf, self.screen.historybuf) - def prepare_for_render(self, sprites): + def prepare_for_render(self, cell_program): with self.buffer_lock: sg = self.render_data if sg is None: return - if self.buffer_id is None: - self.buffer_id = sprites.add_sprite_map() + if self.vao_id is None: + self.vao_id = cell_program.create_sprite_map() buf = self.render_buf start, end = sel = self.current_selection.limits(self.scrolled_by, self.screen.lines, self.screen.columns) if start != end: @@ -362,13 +375,13 @@ class CharGrid: bg = bg >> 8 if bg & 2 else self.highlight_bg self.screen.apply_selection(addressof(buf), start[0], start[1], end[0], end[1], fg, bg) if self.render_buf_is_dirty or self.last_rendered_selection != sel: - sprites.set_sprite_map(self.buffer_id, buf) + cell_program.send_vertex_data(self.vao_id, buf, usage=GL_STREAM_DRAW) self.render_buf_is_dirty = False self.last_rendered_selection = sel return sg def render_cells(self, sg, cell_program, sprites, invert_colors=False): - render_cells(self.buffer_id, sg, cell_program, sprites, invert_colors=invert_colors) + render_cells(self.vao_id, sg, cell_program, sprites, invert_colors=invert_colors) def render_cursor(self, sg, cursor_program, is_focused): cursor = self.current_cursor @@ -398,6 +411,6 @@ class CharGrid: glUniform4f(ul('color'), col[0] / 255.0, col[1] / 255.0, col[2] / 255.0, alpha) glUniform2f(ul('xpos'), left, right) glUniform2f(ul('ypos'), top, bottom) - cursor_program.bind_vertex_array(cursor_program.vao_id) - glDrawArrays(GL_TRIANGLE_FAN if is_focused else GL_LINE_LOOP, 0, 4) + with cursor_program.bound_vertex_array(cursor_program.vao_id): + glDrawArrays(GL_TRIANGLE_FAN if is_focused else GL_LINE_LOOP, 0, 4) glDisable(GL_BLEND) diff --git a/kitty/gl.h b/kitty/gl.h index f06a86396..94cf7b159 100644 --- a/kitty/gl.h +++ b/kitty/gl.h @@ -665,13 +665,23 @@ EnableVertexAttribArray(PyObject UNUSED *self, PyObject *val) { static PyObject* VertexAttribPointer(PyObject UNUSED *self, PyObject *args) { - unsigned int index, stride; - int type=GL_FLOAT, normalized, size; - void *offset; - PyObject *l; - if (!PyArg_ParseTuple(args, "IiipIO!", &index, &size, &type, &normalized, &stride, &PyLong_Type, &l)) return NULL; - offset = PyLong_AsVoidPtr(l); - glVertexAttribPointer(index, size, type, normalized, stride, offset); + unsigned int index; + int type, normalized, size, stride; + unsigned long offset; + if (!PyArg_ParseTuple(args, "Iiipik", &index, &size, &type, &normalized, &stride, &offset)) return NULL; + switch(type) { + case GL_BYTE: + case GL_UNSIGNED_BYTE: + case GL_SHORT: + case GL_UNSIGNED_SHORT: + case GL_INT: + case GL_UNSIGNED_INT: + glVertexAttribIPointer(index, size, type, stride, (GLvoid*)offset); + break; + default: + glVertexAttribPointer(index, size, type, normalized ? GL_TRUE : GL_FALSE, stride, (GLvoid*)offset); + break; + } CHECK_ERROR; Py_RETURN_NONE; } @@ -744,7 +754,7 @@ int add_module_gl_constants(PyObject *module) { GLC(GL_R8); GLC(GL_RED); GLC(GL_UNSIGNED_BYTE); GLC(GL_R32UI); GLC(GL_RGB32UI); GLC(GL_RGBA); GLC(GL_TEXTURE_BUFFER); GLC(GL_STATIC_DRAW); GLC(GL_STREAM_DRAW); GLC(GL_SRC_ALPHA); GLC(GL_ONE_MINUS_SRC_ALPHA); - GLC(GL_BLEND); GLC(GL_FLOAT); GLC(GL_ARRAY_BUFFER); + GLC(GL_BLEND); GLC(GL_FLOAT); GLC(GL_UNSIGNED_INT); GLC(GL_ARRAY_BUFFER); return 1; } diff --git a/kitty/shaders.py b/kitty/shaders.py index 233e4fdcf..e681b23e5 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -5,6 +5,7 @@ import os import sys from collections import namedtuple +from contextlib import contextmanager from ctypes import addressof, sizeof from functools import lru_cache from threading import Lock @@ -12,22 +13,21 @@ from threading import Lock from .fast_data_types import ( BOLD, GL_ARRAY_BUFFER, GL_CLAMP_TO_EDGE, GL_COMPILE_STATUS, GL_FLOAT, GL_FRAGMENT_SHADER, GL_LINK_STATUS, GL_MAX_ARRAY_TEXTURE_LAYERS, - GL_MAX_TEXTURE_BUFFER_SIZE, GL_MAX_TEXTURE_SIZE, GL_NEAREST, GL_R8, - GL_R32UI, GL_RED, GL_STATIC_DRAW, GL_STREAM_DRAW, GL_TEXTURE0, - GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BUFFER, GL_TEXTURE_MAG_FILTER, - GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, GL_TRUE, - GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_VERTEX_SHADER, ITALIC, SpriteMap, - copy_image_sub_data, glActiveTexture, glAttachShader, glBindBuffer, - glBindTexture, glBindVertexArray, glCompileShader, glCopyImageSubData, - glCreateProgram, glCreateShader, glDeleteBuffer, glDeleteProgram, - glDeleteShader, glDeleteTexture, glDeleteVertexArray, - glEnableVertexAttribArray, glGenBuffers, glGenTextures, glGenVertexArrays, - glGetAttribLocation, glGetBufferSubData, glGetIntegerv, - glGetProgramInfoLog, glGetProgramiv, glGetShaderInfoLog, glGetShaderiv, - glGetUniformLocation, glLinkProgram, glPixelStorei, glShaderSource, - glTexBuffer, glTexParameteri, glTexStorage3D, glTexSubImage3D, - glUseProgram, glVertexAttribDivisor, glVertexAttribPointer, - replace_or_create_buffer + GL_MAX_TEXTURE_SIZE, GL_NEAREST, GL_R8, GL_R32UI, GL_RED, GL_STATIC_DRAW, + GL_STREAM_DRAW, GL_TEXTURE0, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BUFFER, + GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S, + GL_TEXTURE_WRAP_T, GL_TRUE, GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, + GL_VERTEX_SHADER, ITALIC, SpriteMap, copy_image_sub_data, glActiveTexture, + glAttachShader, glBindBuffer, glBindTexture, glBindVertexArray, + glCompileShader, glCopyImageSubData, glCreateProgram, glCreateShader, + glDeleteBuffer, glDeleteProgram, glDeleteShader, glDeleteTexture, + glDeleteVertexArray, glEnableVertexAttribArray, glGenBuffers, + glGenTextures, glGenVertexArrays, glGetAttribLocation, glGetBufferSubData, + glGetIntegerv, glGetProgramInfoLog, glGetProgramiv, glGetShaderInfoLog, + glGetShaderiv, glGetUniformLocation, glLinkProgram, glPixelStorei, + glShaderSource, glTexBuffer, glTexParameteri, glTexStorage3D, + glTexSubImage3D, glUseProgram, glVertexAttribDivisor, + glVertexAttribPointer, replace_or_create_buffer ) from .fonts.render import render_cell from .utils import safe_print @@ -83,6 +83,12 @@ class BufferManager: # {{{ 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) + buffer_manager = BufferManager() # }}} @@ -104,11 +110,8 @@ class Sprites: # {{{ self.last_num_of_layers = 1 self.last_ynum = -1 self.sampler_num = 0 - self.buffer_sampler_num = 1 self.texture_unit = GL_TEXTURE0 + self.sampler_num - self.buffer_texture_unit = GL_TEXTURE0 + self.buffer_sampler_num self.backend = SpriteMap(glGetIntegerv(GL_MAX_TEXTURE_SIZE), glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS)) - self.max_texture_buffer_size = glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE) self.lock = Lock() def do_layout(self, cell_width=1, cell_height=1): @@ -210,31 +213,11 @@ class Sprites: # {{{ if self.buffer_texture_id is None: self.buffer_texture_id = glGenTextures(1) - def add_sprite_map(self): - return buffer_manager.create() - - def set_sprite_map(self, buf_id, data, usage=GL_STREAM_DRAW): - new_sz = sizeof(data) - if new_sz // 4 > self.max_texture_buffer_size: - raise RuntimeError(('The OpenGL implementation on your system has a max_texture_buffer_size of {} which' - ' is insufficient for the sprite_map').format(self.max_texture_buffer_size)) - buffer_manager.set_data(buf_id, data, usage) - - def bind_sprite_map(self, buf_id): - buffer_manager.bind(buf_id) - glTexBuffer(GL_TEXTURE_BUFFER, GL_R32UI, buf_id) - - def destroy_sprite_map(self, buf_id): - buffer_manager.delete(buf_id) - def __enter__(self): self.ensure_state() glActiveTexture(self.texture_unit) glBindTexture(GL_TEXTURE_2D_ARRAY, self.texture_id) - glActiveTexture(self.buffer_texture_unit) - glBindTexture(GL_TEXTURE_BUFFER, self.buffer_texture_id) - def __exit__(self, *a): glBindTexture(GL_TEXTURE_2D_ARRAY, 0) glBindTexture(GL_TEXTURE_BUFFER, 0) @@ -243,7 +226,7 @@ class Sprites: # {{{ # }}} -class ShaderProgram: +class ShaderProgram: # {{{ """ Helper class for using GLSL shader programs """ def __init__(self, vertex, fragment): @@ -275,20 +258,21 @@ class ShaderProgram: def add_vertex_arrays(self, *arrays): vao_id = glGenVertexArrays(1) self.vertex_arrays[vao_id] = buf_id = buffer_manager.create(for_use=GL_ARRAY_BUFFER) - glBindVertexArray(vao_id) - buffer_manager.bind(buf_id) - for x in arrays: - aid = self.attribute_location(x.name) - glEnableVertexAttribArray(aid) - glVertexAttribPointer(aid, x.size, x.dtype, x.normalized, x.stride, x.offset) - if x.divisor > 0: - glVertexAttribDivisor(aid, x.divisor) - buffer_manager.unbind(buf_id) - glBindVertexArray(0) - return vao_id + with self.bound_vertex_array(vao_id), buffer_manager.bound_buffer(buf_id): + for x in arrays: + aid = self.attribute_location(x.name) + if aid > -1: + glEnableVertexAttribArray(aid) + glVertexAttribPointer(aid, x.size, x.dtype, x.normalized, x.stride, x.offset) + if x.divisor > 0: + glVertexAttribDivisor(aid, x.divisor) + return vao_id - def bind_vertex_array(self, vao_id): + @contextmanager + def bound_vertex_array(self, vao_id): glBindVertexArray(vao_id) + yield + glBindVertexArray(0) def remove_vertex_array(self, vao_id): buf_id = self.vertex_arrays.pop(vao_id, None) @@ -339,4 +323,4 @@ class ShaderProgram: def __exit__(self, *args): glUseProgram(0) - glBindVertexArray(0) +# }}} diff --git a/kitty/tabs.py b/kitty/tabs.py index cbf8bf2c8..6826dd41a 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -3,18 +3,24 @@ # License: GPL v3 Copyright: 2016, Kovid Goyal from collections import deque +from ctypes import addressof from functools import partial from threading import Lock -from ctypes import addressof +from .borders import Borders +from .char_grid import calculate_gl_geometry, render_cells from .child import Child from .config import build_ansi_color_table -from .constants import get_boss, appname, shell_path, cell_size, queue_action, viewport_size, WindowGeometry, GLuint -from .fast_data_types import glfw_post_empty_event, Screen, DECAWM, DATA_CELL_SIZE, ColorProfile -from .char_grid import calculate_gl_geometry, render_cells -from .layout import all_layouts, Rect +from .constants import ( + GLuint, WindowGeometry, appname, cell_size, get_boss, queue_action, + shell_path, viewport_size +) +from .fast_data_types import ( + DATA_CELL_SIZE, DECAWM, GL_STREAM_DRAW, ColorProfile, Screen, + glfw_post_empty_event +) +from .layout import Rect, all_layouts from .utils import color_as_int -from .borders import Borders from .window import Window @@ -202,7 +208,7 @@ class TabManager: def __init__(self, opts, args, startup_session): self.opts, self.args = opts, args - self.buffer_id = None + self.vao_id = None self.tabbar_lock = Lock() self.tabs = [Tab(opts, args, self.title_changed, t) for t in startup_session.tabs] self.cell_ranges = [] @@ -306,7 +312,7 @@ class TabManager: if needs_resize: queue_action(get_boss().tabbar_visibility_changed) - def update_tab_bar_data(self, sprites): + def update_tab_bar_data(self, sprites, cell_program): s = self.tab_bar_screen s.cursor.x = 0 s.erase_in_line(2, False) @@ -336,9 +342,9 @@ class TabManager: s.update_cell_data( sprites.backend, self.color_profile, addressof(self.sprite_map), self.default_fg, self.default_bg, True) sprites.render_dirty_cells() - if self.buffer_id is None: - self.buffer_id = sprites.add_sprite_map() - sprites.set_sprite_map(self.buffer_id, self.sprite_map) + if self.vao_id is None: + self.vao_id = cell_program.create_sprite_map() + cell_program.send_vertex_data(self.vao_id, self.sprite_map, usage=GL_STREAM_DRAW) def activate_tab_at(self, x): x = (x - self.window_geometry.left) // cell_size.width @@ -358,5 +364,5 @@ class TabManager: return with self.tabbar_lock: if self.tabbar_dirty: - self.update_tab_bar_data(sprites) - render_cells(self.buffer_id, self.screen_geometry, cell_program, sprites) + self.update_tab_bar_data(sprites, cell_program) + render_cells(self.vao_id, self.screen_geometry, cell_program, sprites)