Use vertex buffers instead of texture buffers

Apparently, integer samplers for textFetch() are broken on some macOS
Intel drivers. Fixes #101
This commit is contained in:
Kovid Goyal 2017-08-22 08:55:29 +05:30
parent 4364163ceb
commit 03e0b9de04
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
7 changed files with 121 additions and 113 deletions

View file

@ -145,12 +145,12 @@ def render(self, program):
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)

View file

@ -422,7 +422,8 @@ def render(self):
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 @@ def render(self):
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

View file

@ -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);

View file

@ -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 .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 @@ def escape(chars):
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 @@ def text_for_selection(self, sel=None):
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 @@ def prepare_for_render(self, sprites):
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 @@ def width(w=2, vert=True):
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)

View file

@ -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;
}

View file

@ -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 .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 @@ def bind(self, 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)
buffer_manager = BufferManager()
# }}}
@ -104,11 +110,8 @@ def __init__(self):
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 @@ def ensure_state(self):
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 @@ def __exit__(self, *a):
# }}}
class ShaderProgram:
class ShaderProgram: # {{{
""" Helper class for using GLSL shader programs """
def __init__(self, vertex, fragment):
@ -275,20 +258,21 @@ def vertex_array(self, name, size=3, dtype=GL_FLOAT, normalized=False, stride=0,
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 @@ def __enter__(self):
def __exit__(self, *args):
glUseProgram(0)
glBindVertexArray(0)
# }}}

View file

@ -3,18 +3,24 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
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 @@ def remove(self, tab):
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 @@ def update_tab_bar_data(self, sprites):
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 @@ def render(self, cell_program, sprites):
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)