From 6127d2d12257aa5e4e1f8d053dca3619d3644f5b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 30 Aug 2017 20:49:53 +0530 Subject: [PATCH] Make the sprite map globally accessible from C code as well as python code The lock was removed as the Python GIL is sufficient to serialize access to the SpriteMap structure. --- kitty/boss.py | 2 +- kitty/char_grid.py | 17 ++- kitty/data-types.c | 3 +- kitty/data-types.h | 27 +---- kitty/screen.c | 10 +- kitty/shaders.py | 39 ++++--- kitty/sprites.c | 222 +++++++++++++++++---------------------- kitty/sprites.h | 39 +++++++ kitty/tabs.py | 3 +- kitty/tracker.c | 4 +- kitty/tracker.h | 2 +- kitty_tests/datatypes.py | 26 ++--- 12 files changed, 191 insertions(+), 203 deletions(-) create mode 100644 kitty/sprites.h diff --git a/kitty/boss.py b/kitty/boss.py index c3223bb15..d3d21141e 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -148,10 +148,10 @@ def __init__(self, glfw_window, opts, args): glfw_window.window_focus_callback = self.on_focus self.tab_manager = TabManager(opts, args, startup_session) self.sprites = Sprites() + self.sprites.do_layout(cell_size.width, cell_size.height) self.cell_program, self.cursor_program = load_shader_programs() self.borders_program = BordersProgram() glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - self.sprites.do_layout(cell_size.width, cell_size.height) self.glfw_window.set_click_cursor(False) self.show_mouse_cursor() self.start_cursor_blink() diff --git a/kitty/char_grid.py b/kitty/char_grid.py index 01e129f2b..403fc00f8 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -11,7 +11,7 @@ from .config import build_ansi_color_table, defaults from .constants import ( - GLfloat, GLuint, ScreenGeometry, cell_size, get_boss, viewport_size + GLfloat, GLuint, ScreenGeometry, cell_size, viewport_size ) from .fast_data_types import ( CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, DATA_CELL_SIZE, GL_BLEND, @@ -234,16 +234,13 @@ def scroll(self, amt, upwards=True): self.update_cell_data() def update_cell_data(self, force_full_refresh=False): - sprites = get_boss().sprites is_dirty = self.screen.is_dirty() - with sprites.lock: - cursor_changed, history_line_added_count = self.screen.update_cell_data( - sprites.backend, addressof(self.main_sprite_map), force_full_refresh) - if self.scrolled_by: - self.scrolled_by = min(self.scrolled_by + history_line_added_count, self.screen.historybuf.count) - self.screen.set_scroll_cell_data( - sprites.backend, addressof(self.main_sprite_map), - self.scrolled_by, addressof(self.scroll_sprite_map)) + cursor_changed, history_line_added_count = self.screen.update_cell_data( + addressof(self.main_sprite_map), force_full_refresh) + if self.scrolled_by: + self.scrolled_by = min(self.scrolled_by + history_line_added_count, self.screen.historybuf.count) + self.screen.set_scroll_cell_data( + addressof(self.main_sprite_map), self.scrolled_by, addressof(self.scroll_sprite_map)) data = self.scroll_sprite_map if self.scrolled_by else self.main_sprite_map with self.buffer_lock: diff --git a/kitty/data-types.c b/kitty/data-types.c index 76f6429ba..88a42c31b 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -9,6 +9,7 @@ #include "glfw.h" #include "gl.h" #include "modes.h" +#include "sprites.h" #include #ifdef WITH_PROFILER #include @@ -79,6 +80,7 @@ static PyMethodDef module_methods[] = { {"get_fontconfig_font", (PyCFunction)get_fontconfig_font, METH_VARARGS, ""}, #endif GLFW_FUNC_WRAPPERS + SPRITE_FUNC_WRAPPERS #ifdef WITH_PROFILER {"start_profiler", (PyCFunction)start_profiler, METH_VARARGS, ""}, {"stop_profiler", (PyCFunction)stop_profiler, METH_NOARGS, ""}, @@ -112,7 +114,6 @@ PyInit_fast_data_types(void) { if (!init_Timers(m)) return NULL; if (!init_ChildMonitor(m)) return NULL; if (!init_ColorProfile(m)) return NULL; - if (!init_SpriteMap(m)) return NULL; if (!init_ChangeTracker(m)) return NULL; if (!init_Screen(m)) return NULL; if (!add_module_gl_constants(m)) return NULL; diff --git a/kitty/data-types.h b/kitty/data-types.h index 094276298..77b08c32b 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -26,6 +26,7 @@ typedef uint32_t char_type; typedef uint32_t color_type; typedef uint32_t combining_type; typedef unsigned int index_type; +typedef uint16_t sprite_index; #define ERROR_PREFIX "[PARSE ERROR]" #define ANY_MODE 3 @@ -191,29 +192,6 @@ typedef struct { } ColorProfile; PyTypeObject ColorProfile_Type; -typedef struct SpritePosition SpritePosition; -struct SpritePosition { - SpritePosition *next; - unsigned int x, y, z; - char_type ch; - combining_type cc; - bool is_second; - bool filled; - bool rendered; -}; -PyTypeObject SpritePosition_Type; - -typedef struct { - PyObject_HEAD - - size_t max_array_len, max_texture_size, max_y; - unsigned int x, y, z, xnum, ynum; - SpritePosition cache[1024]; - bool dirty; - -} SpriteMap; -PyTypeObject SpriteMap_Type; - typedef struct { PyObject_HEAD @@ -336,7 +314,6 @@ int init_Timers(PyObject *); int init_ChildMonitor(PyObject *); int init_Line(PyObject *); int init_ColorProfile(PyObject *); -int init_SpriteMap(PyObject *); int init_ChangeTracker(PyObject *); int init_Screen(PyObject *); int init_Face(PyObject *); @@ -351,7 +328,7 @@ void cursor_reset(Cursor*); Cursor* cursor_copy(Cursor*); void cursor_copy_to(Cursor *src, Cursor *dest); void cursor_reset_display_attrs(Cursor*); -bool update_cell_range_data(ScreenModes *modes, SpriteMap *, Line *, unsigned int, unsigned int, unsigned int *); +bool update_cell_range_data(ScreenModes *modes, Line *, unsigned int, unsigned int, unsigned int *); PyObject* line_text_at(char_type, combining_type); void line_clear_text(Line *self, unsigned int at, unsigned int num, int ch); diff --git a/kitty/screen.c b/kitty/screen.c index 88ff7d3fd..627bd3611 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1167,25 +1167,23 @@ change_scrollback_size(Screen *self, PyObject *args) { static PyObject* screen_update_cell_data(Screen *self, PyObject *args) { - SpriteMap *spm; PyObject *dp; unsigned int *data; int force_screen_refresh; - if (!PyArg_ParseTuple(args, "O!O!p", &SpriteMap_Type, &spm, &PyLong_Type, &dp, &force_screen_refresh)) return NULL; + if (!PyArg_ParseTuple(args, "O!p", &PyLong_Type, &dp, &force_screen_refresh)) return NULL; data = PyLong_AsVoidPtr(dp); PyObject *cursor_changed = self->change_tracker->cursor_changed ? Py_True : Py_False; unsigned int history_line_added_count = self->change_tracker->history_line_added_count; - if (!tracker_update_cell_data(&(self->modes), self->change_tracker, self->linebuf, spm, data, (bool)force_screen_refresh)) return NULL; + if (!tracker_update_cell_data(&(self->modes), self->change_tracker, self->linebuf, data, (bool)force_screen_refresh)) return NULL; return Py_BuildValue("OI", cursor_changed, history_line_added_count); } static PyObject* set_scroll_cell_data(Screen *self, PyObject *args) { - SpriteMap *spm; PyObject *dp, *sp; unsigned int *data, *src, scrolled_by; - if (!PyArg_ParseTuple(args, "O!O!IO", &SpriteMap_Type, &spm, &PyLong_Type, &sp, &scrolled_by, &dp)) return NULL; + if (!PyArg_ParseTuple(args, "O!IO", &PyLong_Type, &sp, &scrolled_by, &dp)) return NULL; data = PyLong_AsVoidPtr(dp); src = PyLong_AsVoidPtr(sp); @@ -1194,7 +1192,7 @@ set_scroll_cell_data(Screen *self, PyObject *args) { for (index_type y = 0; y < MIN(self->lines, scrolled_by); y++) { historybuf_init_line(self->historybuf, scrolled_by - 1 - y, self->historybuf->line); self->historybuf->line->ynum = y; - if (!update_cell_range_data(&(self->modes), spm, self->historybuf->line, 0, self->columns - 1, data)) return NULL; + if (!update_cell_range_data(&(self->modes), self->historybuf->line, 0, self->columns - 1, data)) return NULL; } if (scrolled_by < self->lines) { // Less than a full screen has been scrolled, copy some lines from the screen buffer to the scroll buffer diff --git a/kitty/shaders.py b/kitty/shaders.py index 6ecf20895..0d3559ade 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -8,7 +8,6 @@ from contextlib import contextmanager from ctypes import addressof, sizeof from functools import lru_cache -from threading import Lock from .fast_data_types import ( BOLD, GL_ARRAY_BUFFER, GL_CLAMP_TO_EDGE, GL_COMPILE_STATUS, GL_FLOAT, @@ -17,7 +16,7 @@ GL_TEXTURE0, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, GL_TRUE, GL_UNIFORM_BUFFER, GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_VERTEX_SHADER, - ITALIC, SpriteMap, copy_image_sub_data, get_uniform_block_offsets, + ITALIC, copy_image_sub_data, get_uniform_block_offsets, get_uniform_block_size, glActiveTexture, glAttachShader, glBindBuffer, glBindBufferBase, glBindTexture, glBindVertexArray, glCompileShader, glCopyImageSubData, glCreateProgram, glCreateShader, glDeleteBuffer, @@ -28,7 +27,8 @@ glGetUniformBlockIndex, glGetUniformLocation, glLinkProgram, glPixelStorei, glShaderSource, glTexParameteri, glTexStorage3D, glTexSubImage3D, glUseProgram, glVertexAttribDivisor, glVertexAttribPointer, - replace_or_create_buffer + render_dirty_sprites, replace_or_create_buffer, sprite_map_current_layout, + sprite_map_increment, sprite_map_set_layout, sprite_map_set_limits, sprite_map_free ) from .fonts.render import render_cell from .utils import safe_print @@ -120,24 +120,20 @@ def __init__(self): self.last_ynum = -1 self.sampler_num = 0 self.texture_unit = GL_TEXTURE0 + self.sampler_num - self.backend = SpriteMap(glGetIntegerv(GL_MAX_TEXTURE_SIZE), glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS)) - self.lock = Lock() + sprite_map_set_limits(glGetIntegerv(GL_MAX_TEXTURE_SIZE), glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS)) def do_layout(self, cell_width=1, cell_height=1): self.cell_width, self.cell_height = cell_width, cell_height - self.backend.layout(cell_width or 1, cell_height or 1) + sprite_map_set_layout(cell_width or 1, cell_height or 1) if self.texture_id is not None: glDeleteTexture(self.texture_id) self.texture_id = None self.ensure_state() - self.pre_render() - - def pre_render(self): # Pre-render the basic cells to ensure they have known sprite numbers def send(*a, **kw): buf = render_cell(*a, **kw)[0] - x, y, z = self.backend.increment() + x, y, z = sprite_map_increment() self.send_to_gpu(x, y, z, buf) return x @@ -149,7 +145,8 @@ def send(*a, **kw): @property def layout(self): - return 1 / self.backend.xnum, 1 / self.backend.ynum + xnum, ynum, znum = sprite_map_current_layout() + return 1 / xnum, 1 / ynum def render_cell(self, text, bold, italic, is_second): first, second = render_cell(text, bold, italic) @@ -157,14 +154,14 @@ def render_cell(self, text, bold, italic, is_second): return ans or render_cell()[0] def render_dirty_cells(self): - with self.lock: - self.backend.render_dirty_cells(self.render_cell, self.send_to_gpu) + render_dirty_sprites(self.render_cell, self.send_to_gpu) def send_to_gpu(self, x, y, z, buf): - if self.backend.z >= self.last_num_of_layers: + xnum, ynum, znum = sprite_map_current_layout() + if znum >= self.last_num_of_layers: self.realloc_texture() else: - if self.backend.z == 0 and self.backend.ynum > self.last_ynum: + if znum == 0 and ynum > self.last_ynum: self.realloc_texture() tgt = GL_TEXTURE_2D_ARRAY glBindTexture(tgt, self.texture_id) @@ -182,12 +179,13 @@ def realloc_texture(self): glTexParameteri(tgt, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(tgt, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(tgt, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) - znum = self.backend.z + 1 - width, height = self.backend.xnum * self.cell_width, self.backend.ynum * self.cell_height + xnum, bynum, z = sprite_map_current_layout() + znum = z + 1 + width, height = xnum * self.cell_width, bynum * self.cell_height glTexStorage3D(tgt, 1, GL_R8, width, height, znum) if self.texture_id is not None: - ynum = self.backend.ynum - if self.backend.z == 0: + ynum = bynum + if z == 0: ynum -= 1 # Only copy the previous rows try: glCopyImageSubData(self.texture_id, tgt, 0, 0, 0, 0, tex, tgt, 0, 0, 0, 0, @@ -203,10 +201,11 @@ def realloc_texture(self): glBindTexture(tgt, tex) glDeleteTexture(self.texture_id) self.last_num_of_layers = znum - self.last_ynum = self.backend.ynum + self.last_ynum = bynum self.texture_id = tex def destroy(self): + sprite_map_free() if self.texture_id is not None: glDeleteTexture(self.texture_id) self.texture_id = None diff --git a/kitty/sprites.c b/kitty/sprites.c index bf7bbf27f..18e8165fa 100644 --- a/kitty/sprites.c +++ b/kitty/sprites.c @@ -6,28 +6,46 @@ */ #include "data-types.h" +#include "sprites.h" #include -static PyObject* -new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { - SpriteMap *self; - unsigned long mlen, msz; - if (!PyArg_ParseTuple(args, "kk", &msz, &mlen)) return NULL; +typedef struct { + size_t max_array_len, max_texture_size, max_y; + unsigned int x, y, z, xnum, ynum; + SpritePosition cache[1024]; + bool dirty; +} SpriteMap; - self = (SpriteMap *)type->tp_alloc(type, 0); - if (self != NULL) { - self->max_array_len = mlen; - self->max_texture_size = msz; - self->dirty = true; +static SpriteMap sprite_map = { + .max_array_len = 1000, + .max_texture_size = 1000, + .max_y = 100, + .dirty = true +}; + +static inline void +sprite_map_set_error(int error) { + switch(error) { + case 1: + PyErr_NoMemory(); break; + case 2: + PyErr_SetString(PyExc_RuntimeError, "Out of texture space for sprites"); break; + default: + PyErr_SetString(PyExc_RuntimeError, "Unknown error occurred while allocating sprites"); break; } - return (PyObject*) self; } -static void -dealloc(SpriteMap* self) { +PyObject* +sprite_map_set_limits(PyObject UNUSED *self, PyObject *args) { + if (!PyArg_ParseTuple(args, "kk", &(sprite_map.max_texture_size), &(sprite_map.max_array_len))) return NULL; + Py_RETURN_NONE; +} + +PyObject* +sprite_map_free() { SpritePosition *s, *t; - for (size_t i = 0; i < sizeof(self->cache)/sizeof(self->cache[0]); i++) { - s = &(self->cache[i]); + for (size_t i = 0; i < sizeof(sprite_map.cache)/sizeof(sprite_map.cache[0]); i++) { + s = &(sprite_map.cache[i]); s = s->next; while (s) { t = s; @@ -35,51 +53,27 @@ dealloc(SpriteMap* self) { PyMem_Free(t); } } - Py_TYPE(self)->tp_free((PyObject*)self); -} - -static PyObject* -layout(SpriteMap *self, PyObject *args) { -#define layout_doc "layout(cell_width, cell_height) -> Invalidate the cache and prepare it for new cell size" - unsigned long cell_width, cell_height; - if (!PyArg_ParseTuple(args, "kk", &cell_width, &cell_height)) return NULL; - self->xnum = MIN(MAX(1, self->max_texture_size / cell_width), UINT16_MAX); - self->max_y = MIN(MAX(1, self->max_texture_size / cell_height), UINT16_MAX); - self->ynum = 1; - self->x = 0; self->y = 0; self->z = 0; - - for (size_t i = 0; i < sizeof(self->cache)/sizeof(self->cache[0]); i++) { - SpritePosition *s = &(self->cache[i]); - do { - s->filled = false; - s->is_second = false; - s->rendered = false; - s->ch = 0; s->cc = 0; - s->x = 0; s->y = 0; s->z = 0; - s = s->next; - } while (s != NULL); - } Py_RETURN_NONE; } -static void -do_increment(SpriteMap *self, int *error) { - self->x++; - if (self->x >= self->xnum) { - self->x = 0; self->y++; - self->ynum = MIN(MAX(self->ynum, self->y + 1), self->max_y); - if (self->y >= self->max_y) { - self->y = 0; self->z++; - if (self->z >= MIN(UINT16_MAX, self->max_array_len)) *error = 2; +static inline void +do_increment(int *error) { + sprite_map.x++; + if (sprite_map.x >= sprite_map.xnum) { + sprite_map.x = 0; sprite_map.y++; + sprite_map.ynum = MIN(MAX(sprite_map.ynum, sprite_map.y + 1), sprite_map.max_y); + if (sprite_map.y >= sprite_map.max_y) { + sprite_map.y = 0; sprite_map.z++; + if (sprite_map.z >= MIN(UINT16_MAX, sprite_map.max_array_len)) *error = 2; } } } -static SpritePosition* -sprite_position_for(SpriteMap *self, char_type ch, combining_type cc, bool is_second, int *error) { +SpritePosition* +sprite_map_position_for(char_type ch, combining_type cc, bool is_second, int *error) { char_type pos_char = ch & POSCHAR_MASK; // Includes only the char and bold and italic bits - unsigned int idx = ((ch >> (ATTRS_SHIFT - 6)) & 0x300) | (ch & 0xFF); // Includes only italic, bold and lowest byte of ch - SpritePosition *s = &(self->cache[idx]); + unsigned int idx = ((ch >> (ATTRS_SHIFT - 4)) & 0x300) | (ch & 0xFF); // Includes only italic, bold and lowest byte of ch + SpritePosition *s = &(sprite_map.cache[idx]); // Optimize for the common case of an ASCII char already in the cache if (LIKELY(s->ch == pos_char && s->filled && s->cc == cc && s->is_second == is_second)) return s; // Cache hit while(true) { @@ -99,47 +93,67 @@ sprite_position_for(SpriteMap *self, char_type ch, combining_type cc, bool is_se s->is_second = is_second; s->filled = true; s->rendered = false; - s->x = self->x; s->y = self->y; s->z = self->z; - do_increment(self, error); - self->dirty = true; + s->x = sprite_map.x; s->y = sprite_map.y; s->z = sprite_map.z; + do_increment(error); + sprite_map.dirty = true; return s; } -static void set_sprite_error(int error) { - switch(error) { - case 1: - PyErr_NoMemory(); break; - case 2: - PyErr_SetString(PyExc_RuntimeError, "Out of texture space for sprites"); break; - default: - PyErr_SetString(PyExc_RuntimeError, "Unknown error occurred while allocating sprites"); break; - } -} - -static PyObject* -increment(SpriteMap *self) { +PyObject* +sprite_map_increment() { #define increment_doc "Increment the current position and return the old (x, y, z) values" - unsigned int x = self->x, y = self->y, z = self->z; + unsigned int x = sprite_map.x, y = sprite_map.y, z = sprite_map.z; int error = 0; - do_increment(self, &error); - if (error) { set_sprite_error(error); return NULL; } + do_increment(&error); + if (error) { sprite_map_set_error(error); return NULL; } return Py_BuildValue("III", x, y, z); } -static PyObject* -position_for(SpriteMap *self, PyObject *args) { +PyObject* +sprite_map_set_layout(PyObject UNUSED *s_, PyObject *args) { + // Invalidate cache since cell size has changed. + unsigned long cell_width, cell_height; + if (!PyArg_ParseTuple(args, "kk", &cell_width, &cell_height)) return NULL; + SpritePosition *s; + sprite_map.xnum = MIN(MAX(1, sprite_map.max_texture_size / cell_width), UINT16_MAX); + sprite_map.max_y = MIN(MAX(1, sprite_map.max_texture_size / cell_height), UINT16_MAX); + sprite_map.ynum = 1; + sprite_map.x = 0; sprite_map.y = 0; sprite_map.z = 0; + + for (size_t i = 0; i < sizeof(sprite_map.cache)/sizeof(sprite_map.cache[0]); i++) { + s = &(sprite_map.cache[i]); + do { + s->filled = false; + s->is_second = false; + s->rendered = false; + s->ch = 0; s->cc = 0; + s->x = 0; s->y = 0; s->z = 0; + s = s->next; + } while (s != NULL); + } + sprite_map.dirty = true; + Py_RETURN_NONE; +} + +PyObject* +sprite_map_current_layout(PyObject UNUSED *s) { + return Py_BuildValue("III", sprite_map.xnum, sprite_map.ynum, sprite_map.z); +} + +PyObject* +sprite_position_for(PyObject UNUSED *self, PyObject *args) { #define position_for_doc "position_for(ch, cc, is_second) -> x, y, z the sprite position for the specified text" unsigned long ch = 0; unsigned long long cc = 0; int is_second = 0, error = 0; if (!PyArg_ParseTuple(args, "|kKp", &ch, &cc, &is_second)) return NULL; - SpritePosition *pos = sprite_position_for(self, ch, cc, is_second, &error); - if (pos == NULL) {set_sprite_error(error); return NULL; } + SpritePosition *pos = sprite_map_position_for(ch, cc, is_second, &error); + if (pos == NULL) { sprite_map_set_error(error); return NULL; } return Py_BuildValue("III", pos->x, pos->y, pos->z); } bool -update_cell_range_data(ScreenModes *modes, SpriteMap *self, Line *line, unsigned int xstart, unsigned int xmax, unsigned int *data) { +update_cell_range_data(ScreenModes *modes, Line *line, unsigned int xstart, unsigned int xmax, unsigned int *data) { SpritePosition *sp; char_type previous_ch=0, ch; uint8_t previous_width = 0; @@ -149,9 +163,9 @@ update_cell_range_data(ScreenModes *modes, SpriteMap *self, Line *line, unsigned size_t base = line->ynum * line->xnum * DATA_CELL_SIZE; for (size_t i = xstart, offset = base + xstart * DATA_CELL_SIZE; i <= xmax; i++, offset += DATA_CELL_SIZE) { ch = line->cells[i].ch; - if (previous_width == 2) sp = sprite_position_for(self, previous_ch, 0, true, &err); - else sp = sprite_position_for(self, ch, line->cells[i].cc, false, &err); - if (sp == NULL) { set_sprite_error(err); return false; } + if (previous_width == 2) sp = sprite_map_position_for(previous_ch, 0, true, &err); + else sp = sprite_map_position_for(ch, line->cells[i].cc, false, &err); + if (sp == NULL) { sprite_map_set_error(err); return false; } char_type attrs = ch >> ATTRS_SHIFT; unsigned int decoration = (attrs >> DECORATION_SHIFT) & DECORATION_MASK; unsigned int strikethrough = ((attrs >> STRIKE_SHIFT) & 1) ? 3 : 0; @@ -167,17 +181,17 @@ update_cell_range_data(ScreenModes *modes, SpriteMap *self, Line *line, unsigned return true; } -static PyObject* -render_dirty_cells(SpriteMap *self, PyObject *args) { +PyObject* +render_dirty_sprites(PyObject UNUSED *s_, PyObject *args) { #define render_dirty_cells_doc "Render all cells that are marked as dirty" PyObject *render_cell, *send_to_gpu; if (!PyArg_ParseTuple(args, "OO", &render_cell, &send_to_gpu)) return NULL; - if (!self->dirty) { Py_RETURN_NONE; } + if (!sprite_map.dirty) { Py_RETURN_NONE; } - for (size_t i = 0; i < sizeof(self->cache)/sizeof(self->cache[0]); i++) { - SpritePosition *sp = &(self->cache[i]); + for (size_t i = 0; i < sizeof(sprite_map.cache)/sizeof(sprite_map.cache[0]); i++) { + SpritePosition *sp = &(sprite_map.cache[i]); while (sp) { if (sp->filled && !sp->rendered) { PyObject *text = line_text_at(sp->ch & CHAR_MASK, sp->cc); @@ -196,44 +210,6 @@ render_dirty_cells(SpriteMap *self, PyObject *args) { sp = sp->next; } } - self->dirty = false; + sprite_map.dirty = false; Py_RETURN_NONE; } - -// Boilerplate {{{ - -static PyMemberDef members[] = { - {"xnum", T_UINT, offsetof(SpriteMap, xnum), 0, "xnum"}, - {"ynum", T_UINT, offsetof(SpriteMap, ynum), 0, "ynum"}, - {"x", T_UINT, offsetof(SpriteMap, x), 0, "x"}, - {"y", T_UINT, offsetof(SpriteMap, y), 0, "y"}, - {"z", T_UINT, offsetof(SpriteMap, z), 0, "z"}, - {NULL} /* Sentinel */ -}; - - -static PyMethodDef methods[] = { - METHOD(layout, METH_VARARGS) - METHOD(position_for, METH_VARARGS) - METHOD(render_dirty_cells, METH_VARARGS) - METHOD(increment, METH_NOARGS) - {NULL} /* Sentinel */ -}; - - -PyTypeObject SpriteMap_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "fast_data_types.SpriteMap", - .tp_basicsize = sizeof(SpriteMap), - .tp_dealloc = (destructor)dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = "SpriteMap", - .tp_methods = methods, - .tp_members = members, - .tp_new = new, -}; - -INIT_TYPE(SpriteMap) -// }}} - - diff --git a/kitty/sprites.h b/kitty/sprites.h new file mode 100644 index 000000000..e68a6851f --- /dev/null +++ b/kitty/sprites.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#pragma once + +typedef struct SpritePosition SpritePosition; +struct SpritePosition { + SpritePosition *next; + sprite_index x, y, z; + char_type ch; + combining_type cc; + bool is_second; + bool filled; + bool rendered; +}; + + +PyObject* sprite_map_set_limits(PyObject UNUSED *self, PyObject *args); +PyObject* sprite_map_set_layout(PyObject UNUSED *s, PyObject *args); +PyObject* sprite_map_current_layout(PyObject UNUSED *s); +PyObject* sprite_map_free(); +PyObject* sprite_map_increment(); +SpritePosition* sprite_map_position_for(char_type ch, combining_type cc, bool is_second, int *error); +PyObject* sprite_position_for(PyObject UNUSED *self, PyObject *args); +bool update_cell_range_data(ScreenModes *modes, Line *line, unsigned int xstart, unsigned int xmax, unsigned int *data); +PyObject* render_dirty_sprites(PyObject UNUSED *self, PyObject *args); + +#define SPRITE_FUNC_WRAPPERS \ + {"sprite_map_set_limits", (PyCFunction)sprite_map_set_limits, METH_VARARGS, ""}, \ + {"sprite_map_set_layout", (PyCFunction)sprite_map_set_layout, METH_VARARGS, ""}, \ + {"sprite_map_current_layout", (PyCFunction)sprite_map_current_layout, METH_NOARGS, ""}, \ + {"sprite_map_free", (PyCFunction)sprite_map_free, METH_NOARGS, ""}, \ + {"sprite_map_increment", (PyCFunction)sprite_map_increment, METH_NOARGS, ""}, \ + {"sprite_position_for", (PyCFunction)sprite_position_for, METH_VARARGS, ""}, \ + {"render_dirty_sprites", (PyCFunction)render_dirty_sprites, METH_VARARGS, ""}, \ + diff --git a/kitty/tabs.py b/kitty/tabs.py index 624325ae5..8875a99d3 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -285,8 +285,7 @@ def update(self): s.draw('…') break s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab - sprites = get_boss().sprites - s.update_cell_data(sprites.backend, addressof(self.render_buf), True) + s.update_cell_data(addressof(self.render_buf), True) self.cell_ranges = cr self.dirty = True glfw_post_empty_event() diff --git a/kitty/tracker.c b/kitty/tracker.c index 52bcef4e6..b75db9c02 100644 --- a/kitty/tracker.c +++ b/kitty/tracker.c @@ -97,13 +97,13 @@ update_cell_range(ChangeTracker *self, PyObject *args) { Py_RETURN_NONE; } -bool tracker_update_cell_data(ScreenModes *modes, ChangeTracker *self, LineBuf *lb, SpriteMap *spm, unsigned int *data, bool force_screen_refresh) { +bool tracker_update_cell_data(ScreenModes *modes, ChangeTracker *self, LineBuf *lb, unsigned int *data, bool force_screen_refresh) { unsigned int y; Py_ssize_t start; #define UPDATE_RANGE(xstart, xmax) \ linebuf_init_line(lb, y); \ - if (!update_cell_range_data(modes, spm, lb->line, (xstart), (xmax), data)) return false; + if (!update_cell_range_data(modes, lb->line, (xstart), (xmax), data)) return false; if (self->screen_changed || force_screen_refresh) { for (y = 0; y < self->ynum; y++) { diff --git a/kitty/tracker.h b/kitty/tracker.h index fc082f5f7..7d2780389 100644 --- a/kitty/tracker.h +++ b/kitty/tracker.h @@ -52,4 +52,4 @@ static inline void tracker_reset(ChangeTracker *self) { PyObject* tracker_consolidate_changes(ChangeTracker *self); bool tracker_resize(ChangeTracker *self, unsigned int ynum, unsigned int xnum); -bool tracker_update_cell_data(ScreenModes*, ChangeTracker *, LineBuf *, SpriteMap *, unsigned int *, bool); +bool tracker_update_cell_data(ScreenModes*, ChangeTracker *, LineBuf *, unsigned int *, bool); diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index ee36dfbe9..25e4bc5a1 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -7,7 +7,8 @@ from kitty.config import build_ansi_color_table, defaults from kitty.fast_data_types import ( - REVERSE, ColorProfile, Cursor as C, HistoryBuf, LineBuf, SpriteMap, Timers + REVERSE, ColorProfile, Cursor as C, HistoryBuf, LineBuf, Timers, + sprite_map_set_layout, sprite_map_set_limits, sprite_position_for ) from kitty.utils import sanitize_title, wcwidth @@ -283,17 +284,18 @@ def test_color_profile(self): self.ae(c.as_color(255 << 8 | 1), (0xee, 0xee, 0xee)) def test_sprite_map(self): - s = SpriteMap(10, 2) - s.layout(5, 5) - self.ae(s.position_for(0), (0, 0, 0)) - self.ae(s.position_for(1), (1, 0, 0)) - self.ae(s.position_for(2), (0, 1, 0)) - self.ae(s.position_for(3), (1, 1, 0)) - self.ae(s.position_for(4), (0, 0, 1)) - self.ae(s.position_for(5), (1, 0, 1)) - self.ae(s.position_for(0, 1), (0, 1, 1)) - self.ae(s.position_for(0, 2), (1, 1, 1)) - self.ae(s.position_for(0, 2), (1, 1, 1)) + sprite_map_set_limits(10, 2) + sprite_map_set_layout(5, 5) + self.ae(sprite_position_for(0), (0, 0, 0)) + self.ae(sprite_position_for(1), (1, 0, 0)) + self.ae(sprite_position_for(2), (0, 1, 0)) + self.ae(sprite_position_for(3), (1, 1, 0)) + self.ae(sprite_position_for(4), (0, 0, 1)) + self.ae(sprite_position_for(5), (1, 0, 1)) + self.ae(sprite_position_for(0, 1), (0, 1, 1)) + self.ae(sprite_position_for(0, 2), (1, 1, 1)) + self.ae(sprite_position_for(0, 2), (1, 1, 1)) + sprite_map_set_limits(1000, 1000) def test_historybuf(self): lb = filled_line_buf()