mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 22:28:24 +02:00
Speed up the update_cell_data inner loop
This commit is contained in:
@@ -4,9 +4,9 @@
|
||||
|
||||
from collections import namedtuple
|
||||
from copy import copy
|
||||
from ctypes import c_uint
|
||||
from itertools import chain, repeat
|
||||
from ctypes import c_uint, addressof
|
||||
from queue import Queue, Empty
|
||||
from threading import Lock
|
||||
|
||||
from .config import build_ansi_color_table, to_color
|
||||
from .fonts import set_font_family
|
||||
@@ -144,6 +144,7 @@ empty_cell = (' ', 0)
|
||||
class CharGrid:
|
||||
|
||||
def __init__(self, screen, opts, window_width, window_height):
|
||||
self.lock = Lock()
|
||||
self.dpix, self.dpiy = get_logical_dpi()
|
||||
self.width, self.height = window_width, window_height
|
||||
self.color_profile = ColorProfile()
|
||||
@@ -162,6 +163,7 @@ class CharGrid:
|
||||
self.render_queue.put(RenderData(
|
||||
viewport=Size(self.width, self.height), clear_color=self.original_bg,
|
||||
cursor=self.default_cursor))
|
||||
self.sprites.ensure_state()
|
||||
|
||||
def destroy(self):
|
||||
self.sprites.destroy()
|
||||
@@ -191,7 +193,6 @@ class CharGrid:
|
||||
self.screen_geometry = sg = calculate_vertices(self.cell_width, self.cell_height, self.width, self.height)
|
||||
self.screen.resize(sg.ynum, sg.xnum)
|
||||
self.sprite_map = (c_uint * (sg.ynum * sg.xnum * 9))()
|
||||
self.sprite_text = list(repeat(empty_cell, sg.xnum * sg.ynum))
|
||||
self.update_cell_data(add_viewport_data=True)
|
||||
|
||||
def change_colors(self, changes):
|
||||
@@ -228,58 +229,38 @@ class CharGrid:
|
||||
|
||||
dfbg = self.default_bg
|
||||
dffg = self.default_fg
|
||||
dfbg = dfbg[0] << 16 | dfbg[1] << 8 | dfbg[2]
|
||||
dffg = dffg[0] << 16 | dffg[1] << 8 | dffg[2]
|
||||
ptr = addressof(self.sprite_map)
|
||||
|
||||
for y in lines:
|
||||
self.update_line(y, range(sg.xnum), dffg, dfbg)
|
||||
with self.lock:
|
||||
for y in lines:
|
||||
self.update_line(y, [(0, sg.xnum - 1)], dffg, dfbg, ptr)
|
||||
|
||||
for y, ranges in cell_ranges.items():
|
||||
self.update_line(y, chain.from_iterable(range(start, stop + 1) for start, stop in ranges),
|
||||
dffg, dfbg)
|
||||
for y, ranges in cell_ranges.items():
|
||||
self.update_line(y, ranges, dffg, dfbg, ptr)
|
||||
|
||||
rd.cell_data = copy(self.sprite_map), self.sprite_text[:]
|
||||
rd.cell_data = copy(self.sprite_map)
|
||||
rd.sprite_layout = self.sprites.layout
|
||||
c = changes.get('cursor')
|
||||
if c is not None:
|
||||
rd.cursor = Cursor(c.x, c.y, c.hidden, c.shape, c.color, c.blink)
|
||||
self.render_queue.put(rd)
|
||||
|
||||
def update_line(self, y, cell_range, dffg, dfbg):
|
||||
def update_line(self, y, cell_ranges, dffg, dfbg, ptr):
|
||||
line = self.screen.line(y)
|
||||
for x in cell_range:
|
||||
self.update_cell(line, x, y, dffg, dfbg)
|
||||
|
||||
def update_cell(self, line, x, y, dffg, dfbg):
|
||||
ch, attrs, colors = line.basic_cell_data(x)
|
||||
idx = x + y * self.screen_geometry.xnum
|
||||
offset = idx * 9
|
||||
bgcol = colors >> COL_SHIFT
|
||||
if bgcol:
|
||||
bgcol = self.as_color(bgcol) or dfbg
|
||||
else:
|
||||
bgcol = dfbg
|
||||
fgcol = colors & COL_MASK
|
||||
if fgcol:
|
||||
fgcol = self.as_color(fgcol) or dffg
|
||||
else:
|
||||
fgcol = dffg
|
||||
if attrs & REVERSE_MASK:
|
||||
self.sprite_map[offset + 3:offset + 6] = bgcol
|
||||
self.sprite_map[offset + 6:offset + 9] = fgcol
|
||||
else:
|
||||
self.sprite_map[offset + 3:offset + 6] = fgcol
|
||||
self.sprite_map[offset + 6:offset + 9] = bgcol
|
||||
if ch == 0 or ch == 32:
|
||||
self.sprite_text[idx] = empty_cell
|
||||
else:
|
||||
self.sprite_text[idx] = line[x], attrs
|
||||
for x, xmax in cell_ranges:
|
||||
self.sprites.update_cell_data(line, x, xmax, self.color_profile, dfbg, dffg, ptr)
|
||||
|
||||
def render(self):
|
||||
' This is the only method in this class called in the UI thread (apart from __init__) '
|
||||
glClear(GL_COLOR_BUFFER_BIT)
|
||||
cell_data_changed = self.get_all_render_changes()
|
||||
with self.sprites:
|
||||
with self.lock:
|
||||
self.sprites.render_dirty_cells()
|
||||
if cell_data_changed:
|
||||
self.update_sprite_map()
|
||||
self.sprites.set_sprite_map(self.last_render_data.cell_data)
|
||||
data = self.last_render_data
|
||||
|
||||
if data.screen_geometry is None:
|
||||
@@ -306,15 +287,6 @@ class CharGrid:
|
||||
data.update(rd)
|
||||
return cell_data_changed
|
||||
|
||||
def update_sprite_map(self):
|
||||
spmap, sptext = self.last_render_data.cell_data
|
||||
psp = self.sprites.primary_sprite_position
|
||||
empty_val = psp(empty_cell) # Ensure the empty cell is 0, 0, 0
|
||||
for i, key in enumerate(sptext):
|
||||
f = i * 9
|
||||
spmap[f:f + 3] = empty_val if key is empty_cell else psp(key)
|
||||
self.sprites.set_sprite_map(spmap)
|
||||
|
||||
def render_cells(self, sg, sprite_layout):
|
||||
with self.program:
|
||||
ul = self.program.uniform_location
|
||||
|
||||
@@ -76,24 +76,24 @@ update_ansi_color_table(ColorProfile *self, PyObject *val) {
|
||||
|
||||
if (!PyList_Check(val)) { PyErr_SetString(PyExc_TypeError, "color table must be a list"); return NULL; }
|
||||
|
||||
#define to_color \
|
||||
#define TO_COLOR \
|
||||
t = PyList_GET_ITEM(val, i); \
|
||||
self->ansi_color_table[i] = PyLong_AsUnsignedLong(t);
|
||||
|
||||
for(i = 30; i < 38; i++) {
|
||||
to_color;
|
||||
TO_COLOR;
|
||||
}
|
||||
i = 39; to_color;
|
||||
i = 39; TO_COLOR;
|
||||
for(i = 90; i < 98; i++) {
|
||||
to_color;
|
||||
TO_COLOR;
|
||||
}
|
||||
i = 99; to_color;
|
||||
i = 99; TO_COLOR;
|
||||
for(i = 40; i < 48; i++) {
|
||||
to_color;
|
||||
TO_COLOR;
|
||||
}
|
||||
i = 49; to_color;
|
||||
i = 49; TO_COLOR;
|
||||
for(i = 100; i < 108; i++) {
|
||||
to_color;
|
||||
TO_COLOR;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -151,6 +151,22 @@ as_color(ColorProfile *self, PyObject *val) {
|
||||
return ans;
|
||||
}
|
||||
|
||||
uint32_t to_color(ColorProfile *self, uint32_t entry, uint32_t defval) {
|
||||
unsigned int t = entry & 0xFF, r;
|
||||
switch(t) {
|
||||
case 1:
|
||||
r = (entry >> 8) & 0xff;
|
||||
return self->ansi_color_table[r];
|
||||
case 2:
|
||||
r = (entry >> 8) & 0xff;
|
||||
return self->color_table_256[r];
|
||||
case 3:
|
||||
return entry >> 8;
|
||||
default:
|
||||
return defval;
|
||||
}
|
||||
}
|
||||
|
||||
// Boilerplate {{{
|
||||
|
||||
|
||||
@@ -163,7 +179,7 @@ static PyMethodDef methods[] = {
|
||||
};
|
||||
|
||||
|
||||
static PyTypeObject ColorProfile_Type = {
|
||||
PyTypeObject ColorProfile_Type = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "fast_data_types.ColorProfile",
|
||||
.tp_basicsize = sizeof(ColorProfile),
|
||||
|
||||
@@ -171,9 +171,10 @@ struct SpritePosition {
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
size_t max_array_len, max_texture_size, xnum, ynum, max_y;
|
||||
unsigned int x, y, z;
|
||||
size_t max_array_len, max_texture_size, max_y;
|
||||
unsigned int x, y, z, xnum, ynum;
|
||||
SpritePosition cache[1024];
|
||||
bool dirty;
|
||||
|
||||
} SpriteMap;
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ from kitty.fast_data_types import (
|
||||
glViewport, enable_automatic_opengl_error_checking, glClearColor,
|
||||
glUniform2f, glUniform4f, glUniform2ui, glUniform1i, glewInit, glGetString,
|
||||
GL_VERSION as GL_VERSION_C, GL_VENDOR, GL_SHADING_LANGUAGE_VERSION, GL_RENDERER,
|
||||
glClear, GL_COLOR_BUFFER_BIT, GL_TRIANGLE_FAN, glDrawArraysInstanced
|
||||
glClear, GL_COLOR_BUFFER_BIT, GL_TRIANGLE_FAN, glDrawArraysInstanced,
|
||||
Cursor, LineBuf, ColorProfile
|
||||
)
|
||||
|
||||
|
||||
@@ -32,11 +33,12 @@ class Renderer:
|
||||
|
||||
def __init__(self, w, h):
|
||||
self.w, self.h = w, h
|
||||
self.color_pairs = [
|
||||
((255, 255, 255), (0, 0, 0)),
|
||||
((0, 0, 0), (255, 255, 255)),
|
||||
((255, 255, 0), (0, 0, 255)),
|
||||
]
|
||||
self.color_pairs = (
|
||||
(0xffffff, 0),
|
||||
(0, 0xffffff),
|
||||
(0xffff00, 0x0000ff)
|
||||
)
|
||||
self.color_profile = ColorProfile()
|
||||
self.program = ShaderProgram(*cell_shader)
|
||||
self.sprites = Sprites()
|
||||
self.sprites.initialize()
|
||||
@@ -54,15 +56,23 @@ class Renderer:
|
||||
self.sprites.ensure_state()
|
||||
self.screen_geometry = sg = calculate_vertices(cell_width, cell_height, self.w, self.h)
|
||||
data = (ctypes.c_uint * (sg.xnum * sg.ynum * 9))()
|
||||
for i in range(0, len(data), 9):
|
||||
idx = i // 9
|
||||
c = '%d' % (idx % 10)
|
||||
data[i:i+3] = self.sprites.primary_sprite_position((c, 0))
|
||||
fg, bg = self.color_pairs[idx % 3]
|
||||
data[i+3:i+9] = fg + bg
|
||||
lb = LineBuf(sg.ynum, sg.xnum)
|
||||
i = -1
|
||||
for y in range(sg.ynum):
|
||||
line = lb.line(y)
|
||||
for x in range(sg.xnum):
|
||||
i += 1
|
||||
c = Cursor()
|
||||
fg, bg = self.color_pairs[i % 3]
|
||||
c.fg = (fg << 8) | 3
|
||||
c.bg = (bg << 8) | 3
|
||||
c.x = x
|
||||
line.set_text('%d' % (i % 10), 0, 1, c)
|
||||
self.sprites.update_cell_data(line, 0, sg.xnum - 1, self.color_profile, 0xffffff, 0, ctypes.addressof(data))
|
||||
self.sprites.set_sprite_map(data)
|
||||
|
||||
def render(self):
|
||||
self.sprites.render_dirty_cells()
|
||||
with self.program:
|
||||
ul = self.program.uniform_location
|
||||
sg = self.screen_geometry
|
||||
|
||||
23
kitty/line.c
23
kitty/line.c
@@ -25,17 +25,8 @@ dealloc(Line* self) {
|
||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
text_at(Line* self, Py_ssize_t xval) {
|
||||
#define text_at_doc "[x] -> Return the text in the specified cell"
|
||||
char_type ch;
|
||||
combining_type cc;
|
||||
PyObject* line_text_at(char_type ch, combining_type cc) {
|
||||
PyObject *ans;
|
||||
|
||||
if (xval >= self->xnum) { PyErr_SetString(PyExc_IndexError, "Column number out of bounds"); return NULL; }
|
||||
|
||||
ch = self->chars[xval] & CHAR_MASK;
|
||||
cc = self->combining_chars[xval];
|
||||
if (cc == 0) {
|
||||
ans = PyUnicode_New(1, ch);
|
||||
if (ans == NULL) return PyErr_NoMemory();
|
||||
@@ -53,6 +44,18 @@ text_at(Line* self, Py_ssize_t xval) {
|
||||
return ans;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
text_at(Line* self, Py_ssize_t xval) {
|
||||
#define text_at_doc "[x] -> Return the text in the specified cell"
|
||||
char_type ch;
|
||||
combining_type cc;
|
||||
|
||||
if (xval >= self->xnum) { PyErr_SetString(PyExc_IndexError, "Column number out of bounds"); return NULL; }
|
||||
ch = self->chars[xval] & CHAR_MASK;
|
||||
cc = self->combining_chars[xval];
|
||||
return line_text_at(ch, cc);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
as_unicode(Line* self) {
|
||||
Py_ssize_t n = 0;
|
||||
|
||||
@@ -924,7 +924,7 @@ class Screen:
|
||||
# This is somewhat non-standard but is nonetheless
|
||||
# supported in quite a few terminals. See discussion
|
||||
# here https://gist.github.com/XVilka/8346728.
|
||||
r, gr, b = attrs.pop() << 8, attrs.pop() << 16, attrs.pop() << 24
|
||||
r, gr, b = attrs.pop() << 24, attrs.pop() << 16, attrs.pop() << 8
|
||||
setattr(c, key, r | gr | b | 3)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
@@ -20,7 +20,7 @@ from .fast_data_types import (
|
||||
GL_NEAREST, GL_TEXTURE_WRAP_T, glGenBuffers, GL_R8, GL_RED,
|
||||
GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_STATIC_DRAW, GL_TEXTURE_BUFFER,
|
||||
GL_RGB32UI, glBindBuffer, glPixelStorei, glTexBuffer, glActiveTexture,
|
||||
glTexStorage3D, glCopyImageSubData, glTexSubImage3D, ITALIC, BOLD
|
||||
glTexStorage3D, glCopyImageSubData, glTexSubImage3D, ITALIC, BOLD, SpriteMap
|
||||
)
|
||||
|
||||
GL_VERSION = (3, 3)
|
||||
@@ -45,27 +45,47 @@ class Sprites:
|
||||
self.x = self.y = self.z = 0
|
||||
self.texture_id = self.buffer_id = self.buffer_texture_id = None
|
||||
self.last_num_of_layers = 1
|
||||
self.last_ynum = -1
|
||||
self.update_cell_data = lambda *a: None
|
||||
|
||||
def initialize(self):
|
||||
self.texture_unit = GL_TEXTURE0
|
||||
self.max_array_len = glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS)
|
||||
self.max_texture_size = glGetIntegerv(GL_MAX_TEXTURE_SIZE)
|
||||
self.backend = SpriteMap(glGetIntegerv(GL_MAX_TEXTURE_SIZE), glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS))
|
||||
self.update_cell_data = self.backend.update_cell_data
|
||||
self.do_layout(getattr(self, 'cell_width', 1), getattr(self, 'cell_height', 1))
|
||||
|
||||
def do_layout(self, cell_width=1, cell_height=1):
|
||||
self.cell_width, self.cell_height = cell_width or 1, cell_height or 1
|
||||
self.first_cell_cache = {}
|
||||
self.second_cell_cache = {}
|
||||
self.xnum = max(1, self.max_texture_size // self.cell_width)
|
||||
self.max_y = max(1, self.max_texture_size // self.cell_height)
|
||||
self.ynum = 1
|
||||
self.cell_width, self.cell_height = cell_width, cell_height
|
||||
self.backend.layout(cell_width or 1, cell_height or 1)
|
||||
if self.texture_id is not None:
|
||||
glDeleteTexture(self.texture_id)
|
||||
self.texture_id = None
|
||||
|
||||
@property
|
||||
def layout(self):
|
||||
return 1 / self.xnum, 1 / self.ynum
|
||||
return 1 / self.backend.xnum, 1 / self.backend.ynum
|
||||
|
||||
def render_cell(self, text, bold, italic, is_second):
|
||||
first, second = render_cell(text, bold, italic)
|
||||
if is_second:
|
||||
return second or first
|
||||
return first
|
||||
|
||||
def render_dirty_cells(self):
|
||||
self.backend.render_dirty_cells(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:
|
||||
self.realloc_texture()
|
||||
else:
|
||||
if self.backend.z == 0 and self.backend.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, self.backend.z, self.cell_width, self.cell_height, 1, GL_RED, GL_UNSIGNED_BYTE, addressof(buf))
|
||||
glBindTexture(tgt, 0)
|
||||
|
||||
def realloc_texture(self):
|
||||
if self.texture_id is None:
|
||||
@@ -79,17 +99,18 @@ class Sprites:
|
||||
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.z + 1
|
||||
width, height = self.xnum * self.cell_width, self.ynum * self.cell_height
|
||||
znum = self.backend.z + 1
|
||||
width, height = self.backend.xnum * self.cell_width, self.backend.ynum * self.cell_height
|
||||
glTexStorage3D(tgt, 1, GL_R8, width, height, znum)
|
||||
if self.texture_id is not None:
|
||||
ynum = self.ynum
|
||||
if self.z == 0:
|
||||
ynum = self.backend.ynum
|
||||
if self.backend.z == 0:
|
||||
ynum -= 1 # Only copy the previous rows
|
||||
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)
|
||||
glDeleteTexture(self.texture_id)
|
||||
self.last_num_of_layers = znum
|
||||
self.last_ynum = self.backend.ynum
|
||||
self.texture_id = tex
|
||||
glBindTexture(tgt, 0)
|
||||
|
||||
@@ -109,58 +130,12 @@ class Sprites:
|
||||
self.buffer_texture_id = glGenTextures(1)
|
||||
self.buffer_texture_unit = GL_TEXTURE1
|
||||
|
||||
def add_sprite(self, buf):
|
||||
tgt = GL_TEXTURE_2D_ARRAY
|
||||
glBindTexture(tgt, self.texture_id)
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
|
||||
x, y = self.x * self.cell_width, self.y * self.cell_height
|
||||
glTexSubImage3D(tgt, 0, x, y, self.z, self.cell_width, self.cell_height, 1, GL_RED, GL_UNSIGNED_BYTE, addressof(buf))
|
||||
glBindTexture(tgt, 0)
|
||||
|
||||
# co-ordinates for this sprite in the sprite sheet
|
||||
x, y, z = self.x, self.y, self.z
|
||||
|
||||
# Now increment the current cell position
|
||||
self.x += 1
|
||||
if self.x >= self.xnum:
|
||||
self.x = 0
|
||||
self.y += 1
|
||||
self.ynum = min(max(self.ynum, self.y + 1), self.max_y)
|
||||
if self.y >= self.max_y:
|
||||
self.y = 0
|
||||
self.z += 1
|
||||
self.realloc_texture() # we allocate a row at a time
|
||||
return x, y, z
|
||||
|
||||
def set_sprite_map(self, data):
|
||||
tgt = GL_TEXTURE_BUFFER
|
||||
glBindBuffer(tgt, self.buffer_id)
|
||||
glBufferData(tgt, sizeof(data), addressof(data), GL_STATIC_DRAW)
|
||||
glBindBuffer(tgt, 0)
|
||||
|
||||
def primary_sprite_position(self, key):
|
||||
' Return a 3-tuple (x, y, z) giving the position of this sprite on the sprite sheet '
|
||||
try:
|
||||
return self.first_cell_cache[key]
|
||||
except KeyError:
|
||||
pass
|
||||
text, attrs = key
|
||||
bold, italic = bool(attrs & BOLD_MASK), bool(attrs & ITALIC_MASK)
|
||||
first, second = render_cell(text, bold, italic)
|
||||
self.first_cell_cache[key] = first = self.add_sprite(first)
|
||||
if second is not None:
|
||||
self.second_cell_cache[key] = self.add_sprite(second)
|
||||
return first
|
||||
|
||||
def secondary_sprite_position(self, key):
|
||||
ans = self.second_cell_cache.get(key)
|
||||
if ans is None:
|
||||
self.primary_sprite_position(key)
|
||||
ans = self.second_cell_cache.get(key)
|
||||
if ans is None:
|
||||
return 0, 0, 0
|
||||
return ans
|
||||
|
||||
def __enter__(self):
|
||||
self.ensure_state()
|
||||
glActiveTexture(self.texture_unit)
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
*/
|
||||
|
||||
#include "data-types.h"
|
||||
#include <structmember.h>
|
||||
extern PyTypeObject Line_Type;
|
||||
extern PyTypeObject ColorProfile_Type;
|
||||
extern uint32_t to_color(ColorProfile *, uint32_t, uint32_t);
|
||||
extern PyObject* line_text_at(char_type, combining_type);
|
||||
|
||||
static PyObject*
|
||||
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
@@ -17,6 +22,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
if (self != NULL) {
|
||||
self->max_array_len = mlen;
|
||||
self->max_texture_size = msz;
|
||||
self->dirty = true;
|
||||
}
|
||||
return (PyObject*) self;
|
||||
}
|
||||
@@ -43,6 +49,7 @@ layout(SpriteMap *self, PyObject *args) {
|
||||
if (!PyArg_ParseTuple(args, "kk", &cell_width, &cell_height)) return NULL;
|
||||
self->xnum = MAX(1, self->max_texture_size / cell_width);
|
||||
self->max_y = MAX(1, self->max_texture_size / cell_height);
|
||||
self->ynum = 1;
|
||||
|
||||
for (size_t i = 0; i < sizeof(self->cache)/sizeof(self->cache[0]); i++) {
|
||||
SpritePosition *s = &(self->cache[i]);
|
||||
@@ -95,8 +102,10 @@ sprite_position_for(SpriteMap *self, char_type ch, combining_type cc, bool is_se
|
||||
s->cc = cc;
|
||||
s->is_second = is_second;
|
||||
s->filled = true;
|
||||
s->rendered = false;
|
||||
s->x = self->x; s->y = self->y; s->z = self->z;
|
||||
increment(self, error);
|
||||
self->dirty = true;
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -122,12 +131,99 @@ position_for(SpriteMap *self, PyObject *args) {
|
||||
if (pos == NULL) {set_sprite_error(error); return NULL; }
|
||||
return Py_BuildValue("III", pos->x, pos->y, pos->z);
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
update_cell_data(SpriteMap *self, PyObject *args) {
|
||||
#define update_cell_data_doc "update_cell_data(line, xstart, xmax, color_profile, default_bg, default_fg, data_pointer) -> Update the range [xstart, xmax] in data_pointer with the data from line"
|
||||
Line *line;
|
||||
unsigned int xstart, xlimit;
|
||||
SpritePosition *sp;
|
||||
PyObject *dp;
|
||||
char_type previous_ch=0, ch;
|
||||
color_type color;
|
||||
uint32_t bg, fg;
|
||||
uint8_t previous_width = 0;
|
||||
ColorProfile *color_profile;
|
||||
unsigned long default_bg, default_fg;
|
||||
int err = 0;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O!IIO!kkO!", &Line_Type, &line, &xstart, &xlimit, &ColorProfile_Type, &color_profile, &default_bg, &default_fg, &PyLong_Type, &dp)) return NULL;
|
||||
|
||||
unsigned int *data = PyLong_AsVoidPtr(dp);
|
||||
size_t base = line->ynum * line->xnum * 9;
|
||||
default_fg &= COL_MASK;
|
||||
default_bg &= COL_MASK;
|
||||
for (size_t i = xstart, offset = base + xstart * 9; i <= xlimit; i++, offset += 9) {
|
||||
ch = line->chars[i];
|
||||
if (previous_width == 2) sp = sprite_position_for(self, previous_ch, 0, true, &err);
|
||||
else sp = sprite_position_for(self, ch, line->combining_chars[i], false, &err);
|
||||
if (sp == NULL) { set_sprite_error(err); return NULL; }
|
||||
data[offset] = sp->x;
|
||||
data[offset+1] = sp->y;
|
||||
data[offset+2] = sp->z;
|
||||
color = line->colors[i];
|
||||
fg = to_color(color_profile, color & COL_MASK, default_fg);
|
||||
bg = to_color(color_profile, color >> COL_SHIFT, default_bg);
|
||||
previous_ch = ch; previous_width = (ch >> ATTRS_SHIFT) & WIDTH_MASK;
|
||||
#define PACK_COL(b, col) data[b] = col >> 16; data[b + 1] = (col >> 8) & 0xff; data[b + 2] = col & 0xff;
|
||||
PACK_COL(offset + 3, fg);
|
||||
PACK_COL(offset + 6, bg);
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
render_dirty_cells(SpriteMap *self, 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; }
|
||||
|
||||
for (size_t i = 0; i < sizeof(self->cache)/sizeof(self->cache[0]); i++) {
|
||||
SpritePosition *sp = &(self->cache[i]);
|
||||
while (sp) {
|
||||
if (sp->filled && !sp->rendered) {
|
||||
PyObject *text = line_text_at(sp->ch & CHAR_MASK, sp->cc);
|
||||
if (text == NULL) return NULL;
|
||||
char_type attrs = sp->ch >> ATTRS_SHIFT;
|
||||
bool bold = (attrs >> BOLD_SHIFT) & 1, italic = (attrs >> ITALIC_SHIFT) & 1;
|
||||
PyObject *rcell = PyObject_CallFunctionObjArgs(render_cell, text, bold ? Py_True : Py_False, italic ? Py_True : Py_False, sp->is_second ? Py_True : Py_False, NULL);
|
||||
Py_CLEAR(text);
|
||||
if (rcell == NULL) return NULL;
|
||||
PyObject *ret = PyObject_CallFunction(send_to_gpu, "IIIN", sp->x, sp->y, sp->z, rcell);
|
||||
Py_CLEAR(rcell);
|
||||
if (ret == NULL) return NULL;
|
||||
Py_CLEAR(ret);
|
||||
sp->rendered = true;
|
||||
}
|
||||
sp = sp->next;
|
||||
}
|
||||
}
|
||||
self->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(update_cell_data, METH_VARARGS)
|
||||
METHOD(render_dirty_cells, METH_VARARGS)
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
@@ -140,6 +236,7 @@ static PyTypeObject SpriteMap_Type = {
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_doc = "SpriteMap",
|
||||
.tp_methods = methods,
|
||||
.tp_members = members,
|
||||
.tp_new = new,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user