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, BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program,
send_borders_rects send_borders_rects
) )
from .shaders import load_shaders from .utils import color_as_int, pt_to_px, load_shaders
from .utils import color_as_int, pt_to_px
def vertical_edge(color, width, top, bottom, left): 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.scroll_callback = self.on_mouse_scroll
glfw_window.cursor_pos_callback = self.on_mouse_move glfw_window.cursor_pos_callback = self.on_mouse_move
glfw_window.window_focus_callback = self.on_focus glfw_window.window_focus_callback = self.on_focus
load_shader_programs()
self.tab_manager = TabManager(opts, args) self.tab_manager = TabManager(opts, args)
self.tab_manager.init(startup_session) self.tab_manager.init(startup_session)
self.sprites = Sprites() self.sprites = Sprites()
self.sprites.do_layout(cell_size.width, cell_size.height) 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) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
self.glfw_window.set_click_cursor(False) self.glfw_window.set_click_cursor(False)
self.show_mouse_cursor() self.show_mouse_cursor()
@ -376,11 +376,10 @@ class Boss:
with self.sprites: with self.sprites:
self.sprites.render_dirty_sprites() self.sprites.render_dirty_sprites()
draw_borders() draw_borders()
with self.cell_program: self.tab_manager.render()
self.tab_manager.render(self.cell_program, self.sprites) for window in tab.visible_windows():
for window in tab.visible_windows(): if not window.needs_layout:
if not window.needs_layout: window.render_cells()
window.render_cells(self.cell_program, self.sprites)
active = self.active_window active = self.active_window
if active is not None: if active is not None:
draw_cursor = True draw_cursor = True
@ -396,7 +395,7 @@ class Boss:
active.char_grid.render_cursor(self.window_is_focused) active.char_grid.render_cursor(self.window_is_focused)
def gui_close_window(self, window): def gui_close_window(self, window):
window.char_grid.destroy(self.cell_program) window.char_grid.destroy()
for tab in self.tab_manager: for tab in self.tab_manager:
if window in tab: if window in tab:
break break

View File

@ -4,24 +4,18 @@
import re import re
from collections import namedtuple from collections import namedtuple
from ctypes import sizeof
from enum import Enum from enum import Enum
from .config import build_ansi_color_table from .config import build_ansi_color_table
from .constants import ( from .constants import ScreenGeometry, cell_size, viewport_size
GLfloat, GLuint, ScreenGeometry, cell_size, viewport_size
)
from .fast_data_types import ( from .fast_data_types import (
CELL, CELL_PROGRAM, CURSOR_BEAM, CURSOR_BLOCK, CURSOR_PROGRAM, CELL_PROGRAM, CURSOR_BEAM, CURSOR_BLOCK, CURSOR_PROGRAM, CURSOR_UNDERLINE,
CURSOR_UNDERLINE, GL_FLOAT, GL_STATIC_DRAW, GL_TRIANGLE_FAN, compile_program, create_cell_vao, draw_cells, draw_cursor,
GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, compile_program, draw_cursor, init_cell_program, init_cursor_program, remove_vao
glDrawArraysInstanced, glUniform1i, glUniform2f, glUniform2i, glUniform2ui,
glUniform4f, glUniform4ui, init_cursor_program
) )
from .rgb import to_color from .rgb import to_color
from .shaders import ShaderProgram, load_shaders
from .utils import ( from .utils import (
color_as_int, get_logical_dpi, open_url, color_as_int, get_logical_dpi, load_shaders, open_url,
set_primary_selection set_primary_selection
) )
@ -32,34 +26,11 @@ class DynamicColor(Enum):
default_fg, default_bg, cursor_color, highlight_fg, highlight_bg = range(1, 6) 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(): 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')) compile_program(CURSOR_PROGRAM, *load_shaders('cursor'))
init_cursor_program() init_cursor_program()
return cell
# }}}
def calculate_gl_geometry(window_geometry, viewport_width, viewport_height, cell_width, cell_height): 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) 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): def render_cells(vao_id, sg, screen, invert_colors=False):
if color_profile.dirty: draw_cells(vao_id, sg.xstart, sg.ystart, sg.dx, sg.dy, invert_colors, screen)
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)
class CharGrid: class CharGrid:
@ -93,23 +51,21 @@ class CharGrid:
url_pat = re.compile('(?:http|https|file|ftp)://\S+', re.IGNORECASE) url_pat = re.compile('(?:http|https|file|ftp)://\S+', re.IGNORECASE)
def __init__(self, screen, opts): def __init__(self, screen, opts):
self.vao_id = None self.vao_id = create_cell_vao()
self.screen_reversed = False self.screen_reversed = False
self.data_buffer_size = None
self.screen = screen self.screen = screen
self.opts = opts self.opts = opts
self.screen.color_profile.update_ansi_color_table(build_ansi_color_table(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, ( self.screen.color_profile.set_configured_colors(*map(color_as_int, (
opts.foreground, opts.background, opts.cursor, opts.selection_foreground, opts.selection_background))) 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.dpix, self.dpiy = get_logical_dpi()
self.opts = opts 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 self.opts = opts
def destroy(self, cell_program): def destroy(self):
if self.vao_id is not None: if self.vao_id is not None:
cell_program.remove_vertex_array(self.vao_id) remove_vao(self.vao_id)
self.vao_id = None self.vao_id = None
def update_position(self, window_geometry): def update_position(self, window_geometry):
@ -117,9 +73,6 @@ class CharGrid:
def resize(self, window_geometry): def resize(self, window_geometry):
self.update_position(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): def change_colors(self, changes):
dirtied = False dirtied = False
@ -139,16 +92,6 @@ class CharGrid:
if dirtied: if dirtied:
self.screen.mark_as_dirty() 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): def cell_for_pos(self, x, y):
x, y = int(x // cell_size.width), int(y // cell_size.height) x, y = int(x // cell_size.width), int(y // cell_size.height)
if 0 <= x < self.screen.columns and 0 <= y < self.screen.lines: if 0 <= x < self.screen.columns and 0 <= y < self.screen.lines:
@ -229,23 +172,15 @@ class CharGrid:
def text_for_selection(self): def text_for_selection(self):
return ''.join(self.screen.text_for_selection()) return ''.join(self.screen.text_for_selection())
def render_cells(self, cell_program, sprites, invert_colors=False): def render_cells(self, 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)
render_cells( render_cells(
self.vao_id, self.screen_geometry, cell_program, sprites, self.vao_id, self.screen_geometry,
self.screen.color_profile, invert_colors=invert_colors, self.screen, invert_colors=invert_colors)
screen_reversed=self.screen_reversed)
def render_cursor(self, is_focused): def render_cursor(self, is_focused):
cursor = self.current_cursor
if not self.screen.cursor_visible or self.screen.scrolled_by: if not self.screen.cursor_visible or self.screen.scrolled_by:
return return
cursor = self.screen.cursor
def width(w=2, vert=True): def width(w=2, vert=True):
dpi = self.dpix if vert else self.dpiy 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(); if (FG_BG_256[255] == 0) create_256_color_table();
memcpy(self->color_table, FG_BG_256, sizeof(FG_BG_256)); memcpy(self->color_table, FG_BG_256, sizeof(FG_BG_256));
memcpy(self->orig_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->dirty = true;
self->ubo = Py_None; Py_INCREF(Py_None);
} }
return (PyObject*) self; return (PyObject*) self;
} }
static void static void
dealloc(ColorProfile* self) { dealloc(ColorProfile* self) {
Py_DECREF(self->dirty);
Py_DECREF(self->ubo);
Py_TYPE(self)->tp_free((PyObject*)self); 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->color_table[i] = PyLong_AsUnsignedLong(PyList_GET_ITEM(val, i));
self->orig_color_table[i] = self->color_table[i]; self->orig_color_table[i] = self->color_table[i];
} }
self->dirty = true;
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@ -136,6 +134,7 @@ static PyObject*
reset_color_table(ColorProfile *self) { reset_color_table(ColorProfile *self) {
#define reset_color_table_doc "Reset all customized colors back to defaults" #define reset_color_table_doc "Reset all customized colors back to defaults"
memcpy(self->color_table, self->orig_color_table, sizeof(FG_BG_256)); memcpy(self->color_table, self->orig_color_table, sizeof(FG_BG_256));
self->dirty = true;
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@ -144,6 +143,7 @@ reset_color(ColorProfile *self, PyObject *val) {
#define reset_color_doc "Reset the specified color" #define reset_color_doc "Reset the specified color"
uint8_t i = PyLong_AsUnsignedLong(val) & 0xff; uint8_t i = PyLong_AsUnsignedLong(val) & 0xff;
self->color_table[i] = self->orig_color_table[i]; self->color_table[i] = self->orig_color_table[i];
self->dirty = true;
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@ -154,6 +154,7 @@ set_color(ColorProfile *self, PyObject *args) {
unsigned long val; unsigned long val;
if (!PyArg_ParseTuple(args, "Bk", &i, &val)) return NULL; if (!PyArg_ParseTuple(args, "Bk", &i, &val)) return NULL;
self->color_table[i] = val; self->color_table[i] = val;
self->dirty = true;
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@ -161,21 +162,19 @@ static PyObject*
set_configured_colors(ColorProfile *self, PyObject *args) { set_configured_colors(ColorProfile *self, PyObject *args) {
#define set_configured_colors_doc "Set the configured colors" #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; 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; Py_RETURN_NONE;
} }
static PyObject* void
copy_color_table(ColorProfile *self, PyObject *args) { copy_color_table_to_buffer(ColorProfile *self, void *address, int offset, size_t stride) {
#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." size_t i;
unsigned int offset, stride, i; color_type *buf = address;
PyObject *ptr;
if (!PyArg_ParseTuple(args, "OII", &ptr, &offset, &stride)) return NULL;
color_type *buf = (color_type*)PyLong_AsVoidPtr(ptr);
stride = MAX(1, stride); stride = MAX(1, stride);
for (i = 0, buf = buf + offset; i < sizeof(self->color_table)/sizeof(self->color_table[0]); i++, buf += 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]; *buf = self->color_table[i];
} }
Py_RETURN_NONE; self->dirty = false;
} }
static PyObject* static PyObject*
@ -189,7 +188,7 @@ color_table_address(ColorProfile *self) {
#define CGETSET(name) \ #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 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_fg)
CGETSET(default_bg) CGETSET(default_bg)
@ -208,8 +207,6 @@ static PyGetSetDef getsetters[] = {
static PyMemberDef members[] = { static PyMemberDef members[] = {
{"dirty", T_OBJECT_EX, offsetof(ColorProfile, dirty), 0, "dirty"},
{"ubo", T_OBJECT_EX, offsetof(ColorProfile, ubo), 0, "ubo"},
{NULL} {NULL}
}; };
@ -221,7 +218,6 @@ static PyMethodDef methods[] = {
METHOD(reset_color, METH_O) METHOD(reset_color, METH_O)
METHOD(set_color, METH_VARARGS) METHOD(set_color, METH_VARARGS)
METHOD(set_configured_colors, METH_VARARGS) METHOD(set_configured_colors, METH_VARARGS)
METHOD(copy_color_table, METH_VARARGS)
{NULL} /* Sentinel */ {NULL} /* Sentinel */
}; };

View File

@ -187,7 +187,7 @@ typedef struct {
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
PyObject *dirty, *ubo; bool dirty;
uint32_t color_table[256]; uint32_t color_table[256];
uint32_t orig_color_table[256]; uint32_t orig_color_table[256];
DynamicColor configured, overridden; DynamicColor configured, overridden;
@ -237,10 +237,10 @@ typedef struct {
uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset; uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset;
Selection selection; Selection selection;
SelectionBoundary last_rendered_selection_start, last_rendered_selection_end; 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; Cursor *cursor;
SavepointBuffer main_savepoints, alt_savepoints; SavepointBuffer main_savepoints, alt_savepoints;
PyObject *callbacks, *is_dirty, *cursor_changed, *scroll_changed; PyObject *callbacks;
LineBuf *linebuf, *main_linebuf, *alt_linebuf; LineBuf *linebuf, *main_linebuf, *alt_linebuf;
HistoryBuf *historybuf; HistoryBuf *historybuf;
unsigned int history_line_added_count; unsigned int history_line_added_count;
@ -326,6 +326,8 @@ PyObject* cm_thread_write(PyObject *self, PyObject *args);
bool set_iutf8(int, bool); bool set_iutf8(int, bool);
color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval); 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); unsigned int safe_wcwidth(uint32_t ch);
void change_wcwidth(bool use9); 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->columns = columns; self->lines = lines;
self->write_buf = NULL; self->write_buf = NULL;
self->modes = empty_modes; self->modes = empty_modes;
self->cursor_changed = Py_True; self->is_dirty = Py_True; self->is_dirty = true;
self->scroll_changed = Py_False; self->scroll_changed = false;
self->margin_top = 0; self->margin_bottom = self->lines - 1; self->margin_top = 0; self->margin_bottom = self->lines - 1;
self->history_line_added_count = 0; self->history_line_added_count = 0;
RESET_CHARSETS; RESET_CHARSETS;
@ -104,8 +104,7 @@ screen_reset(Screen *self) {
init_tabstops(self->main_tabstops, self->columns); init_tabstops(self->main_tabstops, self->columns);
init_tabstops(self->alt_tabstops, self->columns); init_tabstops(self->alt_tabstops, self->columns);
cursor_reset(self->cursor); cursor_reset(self->cursor);
self->cursor_changed = Py_True; self->is_dirty = true;
self->is_dirty = Py_True;
screen_cursor_position(self, 1, 1); screen_cursor_position(self, 1, 1);
set_dynamic_color(self, 110, NULL); set_dynamic_color(self, 110, NULL);
set_dynamic_color(self, 111, 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; self->tabstops = self->main_tabstops;
init_tabstops(self->main_tabstops, self->columns); init_tabstops(self->main_tabstops, self->columns);
init_tabstops(self->alt_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); if (index_after_resize) screen_index(self);
return true; return true;
@ -263,7 +263,6 @@ void
screen_draw(Screen *self, uint32_t och) { screen_draw(Screen *self, uint32_t och) {
if (is_ignored_char(och)) return; if (is_ignored_char(och)) return;
uint32_t ch = och < 256 ? self->g_charset[och] : och; 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); unsigned int char_width = safe_wcwidth(ch);
if (self->columns - self->cursor->x < char_width) { if (self->columns - self->cursor->x < char_width) {
if (self->modes.mDECAWM) { 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); line_set_char(self->linebuf->line, self->cursor->x, 0, 0, self->cursor);
self->cursor->x++; self->cursor->x++;
} }
self->is_dirty = Py_True; self->is_dirty = true;
} else if (is_combining_char(ch)) { } else if (is_combining_char(ch)) {
if (self->cursor->x > 0) { if (self->cursor->x > 0) {
linebuf_init_line(self->linebuf, self->cursor->y); linebuf_init_line(self->linebuf, self->cursor->y);
line_add_combining_char(self->linebuf->line, ch, self->cursor->x - 1); 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) { } else if (self->cursor->y > 0) {
linebuf_init_line(self->linebuf, self->cursor->y - 1); linebuf_init_line(self->linebuf, self->cursor->y - 1);
line_add_combining_char(self->linebuf->line, ch, self->columns - 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 void
@ -419,7 +417,7 @@ screen_toggle_screen_buffer(Screen *self) {
screen_restore_cursor(self); screen_restore_cursor(self);
} }
CALLBACK("buf_toggled", "O", self->linebuf == self->main_linebuf ? Py_True : Py_False); 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 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; break;
case DECTCEM: case DECTCEM:
self->modes.mDECTCEM = val; self->modes.mDECTCEM = val;
self->cursor_changed = Py_True;
break; break;
case DECSCNM: case DECSCNM:
// Render screen in reverse video // Render screen in reverse video
if (self->modes.mDECSCNM != val) { if (self->modes.mDECSCNM != val) {
self->modes.mDECSCNM = val; self->modes.mDECSCNM = val;
self->is_dirty = Py_True; self->is_dirty = true;
} }
break; break;
case DECOM: case DECOM:
@ -484,7 +481,6 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) {
break; break;
case CONTROL_CURSOR_BLINK: case CONTROL_CURSOR_BLINK:
self->cursor->blink = val; self->cursor->blink = val;
self->cursor_changed = Py_True;
break; break;
case ALTERNATE_SCREEN: case ALTERNATE_SCREEN:
if (val && self->linebuf == self->main_linebuf) screen_toggle_screen_buffer(self); 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) found = self->columns - 1;
if (found != self->cursor->x) { if (found != self->cursor->x) {
self->cursor->x = found; self->cursor->x = found;
self->cursor_changed = Py_True;
} }
} }
@ -534,7 +529,6 @@ void
screen_backtab(Screen *self, unsigned int count) { screen_backtab(Screen *self, unsigned int count) {
// Move back count tabs // Move back count tabs
if (!count) count = 1; if (!count) count = 1;
unsigned int before = self->cursor->x;
int i; int i;
while (count > 0 && self->cursor->x > 0) { while (count > 0 && self->cursor->x > 0) {
count--; count--;
@ -543,7 +537,6 @@ screen_backtab(Screen *self, unsigned int count) {
} }
if (i <= 0) self->cursor->x = 0; if (i <= 0) self->cursor->x = 0;
} }
if (before != self->cursor->x) self->cursor_changed = Py_True;
} }
void void
@ -571,12 +564,10 @@ screen_set_tab_stop(Screen *self) {
void void
screen_cursor_back(Screen *self, unsigned int count/*=1*/, int move_direction/*=-1*/) { 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 (count == 0) count = 1;
if (move_direction < 0 && count > self->cursor->x) self->cursor->x = 0; if (move_direction < 0 && count > self->cursor->x) self->cursor->x = 0;
else self->cursor->x += move_direction * count; else self->cursor->x += move_direction * count;
screen_ensure_bounds(self, false); screen_ensure_bounds(self, false);
if (x != self->cursor->x) self->cursor_changed = Py_True;
} }
void void
@ -586,13 +577,11 @@ screen_cursor_forward(Screen *self, unsigned int count/*=1*/) {
void void
screen_cursor_up(Screen *self, unsigned int count/*=1*/, bool do_carriage_return/*=false*/, int move_direction/*=-1*/) { 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 (count == 0) count = 1;
if (move_direction < 0 && count > self->cursor->y) self->cursor->y = 0; if (move_direction < 0 && count > self->cursor->y) self->cursor->y = 0;
else self->cursor->y += move_direction * count; else self->cursor->y += move_direction * count;
screen_ensure_bounds(self, true); screen_ensure_bounds(self, true);
if (do_carriage_return) self->cursor->x = 0; if (do_carriage_return) self->cursor->x = 0;
if (x != self->cursor->x || y != self->cursor->y) self->cursor_changed = Py_True;
} }
void void
@ -616,7 +605,6 @@ screen_cursor_to_column(Screen *self, unsigned int column) {
if (x != self->cursor->x) { if (x != self->cursor->x) {
self->cursor->x = x; self->cursor->x = x;
screen_ensure_bounds(self, false); 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++; \ self->history_line_added_count++; \
} \ } \
linebuf_clear_line(self->linebuf, bottom); \ linebuf_clear_line(self->linebuf, bottom); \
self->is_dirty = Py_True; self->is_dirty = true;
void void
screen_index(Screen *self) { screen_index(Screen *self) {
@ -654,7 +642,7 @@ screen_scroll(Screen *self, unsigned int count) {
#define INDEX_DOWN \ #define INDEX_DOWN \
linebuf_reverse_index(self->linebuf, top, bottom); \ linebuf_reverse_index(self->linebuf, top, bottom); \
linebuf_clear_line(self->linebuf, top); \ linebuf_clear_line(self->linebuf, top); \
self->is_dirty = Py_True; self->is_dirty = true;
void void
screen_reverse_index(Screen *self) { screen_reverse_index(Screen *self) {
@ -681,7 +669,6 @@ void
screen_carriage_return(Screen *self) { screen_carriage_return(Screen *self) {
if (self->cursor->x != 0) { if (self->cursor->x != 0) {
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); Savepoint *sp = savepoints_pop(pts);
if (sp == NULL) { if (sp == NULL) {
screen_cursor_position(self, 1, 1); screen_cursor_position(self, 1, 1);
self->cursor_changed = Py_True;
screen_reset_mode(self, DECOM); screen_reset_mode(self, DECOM);
RESET_CHARSETS; RESET_CHARSETS;
screen_reset_mode(self, DECSCNM); 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 += self->margin_top;
line = MAX(self->margin_top, MIN(line, self->margin_bottom)); 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; self->cursor->x = column; self->cursor->y = line;
screen_ensure_bounds(self, false); screen_ensure_bounds(self, false);
if (x != self->cursor->x || y != self->cursor->y) self->cursor_changed = Py_True;
} }
void void
@ -816,7 +800,7 @@ void screen_erase_in_line(Screen *self, unsigned int how, bool private) {
} else { } else {
line_apply_cursor(self->linebuf->line, self->cursor, s, n, true); 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); line_apply_cursor(self->linebuf->line, self->cursor, 0, self->columns, true);
} }
} }
self->is_dirty = Py_True; self->is_dirty = true;
} }
if (how != 2) { if (how != 2) {
screen_erase_in_line(self, how, private); 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 (count == 0) count = 1;
if (top <= self->cursor->y && self->cursor->y <= bottom) { if (top <= self->cursor->y && self->cursor->y <= bottom) {
linebuf_insert_lines(self->linebuf, count, 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); screen_carriage_return(self);
} }
} }
@ -875,7 +859,7 @@ void screen_delete_lines(Screen *self, unsigned int count) {
if (count == 0) count = 1; if (count == 0) count = 1;
if (top <= self->cursor->y && self->cursor->y <= bottom) { if (top <= self->cursor->y && self->cursor->y <= bottom) {
linebuf_delete_lines(self->linebuf, count, 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); 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); linebuf_init_line(self->linebuf, self->cursor->y);
line_right_shift(self->linebuf->line, x, num); line_right_shift(self->linebuf->line, x, num);
line_apply_cursor(self->linebuf->line, self->cursor, x, num, true); 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); linebuf_init_line(self->linebuf, self->cursor->y);
left_shift_line(self->linebuf->line, x, num); left_shift_line(self->linebuf->line, x, num);
line_apply_cursor(self->linebuf->line, self->cursor, self->columns - num, num, true); 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); unsigned int num = MIN(self->columns - x, count);
linebuf_init_line(self->linebuf, self->cursor->y); linebuf_init_line(self->linebuf, self->cursor->y);
line_apply_cursor(self->linebuf->line, self->cursor, x, num, true); 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) { if (shape != self->cursor->shape || blink != self->cursor->blink) {
self->cursor->shape = shape; self->cursor->blink = blink; self->cursor->shape = shape; self->cursor->blink = blink;
self->cursor_changed = Py_True;
} }
break; 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 {{{ // Python interface {{{
#define WRAP0(name) static PyObject* name(Screen *self) { screen_##name(self); Py_RETURN_NONE; } #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; } #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; 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* static PyObject*
visual_line(Screen *self, PyObject *args) { visual_line(Screen *self, PyObject *args) {
// The line corresponding to the yth visual line, taking into account scrolling // The line corresponding to the yth visual line, taking into account scrolling
@ -1149,9 +1215,7 @@ set_mode(Screen *self, PyObject *args) {
static PyObject* static PyObject*
reset_dirty(Screen *self) { reset_dirty(Screen *self) {
self->is_dirty = Py_False; screen_reset_dirty(self);
self->cursor_changed = Py_False;
self->history_line_added_count = 0;
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@ -1210,91 +1274,6 @@ change_scrollback_size(Screen *self, PyObject *args) {
Py_RETURN_NONE; 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* static PyObject*
text_for_selection(Screen *self) { text_for_selection(Screen *self) {
SelectionBoundary start, end; SelectionBoundary start, end;
@ -1383,19 +1362,13 @@ scroll(Screen *self, PyObject *args) {
unsigned int new_scroll = MIN(self->scrolled_by + amt, self->historybuf->count); unsigned int new_scroll = MIN(self->scrolled_by + amt, self->historybuf->count);
if (new_scroll != self->scrolled_by) { if (new_scroll != self->scrolled_by) {
self->scrolled_by = new_scroll; self->scrolled_by = new_scroll;
self->scroll_changed = Py_True; self->scroll_changed = true;
Py_RETURN_TRUE; Py_RETURN_TRUE;
} }
} }
Py_RETURN_FALSE; Py_RETURN_FALSE;
} }
static PyObject*
clear_selection(Screen *self) {
self->selection = EMPTY_SELECTION;
Py_RETURN_NONE;
}
static PyObject* static PyObject*
is_selection_in_progress(Screen *self) { is_selection_in_progress(Screen *self) {
PyObject *ans = self->selection.in_progress ? Py_True : Py_False; PyObject *ans = self->selection.in_progress ? Py_True : Py_False;
@ -1403,12 +1376,12 @@ is_selection_in_progress(Screen *self) {
return ans; return ans;
} }
static PyObject* bool
is_selection_dirty(Screen *self) { screen_is_selection_dirty(Screen *self) {
SelectionBoundary start, end; SelectionBoundary start, end;
selection_limits_(self, &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; } 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;
Py_RETURN_FALSE; return false;
} }
static PyObject* static PyObject*
@ -1433,7 +1406,7 @@ update_selection(Screen *self, PyObject *args) {
static PyObject* static PyObject*
mark_as_dirty(Screen *self) { mark_as_dirty(Screen *self) {
self->is_dirty = Py_True; self->is_dirty = true;
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@ -1515,19 +1488,15 @@ static PyMethodDef methods[] = {
MND(mark_as_dirty, METH_NOARGS) MND(mark_as_dirty, METH_NOARGS)
MND(resize, METH_VARARGS) MND(resize, METH_VARARGS)
MND(set_margins, METH_VARARGS) MND(set_margins, METH_VARARGS)
MND(apply_selection, METH_VARARGS)
MND(selection_range_for_line, METH_VARARGS) MND(selection_range_for_line, METH_VARARGS)
MND(selection_range_for_word, METH_VARARGS) MND(selection_range_for_word, METH_VARARGS)
MND(text_for_selection, METH_NOARGS) MND(text_for_selection, METH_NOARGS)
MND(clear_selection, METH_NOARGS)
MND(is_selection_in_progress, METH_NOARGS) MND(is_selection_in_progress, METH_NOARGS)
MND(is_selection_dirty, METH_NOARGS)
MND(start_selection, METH_VARARGS) MND(start_selection, METH_VARARGS)
MND(update_selection, METH_VARARGS) MND(update_selection, METH_VARARGS)
MND(scroll, METH_VARARGS) MND(scroll, METH_VARARGS)
MND(toggle_alt_screen, METH_NOARGS) MND(toggle_alt_screen, METH_NOARGS)
MND(reset_callbacks, 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, ""}, {"select_graphic_rendition", (PyCFunction)_select_graphic_rendition, METH_VARARGS, ""},
{NULL} /* Sentinel */ {NULL} /* Sentinel */
@ -1553,9 +1522,6 @@ static PyGetSetDef getsetters[] = {
static PyMemberDef members[] = { static PyMemberDef members[] = {
{"callbacks", T_OBJECT_EX, offsetof(Screen, callbacks), 0, "callbacks"}, {"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"}, {"cursor", T_OBJECT_EX, offsetof(Screen, cursor), READONLY, "cursor"},
{"color_profile", T_OBJECT_EX, offsetof(Screen, color_profile), READONLY, "color_profile"}, {"color_profile", T_OBJECT_EX, offsetof(Screen, color_profile), READONLY, "color_profile"},
{"linebuf", T_OBJECT_EX, offsetof(Screen, linebuf), READONLY, "linebuf"}, {"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 select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count);
void report_device_status(Screen *self, unsigned int which, bool UNUSED); void report_device_status(Screen *self, unsigned int which, bool UNUSED);
void report_mode_status(Screen *self, unsigned int which, bool); 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); #define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen);
DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(bell)
DECLARE_CH_SCREEN_HANDLER(backspace) DECLARE_CH_SCREEN_HANDLER(backspace)

View File

@ -6,6 +6,7 @@
*/ */
#include "data-types.h" #include "data-types.h"
#include "screen.h"
#ifdef __APPLE__ #ifdef __APPLE__
#include <OpenGL/gl3.h> #include <OpenGL/gl3.h>
#include <OpenGL/gl3ext.h> #include <OpenGL/gl3ext.h>
@ -44,7 +45,7 @@ check_for_gl_error(int line) {
case GL_INVALID_VALUE: case GL_INVALID_VALUE:
f("An numeric value is invalid (GL_INVALID_VALUE)"); f("An numeric value is invalid (GL_INVALID_VALUE)");
case GL_INVALID_OPERATION: 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: case GL_INVALID_FRAMEBUFFER_OPERATION:
f("The framebuffer object is not complete (GL_INVALID_FRAMEBUFFER_OPERATION)"); f("The framebuffer object is not complete (GL_INVALID_FRAMEBUFFER_OPERATION)");
case GL_OUT_OF_MEMORY: case GL_OUT_OF_MEMORY:
@ -87,7 +88,7 @@ enum ProgramNames { CELL_PROGRAM, CURSOR_PROGRAM, BORDERS_PROGRAM, NUM_PROGRAMS
typedef struct { typedef struct {
char name[256]; char name[256];
GLint size, id; GLint size, location, idx;
GLenum type; GLenum type;
} Uniform; } Uniform;
@ -130,7 +131,8 @@ init_uniforms(int program) {
Uniform *u = p->uniforms + i; Uniform *u = p->uniforms + i;
glGetActiveUniform(p->id, (GLuint)i, sizeof(u->name)/sizeof(u->name[0]), NULL, &(u->size), &(u->type), u->name); glGetActiveUniform(p->id, (GLuint)i, sizeof(u->name)/sizeof(u->name[0]), NULL, &(u->size), &(u->type), u->name);
check_gl(); 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; 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 static void
bind_program(int program) { bind_program(int program) {
glUseProgram(programs[program].id); glUseProgram(programs[program].id);
@ -192,10 +220,11 @@ delete_buffer(ssize_t buf_idx) {
buffers[buf_idx].size = 0; buffers[buf_idx].size = 0;
} }
static void static GLuint
bind_buffer(ssize_t buf_idx) { bind_buffer(ssize_t buf_idx) {
glBindBuffer(buffers[buf_idx].usage, buffers[buf_idx].id); glBindBuffer(buffers[buf_idx].usage, buffers[buf_idx].id);
check_gl(); check_gl();
return buffers[buf_idx].id;
} }
static void static void
@ -258,13 +287,13 @@ create_vao() {
} }
static void 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; VAO* vao = vaos + vao_idx;
if (vao->num_buffers >= sizeof(vao->buffers) / sizeof(vao->buffers[0])) { if (vao->num_buffers >= sizeof(vao->buffers) / sizeof(vao->buffers[0])) {
fatal("too many buffers in a single VAO"); fatal("too many buffers in a single VAO");
return; return;
} }
ssize_t buf = create_buffer(GL_ARRAY_BUFFER); ssize_t buf = create_buffer(usage);
vao->buffers[vao->num_buffers++] = buf; 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; 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 static void
unmap_vao_buffer(ssize_t vao_idx, size_t bufnum) { unmap_vao_buffer(ssize_t vao_idx, size_t bufnum) {
ssize_t buf_idx = vaos[vao_idx].buffers[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 {{{ // Cursor {{{
enum CursorUniforms { CURSOR_color, CURSOR_xpos, CURSOR_ypos, NUM_CURSOR_UNIFORMS }; enum CursorUniforms { CURSOR_color, CURSOR_xpos, CURSOR_ypos, NUM_CURSOR_UNIFORMS };
static GLint cursor_uniform_locations[NUM_CURSOR_UNIFORMS] = {0}; static GLint cursor_uniform_locations[NUM_CURSOR_UNIFORMS] = {0};
@ -353,7 +489,7 @@ init_cursor_program() {
int left = NUM_CURSOR_UNIFORMS; int left = NUM_CURSOR_UNIFORMS;
cursor_vertex_array = create_vao(); cursor_vertex_array = create_vao();
for (int i = 0; i < p->num_of_uniforms; i++, left--) { 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); SET_LOC(color);
else SET_LOC(xpos); else SET_LOC(xpos);
else SET_LOC(ypos); else SET_LOC(ypos);
@ -394,13 +530,13 @@ init_borders_program() {
int left = NUM_BORDER_UNIFORMS; int left = NUM_BORDER_UNIFORMS;
border_vertex_array = create_vao(); border_vertex_array = create_vao();
for (int i = 0; i < p->num_of_uniforms; i++, left--) { 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); SET_LOC(viewport);
else { fatal("Unknown uniform in borders program"); return; } else { fatal("Unknown uniform in borders program"); return; }
} }
if (left) { fatal("Left over uniforms in borders program"); return; } if (left) { fatal("Left over uniforms in borders program"); return; }
#undef SET_LOC #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", add_attribute_to_vao(BORDERS_PROGRAM, border_vertex_array, "rect",
/*size=*/4, /*dtype=*/GL_UNSIGNED_INT, /*stride=*/sizeof(GLuint)*5, /*offset=*/0, /*divisor=*/1); /*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", 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 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 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(name) PYWRAP0(name) { name(); Py_RETURN_NONE; }
#define NO_ARG_INT(name) PYWRAP0(name) { return PyLong_FromSsize_t(name()); }
ONE_INT(bind_program) ONE_INT(bind_program)
NO_ARG(unbind_program) NO_ARG(unbind_program)
@ -508,18 +645,6 @@ PYWRAP0(create_vao) {
} }
ONE_INT(remove_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) ONE_INT(bind_vertex_array)
NO_ARG(unbind_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; } 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) 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 M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL}
#define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL}
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
@ -554,8 +691,6 @@ static PyMethodDef module_methods[] = {
M(compile_program, METH_VARARGS), M(compile_program, METH_VARARGS),
MW(create_vao, METH_NOARGS), MW(create_vao, METH_NOARGS),
MW(remove_vao, METH_O), 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(bind_vertex_array, METH_O),
MW(unbind_vertex_array, METH_NOARGS), MW(unbind_vertex_array, METH_NOARGS),
MW(map_vao_buffer, METH_VARARGS), MW(map_vao_buffer, METH_VARARGS),
@ -568,6 +703,9 @@ static PyMethodDef module_methods[] = {
MW(draw_borders, METH_NOARGS), MW(draw_borders, METH_NOARGS),
MW(add_borders_rect, METH_VARARGS), MW(add_borders_rect, METH_VARARGS),
MW(send_borders_rects, 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 */ {NULL, NULL, 0, NULL} /* Sentinel */
}; };

View File

@ -2,114 +2,25 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys import sys
from collections import namedtuple from ctypes import addressof
from contextlib import contextmanager
from ctypes import addressof, c_char, sizeof
from functools import lru_cache
from .fast_data_types import ( from .fast_data_types import (
GL_ARRAY_BUFFER, GL_CLAMP_TO_EDGE, GL_FLOAT, GL_MAX_ARRAY_TEXTURE_LAYERS, GL_CLAMP_TO_EDGE, GL_MAX_ARRAY_TEXTURE_LAYERS, GL_MAX_TEXTURE_SIZE,
GL_MAX_TEXTURE_SIZE, GL_NEAREST, GL_R8, GL_RED, GL_STREAM_DRAW, GL_NEAREST, GL_R8, GL_RED, GL_TEXTURE0, GL_TEXTURE_2D_ARRAY,
GL_TEXTURE0, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S,
GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, GL_TEXTURE_WRAP_T, GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE,
GL_UNIFORM_BUFFER, GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_WRITE_ONLY, copy_image_sub_data, glActiveTexture, glBindTexture, glCopyImageSubData,
GLSL_VERSION, compile_program, copy_image_sub_data, glDeleteTexture, glGenTextures, glGetIntegerv, glPixelStorei,
get_uniform_block_offsets, get_uniform_block_size, glActiveTexture, glTexParameteri, glTexStorage3D, glTexSubImage3D, render_dirty_sprites,
glBindBuffer, glBindBufferBase, glBindTexture, glBindVertexArray, sprite_map_current_layout, sprite_map_free, sprite_map_increment,
glCopyImageSubData, glDeleteBuffer, glDeleteTexture, glDeleteVertexArray, sprite_map_set_layout, sprite_map_set_limits
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
) )
from .fonts.render import render_cell from .fonts.render import render_cell
from .utils import safe_print from .utils import safe_print
UBO = namedtuple('UBO', 'size index offsets buf_id')
BASE = os.path.dirname(os.path.abspath(__file__))
class Sprites:
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: # {{{
''' Maintain sprite sheets of all rendered characters on the GPU as a texture ''' Maintain sprite sheets of all rendered characters on the GPU as a texture
array with each texture being a sprite sheet. ''' array with each texture being a sprite sheet. '''
@ -149,11 +60,6 @@ class Sprites: # {{{
if send(strikethrough=True) != 3: if send(strikethrough=True) != 3:
raise RuntimeError('Available OpenGL texture size is too small') 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): def render_cell(self, text, bold, italic, is_second):
first, second = render_cell(text, bold, italic) first, second = render_cell(text, bold, italic)
ans = second if is_second else first ans = second if is_second else first
@ -231,121 +137,3 @@ class Sprites: # {{{
def __exit__(self, *a): def __exit__(self, *a):
glBindTexture(GL_TEXTURE_2D_ARRAY, 0) 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; Py_RETURN_NONE;
} }
PyObject* void
sprite_map_current_layout(PyObject UNUSED *s) { 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); 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[] = { static PyMethodDef module_methods[] = {
{"sprite_map_set_limits", (PyCFunction)sprite_map_set_limits, METH_VARARGS, ""}, \ {"sprite_map_set_limits", (PyCFunction)sprite_map_set_limits, METH_VARARGS, ""}, \
{"sprite_map_set_layout", (PyCFunction)sprite_map_set_layout, 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_free", (PyCFunction)sprite_map_free, METH_NOARGS, ""}, \
{"sprite_map_increment", (PyCFunction)sprite_map_increment, METH_NOARGS, ""}, \ {"sprite_map_increment", (PyCFunction)sprite_map_increment, METH_NOARGS, ""}, \
{"sprite_position_for", (PyCFunction)sprite_position_for, METH_VARARGS, ""}, \ {"sprite_position_for", (PyCFunction)sprite_position_for, METH_VARARGS, ""}, \
{"render_dirty_sprites", (PyCFunction)render_dirty_sprites, METH_NOARGS, ""}, \ {"render_dirty_sprites", (PyCFunction)render_dirty_sprites, METH_NOARGS, ""}, \
{"sprite_map_current_layout", (PyCFunction)current_layout, METH_NOARGS, ""}, \
{NULL, NULL, 0, NULL} /* Sentinel */ {NULL, NULL, 0, NULL} /* Sentinel */
}; };

View File

@ -3,7 +3,6 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from collections import deque, namedtuple from collections import deque, namedtuple
from ctypes import memset, sizeof
from functools import partial from functools import partial
from .borders import Borders from .borders import Borders
@ -11,10 +10,12 @@ from .char_grid import calculate_gl_geometry, render_cells
from .child import Child from .child import Child
from .config import build_ansi_color_table from .config import build_ansi_color_table
from .constants import ( from .constants import (
GLfloat, WindowGeometry, appname, cell_size, get_boss, shell_path, WindowGeometry, appname, cell_size, get_boss, shell_path,
viewport_size 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 .layout import Rect, all_layouts
from .utils import color_as_int from .utils import color_as_int
from .window import Window from .window import Window
@ -205,7 +206,7 @@ class TabBar:
self.num_tabs = 1 self.num_tabs = 1
self.cell_width = 1 self.cell_width = 1
self.data_buffer_size = 0 self.data_buffer_size = 0
self.vao_id = None self.vao_id = create_cell_vao()
self.layout_changed = None self.layout_changed = None
self.dirty = True self.dirty = True
self.screen = s = Screen(None, 1, 10) 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_foreground),
color_as_int(opts.inactive_tab_background) color_as_int(opts.inactive_tab_background)
) )
s.color_profile.dirty = True
self.blank_rects = () self.blank_rects = ()
def as_rgb(x): def as_rgb(x):
@ -229,8 +229,6 @@ class TabBar:
ncells = viewport_width // cell_width ncells = viewport_width // cell_width
s.resize(1, ncells) s.resize(1, ncells)
s.reset_mode(DECAWM) 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 self.layout_changed = True
margin = (viewport_width - ncells * cell_width) // 2 margin = (viewport_width - ncells * cell_width) // 2
self.window_geometry = g = WindowGeometry( self.window_geometry = g = WindowGeometry(
@ -269,22 +267,11 @@ class TabBar:
break break
s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab
self.cell_ranges = cr self.cell_ranges = cr
self.dirty = True
glfw_post_empty_event() glfw_post_empty_event()
def render(self, cell_program, sprites): def render(self):
if self.layout_changed is not None: if self.layout_changed is not None:
if self.vao_id is None: render_cells(self.vao_id, self.screen_geometry, self.screen)
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)
def tab_at(self, x): def tab_at(self, x):
x = (x - self.window_geometry.left) // self.cell_width x = (x - self.window_geometry.left) // self.cell_width
@ -394,7 +381,7 @@ class TabManager:
def blank_rects(self): def blank_rects(self):
return self.tab_bar.blank_rects if len(self.tabs) > 1 else () 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: if len(self.tabs) < 2:
return 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 time import monotonic
from .constants import isosx 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 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): def safe_print(*a, **k):
try: try:

View File

@ -283,13 +283,13 @@ class Window:
def buf_toggled(self, is_main_linebuf): def buf_toggled(self, is_main_linebuf):
self.screen.scroll(SCROLL_FULL, False) self.screen.scroll(SCROLL_FULL, False)
def render_cells(self, program, sprites): def render_cells(self):
invert_colors = False invert_colors = False
if self.start_visual_bell_at is not None: if self.start_visual_bell_at is not None:
invert_colors = monotonic() - self.start_visual_bell_at <= self.opts.visual_bell_duration invert_colors = monotonic() - self.start_visual_bell_at <= self.opts.visual_bell_duration
if not invert_colors: if not invert_colors:
self.start_visual_bell_at = None self.start_visual_bell_at = None
self.char_grid.render_cells(program, sprites, invert_colors) self.char_grid.render_cells(invert_colors)
# actions {{{ # actions {{{