kitty/kitty/cursor.c
Kovid Goyal 8dea5b3e3e
Reduce data sent to GPU per draw by 30%
Split up the Cell structure into a CPUCell and a GPUCell. Only the
GPUCell part needs to be sent to the GPU. Should make kitty use even
less system resources and make a performance difference on systems where
the GPU bandwidth is constrained.

Also allows adding more CPU only data in the future without affecting
GPU bandwidth. For example, hyperlinks or more combining characters.
2018-05-27 21:25:09 +05:30

365 lines
12 KiB
C

/*
* cursor.c
* Copyright (C) 2016 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "data-types.h"
#include <structmember.h>
static PyObject *
new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
Cursor *self;
self = (Cursor *)type->tp_alloc(type, 0);
return (PyObject*) self;
}
static void
dealloc(Cursor* self) {
Py_TYPE(self)->tp_free((PyObject*)self);
}
#define EQ(x) (a->x == b->x)
static int __eq__(Cursor *a, Cursor *b) {
return EQ(bold) && EQ(italic) && EQ(strikethrough) && EQ(dim) && EQ(reverse) && EQ(decoration) && EQ(fg) && EQ(bg) && EQ(decoration_fg) && EQ(x) && EQ(y) && EQ(shape) && EQ(blink);
}
static const char* cursor_names[NUM_OF_CURSOR_SHAPES] = { "NO_SHAPE", "BLOCK", "BEAM", "UNDERLINE" };
#define BOOL(x) ((x) ? Py_True : Py_False)
static PyObject *
repr(Cursor *self) {
return PyUnicode_FromFormat(
"Cursor(x=%u, y=%u, shape=%s, blink=%R, fg=#%08x, bg=#%08x, bold=%R, italic=%R, reverse=%R, strikethrough=%R, dim=%R, decoration=%d, decoration_fg=#%08x)",
self->x, self->y, (self->shape < NUM_OF_CURSOR_SHAPES ? cursor_names[self->shape] : "INVALID"),
BOOL(self->blink), self->fg, self->bg, BOOL(self->bold), BOOL(self->italic), BOOL(self->reverse), BOOL(self->strikethrough), BOOL(self->dim), self->decoration, self->decoration_fg
);
}
void
cursor_reset_display_attrs(Cursor *self) {
self->bg = 0; self->fg = 0; self->decoration_fg = 0;
self->decoration = 0; self->bold = false; self->italic = false; self->reverse = false; self->strikethrough = false; self->dim = false;
}
static inline void
parse_color(unsigned int *params, unsigned int *i, unsigned int count, uint32_t *result) {
unsigned int attr;
uint8_t r, g, b;
if (*i < count) {
attr = params[(*i)++];
switch(attr) {
case 5:
if (*i < count) *result = (params[(*i)++] & 0xFF) << 8 | 1;
break;
case 2: \
if (*i < count - 2) {
/* Ignore the first parameter in a four parameter RGB */
/* sequence (unused color space id), see https://github.com/kovidgoyal/kitty/issues/227 */
if (*i < count - 3) (*i)++;
r = params[(*i)++] & 0xFF;
g = params[(*i)++] & 0xFF;
b = params[(*i)++] & 0xFF;
*result = r << 24 | g << 16 | b << 8 | 2;
}
break;
}
}
}
void
cursor_from_sgr(Cursor *self, unsigned int *params, unsigned int count) {
#define SET_COLOR(which) { parse_color(params, &i, count, &self->which); } break;
START_ALLOW_CASE_RANGE
unsigned int i = 0, attr;
if (!count) { params[0] = 0; count = 1; }
while (i < count) {
attr = params[i++];
switch(attr) {
case 0:
cursor_reset_display_attrs(self); break;
case 1:
self->bold = true; break;
case 2:
self->dim = true; break;
case 3:
self->italic = true; break;
case 4:
if (i < count) { self->decoration = MIN(3, params[i]); i++; }
else self->decoration = 1;
break;
case 7:
self->reverse = true; break;
case 9:
self->strikethrough = true; break;
case 21:
self->decoration = 2; break;
case 22:
self->bold = false; self->dim = false; break;
case 23:
self->italic = false; break;
case 24:
self->decoration = 0; break;
case 27:
self->reverse = false; break;
case 29:
self->strikethrough = false; break;
case 30 ... 37:
self->fg = ((attr - 30) << 8) | 1; break;
case 38:
SET_COLOR(fg);
case 39:
self->fg = 0; break;
case 40 ... 47:
self->bg = ((attr - 40) << 8) | 1; break;
case 48:
SET_COLOR(bg);
case 49:
self->bg = 0; break;
case 90 ... 97:
self->fg = ((attr - 90 + 8) << 8) | 1; break;
case 100 ... 107:
self->bg = ((attr - 100 + 8) << 8) | 1; break;
case DECORATION_FG_CODE:
SET_COLOR(decoration_fg);
case DECORATION_FG_CODE + 1:
self->decoration_fg = 0; break;
}
}
#undef SET_COLOR
END_ALLOW_CASE_RANGE
}
void
apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, unsigned int *params, unsigned int count) {
#define RANGE for(unsigned c = 0; c < cell_count; c++, cell++)
#define SET(shift) RANGE { cell->attrs |= (1 << shift); } break;
#define RESET(shift) RANGE { cell->attrs &= ~(1 << shift); } break;
#define RESET2(shift1, shift2) RANGE { cell->attrs &= ~((1 << shift1) | (1 << shift2)); } break;
#define SETM(val, mask, shift) { RANGE { cell->attrs &= ~(mask << shift); cell->attrs |= ((val) << shift); } break; }
#define SET_COLOR(which) { color_type color = 0; parse_color(params, &i, count, &color); if (color) { RANGE { cell->which = color; }} } break;
#define SIMPLE(which, val) RANGE { cell->which = (val); } break;
unsigned int i = 0, attr;
if (!count) { params[0] = 0; count = 1; }
while (i < count) {
GPUCell *cell = first_cell;
attr = params[i++];
switch(attr) {
case 0:
RANGE { cell->attrs &= WIDTH_MASK; cell->fg = 0; cell->bg = 0; cell->decoration_fg = 0; }
break;
case 1:
SET(BOLD_SHIFT);
case 2:
SET(DIM_SHIFT);
case 3:
SET(ITALIC_SHIFT);
case 4:
if (i < count) { uint8_t val = MIN(3, params[i]); i++; SETM(val, DECORATION_MASK, DECORATION_SHIFT); }
else { SETM(1, DECORATION_MASK, DECORATION_SHIFT); }
case 7:
SET(REVERSE_SHIFT);
case 9:
SET(STRIKE_SHIFT);
case 21:
SETM(2, DECORATION_MASK, DECORATION_SHIFT);
case 22:
RESET2(DIM_SHIFT, BOLD_SHIFT);
case 23:
RESET(ITALIC_SHIFT);
case 24:
SETM(0, DECORATION_MASK, DECORATION_SHIFT);
case 27:
RESET(REVERSE_SHIFT);
case 29:
RESET(STRIKE_SHIFT);
START_ALLOW_CASE_RANGE
case 30 ... 37:
SIMPLE(fg, ((attr - 30) << 8) | 1);
case 38:
SET_COLOR(fg);
case 39:
SIMPLE(fg, 0);
case 40 ... 47:
SIMPLE(bg, ((attr - 40) << 8) | 1);
case 48:
SET_COLOR(bg);
case 49:
SIMPLE(bg, 0);
case 90 ... 97:
SIMPLE(fg, ((attr - 90 + 8) << 8) | 1);
case 100 ... 107:
SIMPLE(bg, ((attr - 100 + 8) << 8) | 1);
END_ALLOW_CASE_RANGE
case DECORATION_FG_CODE:
SET_COLOR(decoration_fg);
case DECORATION_FG_CODE + 1:
SIMPLE(decoration_fg, 0);
}
}
#undef RESET
#undef RESET2
#undef SET_COLOR
#undef SET
#undef SETM
#undef RANGE
}
static inline int
color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsigned aix_code, unsigned complex_code) {
switch(val & 0xff) {
case 1:
val >>= 8;
if (val < 16 && simple_code) {
return snprintf(buf, sz, "%lu;", (val < 8) ? simple_code + val : aix_code + (val - 8));
}
return snprintf(buf, sz, "%u:5:%lu;", complex_code, val);
case 2:
return snprintf(buf, sz, "%u:2:%lu:%lu:%lu;", complex_code, (val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff);
default:
return snprintf(buf, sz, "%u;", complex_code + 1); // reset
}
}
static inline const char*
decoration_as_sgr(uint8_t decoration) {
switch(decoration) {
case 1: return "4";
case 2: return "4:2";
case 3: return "4:3";
default: return "24";
}
}
const char*
cursor_as_sgr(Cursor *self, Cursor *prev) {
static char buf[128];
#define SZ sizeof(buf) - (p - buf) - 2
#define P(fmt, ...) { p += snprintf(p, SZ, fmt ";", __VA_ARGS__); }
char *p = buf;
bool intensity_differs = self->bold != prev->bold || self->dim != prev->dim;
if (intensity_differs) {
if (!self->bold || !self->dim) { P("%d", 22); }
else P("%d;%d", 1, 2);
}
if (self->italic != prev->italic) P("%d", self->italic ? 3 : 23);
if (self->reverse != prev->reverse) P("%d", self->reverse ? 7 : 27);
if (self->strikethrough != prev->strikethrough) P("%d", self->strikethrough ? 9 : 29);
if (self->decoration != prev->decoration) P("%s", decoration_as_sgr(self->decoration));
if (self->fg != prev->fg) p += color_as_sgr(p, SZ, self->fg, 30, 90, 38);
if (self->bg != prev->bg) p += color_as_sgr(p, SZ, self->bg, 40, 100, 48);
if (self->decoration_fg != prev->decoration_fg) p += color_as_sgr(p, SZ, self->decoration_fg, 0, 0, DECORATION_FG_CODE);
#undef P
#undef SZ
if (p > buf) *(p - 1) = 0; // remove trailing semi-colon
*p = 0; // ensure string is null-terminated
return buf;
}
static PyObject *
reset_display_attrs(Cursor *self, PyObject *a UNUSED) {
#define reset_display_attrs_doc "Reset all display attributes to unset"
cursor_reset_display_attrs(self);
Py_RETURN_NONE;
}
void cursor_reset(Cursor *self) {
cursor_reset_display_attrs(self);
self->x = 0; self->y = 0;
self->shape = NO_CURSOR_SHAPE; self->blink = false;
}
void cursor_copy_to(Cursor *src, Cursor *dest) {
#define CCY(x) dest->x = src->x;
CCY(x); CCY(y); CCY(shape); CCY(blink);
CCY(bold); CCY(italic); CCY(strikethrough); CCY(dim); CCY(reverse); CCY(decoration); CCY(fg); CCY(bg); CCY(decoration_fg);
}
static PyObject*
copy(Cursor *self, PyObject*);
#define copy_doc "Create a clone of this cursor"
// Boilerplate {{{
BOOL_GETSET(Cursor, bold)
BOOL_GETSET(Cursor, italic)
BOOL_GETSET(Cursor, reverse)
BOOL_GETSET(Cursor, strikethrough)
BOOL_GETSET(Cursor, dim)
BOOL_GETSET(Cursor, blink)
static PyMemberDef members[] = {
{"x", T_UINT, offsetof(Cursor, x), 0, "x"},
{"y", T_UINT, offsetof(Cursor, y), 0, "y"},
{"shape", T_INT, offsetof(Cursor, shape), 0, "shape"},
{"decoration", T_UBYTE, offsetof(Cursor, decoration), 0, "decoration"},
{"fg", T_UINT, offsetof(Cursor, fg), 0, "fg"},
{"bg", T_UINT, offsetof(Cursor, bg), 0, "bg"},
{"decoration_fg", T_UINT, offsetof(Cursor, decoration_fg), 0, "decoration_fg"},
{NULL} /* Sentinel */
};
static PyGetSetDef getseters[] = {
GETSET(bold)
GETSET(italic)
GETSET(reverse)
GETSET(strikethrough)
GETSET(dim)
GETSET(blink)
{NULL} /* Sentinel */
};
static PyMethodDef methods[] = {
METHOD(copy, METH_NOARGS)
METHOD(reset_display_attrs, METH_NOARGS)
{NULL} /* Sentinel */
};
static PyObject *
richcmp(PyObject *obj1, PyObject *obj2, int op);
PyTypeObject Cursor_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "fast_data_types.Cursor",
.tp_basicsize = sizeof(Cursor),
.tp_dealloc = (destructor)dealloc,
.tp_repr = (reprfunc)repr,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "Cursors",
.tp_richcompare = richcmp,
.tp_methods = methods,
.tp_members = members,
.tp_getset = getseters,
.tp_new = new,
};
RICHCMP(Cursor)
// }}}
Cursor*
cursor_copy(Cursor *self) {
Cursor* ans;
ans = alloc_cursor();
if (ans == NULL) { PyErr_NoMemory(); return NULL; }
cursor_copy_to(self, ans);
return ans;
}
static PyObject*
copy(Cursor *self, PyObject *a UNUSED) {
return (PyObject*)cursor_copy(self);
}
Cursor *alloc_cursor() {
return (Cursor*)new(&Cursor_Type, NULL, NULL);
}
INIT_TYPE(Cursor)