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,
|
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):
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
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->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"},
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
184
kitty/shaders.c
184
kitty/shaders.c
@ -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 */
|
||||||
};
|
};
|
||||||
|
|||||||
234
kitty/shaders.py
234
kitty/shaders.py
@ -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)
|
|
||||||
# }}}
|
|
||||||
|
|||||||
@ -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 */
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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 {{{
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user