mirror of
https://github.com/kovidgoyal/kitty.git
synced 2024-09-19 02:27:10 +03:00
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.
This commit is contained in:
parent
4feff2e2da
commit
6127d2d122
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "glfw.h"
|
||||
#include "gl.h"
|
||||
#include "modes.h"
|
||||
#include "sprites.h"
|
||||
#include <stddef.h>
|
||||
#ifdef WITH_PROFILER
|
||||
#include <gperftools/profiler.h>
|
||||
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
222
kitty/sprites.c
222
kitty/sprites.c
@ -6,28 +6,46 @@
|
||||
*/
|
||||
|
||||
#include "data-types.h"
|
||||
#include "sprites.h"
|
||||
#include <structmember.h>
|
||||
|
||||
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)
|
||||
// }}}
|
||||
|
||||
|
||||
|
39
kitty/sprites.h
Normal file
39
kitty/sprites.h
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* 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, ""}, \
|
||||
|
@ -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()
|
||||
|
@ -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++) {
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user