Migrate the cell program
This commit is contained in:
parent
2fff6e1cb9
commit
44f456089b
@ -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):
|
||||
|
||||
@ -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)
|
||||
self.tab_manager.render()
|
||||
for window in tab.visible_windows():
|
||||
if not window.needs_layout:
|
||||
window.render_cells(self.cell_program, self.sprites)
|
||||
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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 */
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
278
kitty/screen.c
278
kitty/screen.c
@ -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"},
|
||||
|
||||
@ -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)
|
||||
|
||||
184
kitty/shaders.c
184
kitty/shaders.c
@ -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 */
|
||||
};
|
||||
|
||||
234
kitty/shaders.py
234
kitty/shaders.py
@ -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)
|
||||
# }}}
|
||||
|
||||
@ -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 */
|
||||
};
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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 {{{
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user