diff --git a/kitty/boss.py b/kitty/boss.py index 06e61163f..a0a38438e 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -15,15 +15,14 @@ from .constants import ( from .fast_data_types import ( GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GLFW_CURSOR, GLFW_CURSOR_HIDDEN, GLFW_CURSOR_NORMAL, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_REPEAT, - ChildMonitor, Timers as _Timers, glBlendFunc, glfw_post_empty_event, - glViewport, draw_borders + ChildMonitor, Timers as _Timers, destroy_sprite_map, draw_borders, + glBlendFunc, glfw_post_empty_event, glViewport, layout_sprite_map ) -from .fonts.render import set_font_family +from .fonts.render import render_cell_wrapper, set_font_family from .keys import ( get_sent_data, get_shortcut, interpret_key_event, interpret_text_event ) from .session import create_session -from .shaders import Sprites from .tabs import SpecialWindow, TabManager from .utils import safe_print @@ -118,8 +117,7 @@ class Boss: load_shader_programs() self.tab_manager = TabManager(opts, args) self.tab_manager.init(startup_session) - self.sprites = Sprites() - self.sprites.do_layout(cell_size.width, cell_size.height) + layout_sprite_map(cell_size.width, cell_size.height, render_cell_wrapper) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) self.glfw_window.set_click_cursor(False) self.show_mouse_cursor() @@ -201,7 +199,7 @@ class Boss: self.current_font_size = new_size cell_size.width, cell_size.height = set_font_family( self.opts, override_font_size=self.current_font_size) - self.sprites.do_layout(cell_size.width, cell_size.height) + layout_sprite_map(cell_size.width, cell_size.height, render_cell_wrapper) self.resize_windows_after_font_size_change() for window in self.window_id_map.values(): if window is not None: @@ -373,26 +371,24 @@ class Boss: self.glfw_window.set_title(self.glfw_window_title) if isosx: cocoa_update_title(self.glfw_window_title) - with self.sprites: - self.sprites.render_dirty_sprites() - draw_borders() - self.tab_manager.render() - for window in tab.visible_windows(): - if not window.needs_layout: - window.render_cells() - active = self.active_window - if active is not None: - draw_cursor = True - if self.cursor_blinking and self.opts.cursor_blink_interval > 0 and self.window_is_focused: - now = monotonic() - self.cursor_blink_zero_time - t = int(now * 1000) - d = int(self.opts.cursor_blink_interval * 1000) - n = t // d - draw_cursor = n % 2 == 0 - self.ui_timers.add_if_before( - ((n + 1) * d / 1000) - now, None) - if draw_cursor: - active.char_grid.render_cursor(self.window_is_focused) + draw_borders() + self.tab_manager.render() + for window in tab.visible_windows(): + if not window.needs_layout: + window.render_cells() + active = self.active_window + if active is not None: + draw_cursor = True + if self.cursor_blinking and self.opts.cursor_blink_interval > 0 and self.window_is_focused: + now = monotonic() - self.cursor_blink_zero_time + t = int(now * 1000) + d = int(self.opts.cursor_blink_interval * 1000) + n = t // d + draw_cursor = n % 2 == 0 + self.ui_timers.add_if_before( + ((n + 1) * d / 1000) - now, None) + if draw_cursor: + active.char_grid.render_cursor(self.window_is_focused) def gui_close_window(self, window): window.char_grid.destroy() @@ -418,8 +414,7 @@ class Boss: for t in self.tab_manager: t.destroy() del self.tab_manager - self.sprites.destroy() - del self.sprites + destroy_sprite_map() del self.glfw_window def paste_from_clipboard(self): diff --git a/kitty/data-types.h b/kitty/data-types.h index 44b542a5e..69e5756e9 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -327,7 +327,6 @@ bool set_iutf8(int, bool); color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval); void copy_color_table_to_buffer(ColorProfile *self, void *address, int offset, size_t stride); -void sprite_map_current_layout(unsigned int *x, unsigned int *y, unsigned int*); unsigned int safe_wcwidth(uint32_t ch); void change_wcwidth(bool use9); diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index a177c2bb4..e0bba9901 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -57,6 +57,22 @@ def render_cell(text=' ', bold=False, italic=False, underline=0, strikethrough=F return first, second +class Buf: + + def __init__(self, buf): + self.buf = buf + + def __call__(self): + return ctypes.addressof(self.buf) + + +def render_cell_wrapper(text, bold, italic, underline, strikethrough, is_second): + first, second = render_cell(text, bold, italic, underline, strikethrough) + ans = second if is_second else first + ans = ans or render_cell()[0] + return Buf(ans) + + def join_cells(cell_width, cell_height, *cells): dstride = len(cells) * cell_width ans = (ctypes.c_ubyte * (cell_height * dstride))() diff --git a/kitty/shaders.c b/kitty/shaders.c index 15f91d163..134e9a1a4 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -7,6 +7,7 @@ #include "data-types.h" #include "screen.h" +#include "sprites.h" #ifdef __APPLE__ #include #include @@ -33,7 +34,6 @@ static char glbuf[4096]; #endif #define fatal(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(EXIT_FAILURE); } -#define fatal_msg(msg) fatal("%s", msg); #ifdef ENABLE_DEBUG_GL static void @@ -382,6 +382,171 @@ unmap_vao_buffer(ssize_t vao_idx, size_t bufnum) { // }}} +// Sprites {{{ +typedef struct { + int xnum, ynum, x, y, z, last_num_of_layers, last_ynum; + unsigned int cell_width, cell_height; + GLuint texture_id; + GLenum texture_unit; + GLint max_texture_size, max_array_texture_layers; + PyObject *render_cell; +} SpriteMap; + +static SpriteMap sprite_map = { .xnum = 1, .ynum = 1, .last_num_of_layers = 1, .last_ynum = -1, .texture_unit = GL_TEXTURE0 }; + +#ifdef __APPLE__ +#ifdef glCopyImageSubData +#define GLEW_ARB_copy_image true +#else +#define GLEW_ARB_copy_image false +#endif +#endif + +static bool copy_image_warned = false; + +static void +copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int width, unsigned int height, unsigned int num_levels) { + if (!GLEW_ARB_copy_image) { + // ARB_copy_image not available, do a slow roundtrip copy + if (!copy_image_warned) { + copy_image_warned = true; + fprintf(stderr, "WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation.\n"); + } + uint8_t *src = malloc(5 * width * height * num_levels); + if (src == NULL) { fatal("Out of memory."); } + uint8_t *dest = src + (4 * width * height * num_levels); + glBindTexture(GL_TEXTURE_2D_ARRAY, src_texture_id); check_gl(); + glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, src); check_gl(); + glBindTexture(GL_TEXTURE_2D_ARRAY, dest_texture_id); check_gl(); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); check_gl(); + for(size_t i = 0; i < width * height * num_levels; i++) dest[i] = src[4*i]; + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels, GL_RED, GL_UNSIGNED_BYTE, dest); check_gl(); + free(src); + } else { + glCopyImageSubData(src_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, dest_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels); check_gl(); + } +} + + +static void +realloc_sprite_texture() { + const GLenum tgt = GL_TEXTURE_2D_ARRAY; + GLuint tex; + glGenTextures(1, &tex); check_gl(); + glBindTexture(tgt, tex); check_gl(); + // We use GL_NEAREST otherwise glyphs that touch the edge of the cell + // often show a border between cells + glTexParameteri(tgt, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + 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); check_gl(); + unsigned int xnum, ynum, z, znum, width, height, src_ynum; + sprite_map_current_layout(&xnum, &ynum, &z); + znum = z + 1; + width = xnum * sprite_map.cell_width; height = ynum * sprite_map.cell_height; + glTexStorage3D(tgt, 1, GL_R8, width, height, znum); check_gl(); + if (sprite_map.texture_id) { + // need to re-alloc + src_ynum = z == 0 ? ynum - 1 : ynum; // Only copy the previous rows + copy_image_sub_data(sprite_map.texture_id, tex, width, src_ynum * sprite_map.cell_height, sprite_map.last_num_of_layers); + glDeleteTextures(1, &sprite_map.texture_id); check_gl(); + } + sprite_map.last_num_of_layers = znum; + sprite_map.last_ynum = ynum; + sprite_map.texture_id = tex; +} + +static inline PyObject* +render_cell(PyObject *text, bool bold, bool italic, unsigned int underline, bool strikethrough, bool is_second) { +#define B(x) (x ? Py_True : Py_False) + PyObject *ret = PyObject_CallFunction(sprite_map.render_cell, "OOOIOO", text, B(bold), B(italic), underline, B(strikethrough), B(is_second)); + if (ret == NULL) { PyErr_Print(); fatal("Rendering of a cell failed, aborting"); } + return ret; +#undef B +} + +static inline int +bind_sprite_map() { + if (!sprite_map.texture_id) realloc_sprite_texture(); + glActiveTexture(GL_TEXTURE0); check_gl(); + glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map.texture_id); check_gl(); + return 0; // corresponds to GL_TEXTURE0 +} + +static inline void +unbind_sprite_map() { + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); check_gl(); +} + +static void +sprite_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, PyObject *buf) { + unsigned int xnum, ynum, znum; + sprite_map_current_layout(&xnum, &ynum, &znum); + if ((int)znum >= sprite_map.last_num_of_layers || (znum == 0 && (int)ynum > sprite_map.last_ynum)) realloc_sprite_texture(); + glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map.texture_id); check_gl(); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); check_gl(); + x *= sprite_map.cell_width; y *= sprite_map.cell_height; + PyObject *ret = PyObject_CallObject(buf, NULL); + if (ret == NULL) { PyErr_Print(); fatal("Failed to get address of rendered cell buffer"); } + void *address = PyLong_AsVoidPtr(ret); + Py_DECREF(ret); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, sprite_map.cell_width, sprite_map.cell_height, 1, GL_RED, GL_UNSIGNED_BYTE, address); check_gl(); + Py_DECREF(buf); +} + +static void +render_and_send_dirty_sprites(PyObject *text, bool bold, bool italic, bool is_second, sprite_index x, sprite_index y, sprite_index z) { + if (text == NULL) { fatal("The text for a sprite was NULL, probably out of memory."); } + PyObject *buf = render_cell(text, bold, italic, false, false, is_second); + sprite_send_to_gpu(x, y, z, buf); +} + +static inline sprite_index +send_prerendered(unsigned int underline, bool strikethrough) { + sprite_index x, y, z; + PyObject *blank = PyUnicode_FromString(" "); + if (blank == NULL) { fatal("Out of memory"); } + PyObject *buf = render_cell(blank, false, false, underline, strikethrough, false); + Py_CLEAR(blank); + if (sprite_map_increment(&x, &y, &z) != 0) { fatal("Failed to increment sprite map for prerendering"); } + sprite_send_to_gpu(x, y, z, buf); + return x; +} + +static void +layout_sprite_map(unsigned int cell_width, unsigned int cell_height, PyObject *render_cell) { + sprite_map.cell_width = MAX(1, cell_width); + sprite_map.cell_height = MAX(1, cell_height); + if (sprite_map.max_texture_size == 0) { + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &(sprite_map.max_texture_size)); + glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &(sprite_map.max_array_texture_layers)); + check_gl(); + sprite_map_set_limits(sprite_map.max_texture_size, sprite_map.max_array_texture_layers); + } + sprite_map_set_layout(cell_width, cell_height); + Py_CLEAR(sprite_map.render_cell); + sprite_map.render_cell = render_cell; Py_INCREF(sprite_map.render_cell); + if (!sprite_map.texture_id) realloc_sprite_texture(); + // Pre-render the basic cells to ensure they have known sprite numbers + send_prerendered(0, false); + send_prerendered(1, false); + send_prerendered(2, false); + if (send_prerendered(0, true) != 3) { fatal("Available OpenGL texture size is too small"); } +} + +static void +destroy_sprite_map() { + sprite_map_free(); + Py_CLEAR(sprite_map.render_cell); + if (sprite_map.texture_id) { + glDeleteTextures(1, &(sprite_map.texture_id)); + check_gl(); + sprite_map.texture_id = 0; + } +} + +// }}} + // Cell {{{ enum CellUniforms { CELL_dimensions, CELL_default_colors, CELL_color_indices, CELL_steps, CELL_sprites, CELL_sprite_layout, CELL_color_table, NUM_CELL_UNIFORMS }; @@ -455,6 +620,8 @@ draw_cells(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat copy_color_table_to_buffer(screen->color_profile, address, cell_color_table_offset, cell_color_table_stride); unmap_vao_buffer(vao_idx, 2); } + int sprite_map_unit = bind_sprite_map(); + render_dirty_sprites(render_and_send_dirty_sprites); #define UL(name) cell_uniform_locations[CELL_##name] bind_program(CELL_PROGRAM); glUniform2ui(UL(dimensions), screen->columns, screen->lines); @@ -467,7 +634,7 @@ draw_cells(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat glUniform4ui(UL(default_colors), COLOR(default_fg), COLOR(default_bg), COLOR(highlight_fg), COLOR(highlight_bg)); check_gl(); #undef COLOR - glUniform1i(UL(sprites), 0); // the sprite map is bound to GL_TEXTURE0 + glUniform1i(UL(sprites), sprite_map_unit); check_gl(); unsigned int x, y, z; sprite_map_current_layout(&x, &y, &z); @@ -478,6 +645,7 @@ draw_cells(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat check_gl(); unbind_vertex_array(); unbind_program(); + unbind_sprite_map(); #undef UL } // }}} @@ -680,6 +848,14 @@ PYWRAP1(draw_cells) { draw_cells(vao_idx, xstart, ystart, dx, dy, inverted & 1, screen); Py_RETURN_NONE; } +NO_ARG(destroy_sprite_map) +PYWRAP1(layout_sprite_map) { + unsigned int cell_width, cell_height; + PyObject *render_cell; + PA("IIO", &cell_width, &cell_height, &render_cell); + layout_sprite_map(cell_width, cell_height, render_cell); + Py_RETURN_NONE; +} #define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL} #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} @@ -703,6 +879,8 @@ static PyMethodDef module_methods[] = { MW(init_cell_program, METH_NOARGS), MW(create_cell_vao, METH_NOARGS), MW(draw_cells, METH_VARARGS), + MW(layout_sprite_map, METH_VARARGS), + MW(destroy_sprite_map, METH_NOARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/shaders.py b/kitty/shaders.py deleted file mode 100644 index 650c5a41e..000000000 --- a/kitty/shaders.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=utf-8 -# License: GPL v3 Copyright: 2016, Kovid Goyal - -import sys -from ctypes import addressof - -from .fast_data_types import ( - GL_CLAMP_TO_EDGE, GL_MAX_ARRAY_TEXTURE_LAYERS, GL_MAX_TEXTURE_SIZE, - GL_NEAREST, GL_R8, GL_RED, GL_TEXTURE0, GL_TEXTURE_2D_ARRAY, - GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S, - GL_TEXTURE_WRAP_T, GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, - copy_image_sub_data, glActiveTexture, glBindTexture, glCopyImageSubData, - glDeleteTexture, glGenTextures, glGetIntegerv, glPixelStorei, - glTexParameteri, glTexStorage3D, glTexSubImage3D, render_dirty_sprites, - sprite_map_current_layout, sprite_map_free, sprite_map_increment, - sprite_map_set_layout, sprite_map_set_limits -) -from .fonts.render import render_cell -from .utils import safe_print - - -class Sprites: - ''' Maintain sprite sheets of all rendered characters on the GPU as a texture - array with each texture being a sprite sheet. ''' - - # TODO: Rewrite this class using the ARB_shader_image_load_store and ARB_shader_storage_buffer_object - # extensions one they become available. - - def __init__(self): - self.xnum = self.ynum = 1 - self.x = self.y = self.z = 0 - self.texture_id = None - self.last_num_of_layers = 1 - self.last_ynum = -1 - self.texture_unit = GL_TEXTURE0 - 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 - 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() - # 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 = sprite_map_increment() - self.send_to_gpu(x, y, z, buf) - return x - - send() # blank - send(underline=1) - send(underline=2) - if send(strikethrough=True) != 3: - raise RuntimeError('Available OpenGL texture size is too small') - - def render_cell(self, text, bold, italic, is_second): - first, second = render_cell(text, bold, italic) - ans = second if is_second else first - return ans or render_cell()[0] - - def render_dirty_sprites(self): - ret = render_dirty_sprites() - if ret: - for text, bold, italic, is_second, x, y, z in ret: - cell = self.render_cell(text, bold, italic, is_second) - self.send_to_gpu(x, y, z, cell) - - def send_to_gpu(self, x, y, z, buf): - xnum, ynum, znum = sprite_map_current_layout() - if znum >= self.last_num_of_layers: - self.realloc_texture() - else: - if znum == 0 and ynum > self.last_ynum: - self.realloc_texture() - tgt = GL_TEXTURE_2D_ARRAY - glBindTexture(tgt, self.texture_id) - glPixelStorei(GL_UNPACK_ALIGNMENT, 1) - x, y = x * self.cell_width, y * self.cell_height - glTexSubImage3D(tgt, 0, x, y, z, self.cell_width, self.cell_height, 1, GL_RED, GL_UNSIGNED_BYTE, addressof(buf)) - - def realloc_texture(self): - tgt = GL_TEXTURE_2D_ARRAY - tex = glGenTextures(1) - glBindTexture(tgt, tex) - # We use GL_NEAREST otherwise glyphs that touch the edge of the cell - # often show a border between cells - glTexParameteri(tgt, GL_TEXTURE_MIN_FILTER, GL_NEAREST) - 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) - 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 = 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, - width, ynum * self.cell_height, self.last_num_of_layers) - except RuntimeError: - # OpenGL does not have ARB_copy_image - if not hasattr(self, 'realloc_warned'): - safe_print( - 'WARNING: Your system\'s OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation', - file=sys.stderr) - self.realloc_warned = True - copy_image_sub_data(self.texture_id, tex, width, ynum * self.cell_height, self.last_num_of_layers) - glBindTexture(tgt, tex) - glDeleteTexture(self.texture_id) - self.last_num_of_layers = znum - 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 - - def ensure_state(self): - if self.texture_id is None: - self.realloc_texture() - - def __enter__(self): - self.ensure_state() - glActiveTexture(self.texture_unit) - glBindTexture(GL_TEXTURE_2D_ARRAY, self.texture_id) - - def __exit__(self, *a): - glBindTexture(GL_TEXTURE_2D_ARRAY, 0) diff --git a/kitty/sprites.c b/kitty/sprites.c index 1300b1e60..61e4e91f0 100644 --- a/kitty/sprites.c +++ b/kitty/sprites.c @@ -46,13 +46,13 @@ sprite_map_set_error(int error) { } } -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; +void +sprite_map_set_limits(size_t max_texture_size, size_t max_array_len) { + sprite_map.max_texture_size = max_texture_size; + sprite_map.max_array_len = max_array_len; } -PyObject* +void sprite_map_free() { SpritePosition *s, *t; for (size_t i = 0; i < sizeof(sprite_map.cache)/sizeof(sprite_map.cache[0]); i++) { @@ -64,7 +64,6 @@ sprite_map_free() { PyMem_Free(t); } } - Py_RETURN_NONE; } static inline void @@ -125,21 +124,17 @@ set_sprite_position(Cell *cell, Cell *previous_cell) { cell->sprite_z = sp->z; } -PyObject* -sprite_map_increment() { -#define increment_doc "Increment the current position and return the old (x, y, z) values" - unsigned int x = sprite_map.x, y = sprite_map.y, z = sprite_map.z; +int +sprite_map_increment(sprite_index *x, sprite_index *y, sprite_index *z) { int error = 0; + *x = sprite_map.x; *y = sprite_map.y; *z = sprite_map.z; do_increment(&error); - if (error) { sprite_map_set_error(error); return NULL; } - return Py_BuildValue("III", x, y, z); + return error; } -PyObject* -sprite_map_set_layout(PyObject UNUSED *s_, PyObject *args) { +void +sprite_map_set_layout(unsigned int cell_width, unsigned int cell_height) { // 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); @@ -158,7 +153,6 @@ sprite_map_set_layout(PyObject UNUSED *s_, PyObject *args) { } while (s != NULL); } sprite_map.dirty = true; - Py_RETURN_NONE; } void @@ -166,11 +160,6 @@ 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); -} - 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" @@ -183,49 +172,35 @@ sprite_position_for(PyObject UNUSED *self, PyObject *args) { return Py_BuildValue("III", pos->x, pos->y, pos->z); } -PyObject* -render_dirty_sprites(PyObject UNUSED *s_) { +void +render_dirty_sprites(void (*render)(PyObject*, bool, bool, bool, sprite_index, sprite_index, sprite_index)) { #define render_dirty_cells_doc "Render all cells that are marked as dirty" - if (!sprite_map.dirty) { Py_RETURN_NONE; } - PyObject *ans = PyList_New(0); - if (ans == NULL) return NULL; + if (!sprite_map.dirty) return; for (size_t i = 0; i < sizeof(sprite_map.cache)/sizeof(sprite_map.cache[0]); i++) { SpritePosition *sp = &(sprite_map.cache[i]); do { if (sp->filled && !sp->rendered) { PyObject *text = line_text_at(sp->ch & CHAR_MASK, sp->cc); - if (text == NULL) { Py_CLEAR(ans); return NULL; } char_type attrs = sp->ch >> ATTRS_SHIFT; bool bold = (attrs >> BOLD_SHIFT) & 1, italic = (attrs >> ITALIC_SHIFT) & 1; - PyObject *x = Py_BuildValue("OOOOHHH", text, bold ? Py_True : Py_False, italic ? Py_True : Py_False, sp->is_second ? Py_True : Py_False, sp->x, sp->y, sp->z); + render(text, bold, italic, sp->is_second, sp->x, sp->y, sp->z); Py_CLEAR(text); - if (x == NULL) { Py_CLEAR(ans); return NULL; } - if (PyList_Append(ans, x) != 0) { Py_CLEAR(x); Py_CLEAR(ans); return NULL; } - Py_CLEAR(x); sp->rendered = true; } sp = sp->next; } while(sp); } sprite_map.dirty = false; - return ans; } static PyMethodDef module_methods[] = { - {"sprite_map_set_limits", (PyCFunction)sprite_map_set_limits, METH_VARARGS, ""}, \ - {"sprite_map_set_layout", (PyCFunction)sprite_map_set_layout, METH_VARARGS, ""}, \ - {"sprite_map_free", (PyCFunction)sprite_map_free, METH_NOARGS, ""}, \ - {"sprite_map_increment", (PyCFunction)sprite_map_increment, METH_NOARGS, ""}, \ {"sprite_position_for", (PyCFunction)sprite_position_for, METH_VARARGS, ""}, \ - {"render_dirty_sprites", (PyCFunction)render_dirty_sprites, METH_NOARGS, ""}, \ - {"sprite_map_current_layout", (PyCFunction)current_layout, METH_NOARGS, ""}, \ {NULL, NULL, 0, NULL} /* Sentinel */ }; - bool init_sprites(PyObject *module) { if (PyModule_AddFunctions(module, module_methods) != 0) return false; diff --git a/kitty/sprites.h b/kitty/sprites.h new file mode 100644 index 000000000..0ac115d71 --- /dev/null +++ b/kitty/sprites.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2017 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#pragma once + + +void sprite_map_current_layout(unsigned int *x, unsigned int *y, unsigned int*); +void sprite_map_set_layout(unsigned int cell_width, unsigned int cell_height); +void sprite_map_set_limits(size_t max_texture_size, size_t max_array_len); +void sprite_map_free(); +int sprite_map_increment(sprite_index *x, sprite_index *y, sprite_index *z); +void render_dirty_sprites(void (*render)(PyObject*, bool, bool, bool, sprite_index, sprite_index, sprite_index));