Migrate the cell program

This commit is contained in:
Kovid Goyal 2017-09-11 22:41:48 +05:30
parent 2fff6e1cb9
commit 44f456089b
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
13 changed files with 366 additions and 540 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"},

View File

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

View File

@ -6,6 +6,7 @@
*/
#include "data-types.h"
#include "screen.h"
#ifdef __APPLE__
#include <OpenGL/gl3.h>
#include <OpenGL/gl3ext.h>
@ -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 */
};

View File

@ -2,114 +2,25 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
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)
# }}}

View File

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

View File

@ -3,7 +3,6 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
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()

View File

@ -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:

View File

@ -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 {{{