pass decorations location as sprite metadata

This commit is contained in:
Kovid Goyal
2024-12-13 13:10:20 +05:30
parent 5d195bf50b
commit 9e9ab3e6f9
8 changed files with 117 additions and 43 deletions

View File

@@ -758,7 +758,6 @@ PyInit_fast_data_types(void) {
#undef s
PyModule_AddIntConstant(m, "MARK_MASK", MARK_MASK);
PyModule_AddIntConstant(m, "DECORATION_MASK", DECORATION_MASK);
PyModule_AddIntConstant(m, "NUM_UNDERLINE_STYLES", NUM_UNDERLINE_STYLES);
PyModule_AddStringMacro(m, ERROR_PREFIX);
#ifdef KITTY_VCS_REV
PyModule_AddStringMacro(m, KITTY_VCS_REV);

View File

@@ -276,7 +276,6 @@ GRAPHICS_PROGRAM: int
MARK: int
MARK_MASK: int
DECORATION_MASK: int
NUM_UNDERLINE_STYLES: int
FILE_TRANSFER_CODE: int
ESC_CSI: int
ESC_OSC: int

View File

@@ -15,13 +15,12 @@
#include "decorations.h"
#include "glyph-cache.h"
#define MISSING_GLYPH (NUM_UNDERLINE_STYLES + 2)
#define MISSING_GLYPH 1
#define MAX_NUM_EXTRA_GLYPHS_PUA 4u
#define debug debug_fonts
static PyObject *python_send_to_gpu_impl = NULL;
#define current_send_sprite_to_gpu(...) (python_send_to_gpu_impl ? python_send_to_gpu(__VA_ARGS__) : send_sprite_to_gpu(__VA_ARGS__))
extern PyTypeObject Line_Type;
enum {NO_FONT=-3, MISSING_FONT=-2, BLANK_FONT=-1, BOX_FONT=0};
@@ -91,6 +90,22 @@ typedef struct ScaledFontData {
#define CMPR_FN vt_cmpr_float
#include "kitty-verstable.h"
typedef union DecorationsKey {
struct { uint8_t scale, subscale_n, subscale_d, vertical_align, multicell_y; };
uint64_t val;
} DecorationsKey;
static_assert(sizeof(DecorationsKey) == sizeof(uint64_t), "Fix the ordering of DecorationsKey");
static uint64_t hash_decorations_key(DecorationsKey k) { return vt_hash_integer(k.val); }
static bool cmpr_decorations_key(DecorationsKey a, DecorationsKey b) { return a.val == b.val; }
#define NAME decorations_index_map_t
#define KEY_TY DecorationsKey
#define VAL_TY sprite_index
#define HASH_FN hash_decorations_key
#define CMPR_FN cmpr_decorations_key
#include "kitty-verstable.h"
typedef struct {
FONTS_DATA_HEAD
id_type id;
@@ -101,6 +116,7 @@ typedef struct {
GPUSpriteTracker sprite_tracker;
fallback_font_map_t fallback_font_map;
scaled_font_map_t scaled_font_map;
decorations_index_map_t decorations_index_map;
} FontGroup;
static FontGroup* font_groups = NULL;
@@ -109,6 +125,25 @@ static size_t num_font_groups = 0;
static id_type font_group_id_counter = 0;
static void initialize_font_group(FontGroup *fg);
static void
python_send_to_gpu(FontGroup *fg, sprite_index idx, pixel *buf) {
unsigned int x, y, z;
sprite_index_to_pos(idx, fg->sprite_tracker.xnum, fg->sprite_tracker.ynum, &x, &y, &z);
const size_t sprite_size = fg->fcm.cell_width * fg->fcm.cell_height;
PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIy#y#", x, y, z, buf, sprite_size * sizeof(buf[0]), buf + sprite_size, fg->fcm.cell_width * sizeof(buf[0]));
if (ret == NULL) PyErr_Print();
else Py_DECREF(ret);
}
static void
current_send_sprite_to_gpu(FontGroup *fg, sprite_index idx, pixel *buf, sprite_index decorations_idx) {
pixel *metadata = buf + fg->fcm.cell_width * fg->fcm.cell_height;
metadata[0] = decorations_idx;
memset(metadata + 1, 0, (fg->fcm.cell_width - 1) * sizeof(metadata[0]));
if (python_send_to_gpu_impl) python_send_to_gpu(fg, idx, buf);
else send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, idx, buf);
}
static void
ensure_canvas_can_fit(FontGroup *fg, unsigned cells, unsigned scale) {
#define cs(cells, scale) (sizeof(fg->canvas.buf[0]) * 3u * cells * fg->fcm.cell_width * (fg->fcm.cell_height + 1) * scale * scale)
@@ -179,6 +214,7 @@ del_font_group(FontGroup *fg) {
fg->sprite_map = free_sprite_map(fg->sprite_map);
vt_cleanup(&fg->fallback_font_map);
vt_cleanup(&fg->scaled_font_map);
vt_cleanup(&fg->decorations_index_map);
for (size_t i = 0; i < fg->fonts_count; i++) del_font(fg->fonts + i);
free(fg->fonts); fg->fonts = NULL; fg->fonts_count = 0;
}
@@ -393,7 +429,7 @@ init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentati
f->ffs_hb_features = calloc(1 + features->count, sizeof(hb_feature_t));
if (!f->ffs_hb_features) { PyErr_NoMemory(); return false; }
f->num_ffs_hb_features = features->count;
memcpy(f->ffs_hb_features, features->features, sizeof(hb_feature_t) * features->count);
if (features->count) memcpy(f->ffs_hb_features, features->features, sizeof(hb_feature_t) * features->count);
memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[CALT_FEATURE], sizeof(hb_feature_t));
return true;
}
@@ -408,20 +444,6 @@ free_font_groups(void) {
free_glyph_cache_global_resources();
}
static void
python_send_to_gpu(FONTS_DATA_HANDLE fg_, unsigned int idx, pixel *buf) {
FontGroup *fg = (FontGroup*)fg_;
if (python_send_to_gpu_impl) {
if (!num_font_groups) fatal("Cannot call send to gpu with no font groups");
unsigned int x, y, z;
sprite_index_to_pos(idx, fg->sprite_tracker.xnum, fg->sprite_tracker.ynum, &x, &y, &z);
const size_t sprite_size = fg->fcm.cell_width * fg->fcm.cell_height;
PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIy#y#", x, y, z, buf, sprite_size * sizeof(pixel), buf + sprite_size, fg->fcm.cell_width * sizeof(pixel));
if (ret == NULL) PyErr_Print();
else Py_DECREF(ret);
}
}
static void
adjust_metric(unsigned int *metric, float adj, AdjustmentUnit unit, double dpi) {
if (adj == 0.f) return;
@@ -869,6 +891,59 @@ set_cell_sprite(GPUCell *cell, const SpritePosition *sp) {
if (sp->colored) cell->sprite_idx |= 0x80000000;
}
static void
render_scaled_decoration(FontCellMetrics unscaled_metrics, FontCellMetrics scaled_metrics, uint8_t *alpha_mask, pixel *output, Region src, Region dest) {
pixel col = 0xffffff00;
unsigned src_limit = MIN(scaled_metrics.cell_height, src.bottom), dest_limit = MIN(unscaled_metrics.cell_height, dest.bottom);
unsigned cell_width = MIN(scaled_metrics.cell_width, unscaled_metrics.cell_width);
for (unsigned srcy = src.top, desty=dest.top; srcy < src_limit && desty < dest_limit; srcy++, desty++) {
uint8_t *srcp = alpha_mask + cell_width * srcy;
pixel *destp = output + cell_width * desty;
for (unsigned x = 0; x < cell_width; x++) destp[x] = col | srcp[x];
}
}
static sprite_index
render_decorations(FontGroup *fg, Region src, Region dest, FontCellMetrics scaled_metrics) {
if ((src.bottom == src.top) || (dest.bottom == dest.top)) return 0; // no overlap
const FontCellMetrics unscaled_metrics = fg->fcm;
scaled_metrics.cell_width = unscaled_metrics.cell_width;
RAII_ALLOC(uint8_t, alpha_mask, malloc(scaled_metrics.cell_height * scaled_metrics.cell_width));
RAII_ALLOC(pixel, buf, malloc(unscaled_metrics.cell_width * (unscaled_metrics.cell_height + 1) * sizeof(pixel)));
if (!alpha_mask || !buf) fatal("Out of memory");
int error = 0;
sprite_index idx = current_sprite_index(&fg->sprite_tracker);
#define increment_sprite_index do_increment(fg, &error); if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("Failed"); }
#define do_one(call) \
memset(alpha_mask, 0, scaled_metrics.cell_width * scaled_metrics.cell_height * sizeof(alpha_mask[0])); \
call; \
memset(buf, 0, unscaled_metrics.cell_width * unscaled_metrics.cell_height * sizeof(buf[0])); \
render_scaled_decoration(unscaled_metrics, scaled_metrics, alpha_mask, buf, src, dest); \
current_send_sprite_to_gpu(fg, current_sprite_index(&fg->sprite_tracker), buf, 0); \
increment_sprite_index;
do_one(add_strikethrough(alpha_mask, scaled_metrics));
do_one(add_straight_underline(alpha_mask, scaled_metrics));
do_one(add_double_underline(alpha_mask, scaled_metrics));
do_one(add_curl_underline(alpha_mask, scaled_metrics));
do_one(add_dotted_underline(alpha_mask, scaled_metrics));
do_one(add_dashed_underline(alpha_mask, scaled_metrics));
return idx;
#undef increment_sprite_index
#undef do_one
}
static sprite_index
index_for_decorations(FontGroup *fg, RunFont rf, Region src, Region dest, FontCellMetrics scaled_metrics) {
DecorationsKey key = {.scale=rf.scale, .subscale_n = rf.subscale_n, .subscale_d = rf.subscale_d, .vertical_align = rf.vertical_align, .multicell_y = rf.multicell_y };
decorations_index_map_t_itr i = vt_get(&fg->decorations_index_map, key);
if (!vt_is_end(i)) return i.data->val;
sprite_index idx = render_decorations(fg, src, dest, scaled_metrics);
if (vt_is_end(vt_insert(&fg->decorations_index_map, key, idx))) fatal("Out of memory");
return idx;
}
static void
render_box_cell(FontGroup *fg, RunFont rf, CPUCell *cpu_cell, GPUCell *gpu_cell, const TextCache *tc) {
int error = 0;
@@ -900,8 +975,10 @@ render_box_cell(FontGroup *fg, RunFont rf, CPUCell *cpu_cell, GPUCell *gpu_cell,
if (!sp[ligature_index]->rendered) all_rendered = false;
}
if (all_rendered) return;
float scale = apply_scale_to_font_group(fg, &rf);
FontCellMetrics scaled_metrics = fg->fcm;
unsigned width = fg->fcm.cell_width, height = fg->fcm.cell_height;
float scale = scaled_cell_dimensions(rf, &width, &height);
if (scale != 1) apply_scale_to_font_group(fg, NULL);
RAII_PyObject(ret, NULL); uint8_t *alpha_mask = NULL;
ensure_canvas_can_fit(fg, num_glyphs + 1, rf.scale);
if (num_glyphs == 1) {
@@ -929,7 +1006,7 @@ render_box_cell(FontGroup *fg, RunFont rf, CPUCell *cpu_cell, GPUCell *gpu_cell,
/*printf("Rendered char sz: (%u, %u)\n", width, height); dump_sprite(fg->canvas.buf, width, height, false);*/
if (scale == 1.f && rf.scale == 1 && !rf.subscale_n) {
sp[0]->rendered = true;
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[0]->idx, fg->canvas.buf);
current_send_sprite_to_gpu(fg, sp[0]->idx, fg->canvas.buf, index_for_decorations(fg, rf, src, dest, scaled_metrics));
} else {
calculate_regions_for_line(rf, fg->fcm.cell_height, &src, &dest);
/*printf("width: %u height: %u unscaled_cell_width: %u unscaled_cell_height: %u src.top: %u src.bottom: %u rf.scale: %u\n", width, height, fg->fcm.cell_width, fg->fcm.cell_height, src.top, src.bottom, rf.scale);*/
@@ -937,7 +1014,7 @@ render_box_cell(FontGroup *fg, RunFont rf, CPUCell *cpu_cell, GPUCell *gpu_cell,
pixel *b = extract_cell_region(&fg->canvas, i, &src, &dest, width, fg->fcm);
/*printf("Sprite %u: pos: (%u, %u, %u) sz: (%u, %u)\n", i, sp[i]->x, sp[i]->y, sp[i]->z, fg->fcm.cell_width, fg->fcm.cell_height); dump_sprite(b, fg->fcm.cell_width, fg->fcm.cell_height, false);*/
sp[i]->rendered = true;
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->idx, b);
current_send_sprite_to_gpu(fg, sp[i]->idx, b, index_for_decorations(fg, rf, src, dest, scaled_metrics));
}
}
#undef sp
@@ -1025,7 +1102,8 @@ render_group(
bool is_repeat_sprite = is_infinite_ligature && i > 0 && sp[i]->idx == sp[i-1]->idx;
if (!is_repeat_sprite) {
pixel *b = num_cells == 1 ? fg->canvas.buf : extract_cell_from_canvas(fg, i, num_cells);
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->idx, b);
Region src = {.bottom=unscaled_metrics.cell_height, .right=unscaled_metrics.cell_width}, dest = src;
current_send_sprite_to_gpu(fg, sp[i]->idx, b, index_for_decorations(fg, rf, src, dest, scaled_metrics));
}
sp[i]->rendered = true; sp[i]->colored = was_colored;
}
@@ -1039,7 +1117,7 @@ render_group(
if (!sp[i]->rendered) {
pixel *b = extract_cell_region(&fg->canvas, i, &src, &dest, scaled_metrics.cell_width * num_scaled_cells, unscaled_metrics);
/*printf("cell %u src -> dest: (%u %u) -> (%u %u)\n", i, src.left, src.right, dest.left, dest.right);*/
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->idx, b);
current_send_sprite_to_gpu(fg, sp[i]->idx, b, index_for_decorations(fg, rf, src, dest, scaled_metrics));
/*dump_sprite(b, unscaled_metrics.cell_width, unscaled_metrics.cell_height, false);*/
sp[i]->rendered = true; sp[i]->colored = was_colored;
}
@@ -1769,7 +1847,8 @@ send_prerendered_sprites(FontGroup *fg) {
int error = 0;
// blank cell
ensure_canvas_can_fit(fg, 1, 1);
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, 0, fg->canvas.buf);
sprite_index dec_idx = 5;
current_send_sprite_to_gpu(fg, 0, fg->canvas.buf, dec_idx);
const unsigned cell_area = fg->fcm.cell_height * fg->fcm.cell_width;
RAII_ALLOC(uint8_t, alpha_mask, malloc(cell_area));
if (!alpha_mask) fatal("Out of memory");
@@ -1781,23 +1860,20 @@ send_prerendered_sprites(FontGroup *fg) {
ensure_canvas_can_fit(fg, 1, 1); /* clear canvas */ \
render_alpha_mask(alpha_mask, fg->canvas.buf, &r, &r, fg->fcm.cell_width, fg->fcm.cell_width, 0xffffff); \
increment_sprite_index; \
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, current_sprite_index(&fg->sprite_tracker), fg->canvas.buf);
current_send_sprite_to_gpu(fg, current_sprite_index(&fg->sprite_tracker), fg->canvas.buf, dec_idx);
// If you change the mapping of these cells you will need to change
// NUM_UNDERLINE_STYLES and BEAM_IDX in shader.c and STRIKE_SPRITE_INDEX in
// window.py and MISSING_GLYPH in font.c
do_one(add_straight_underline(alpha_mask, fg->fcm));
do_one(add_double_underline(alpha_mask, fg->fcm));
do_one(add_curl_underline(alpha_mask, fg->fcm));
do_one(add_dotted_underline(alpha_mask, fg->fcm));
do_one(add_dashed_underline(alpha_mask, fg->fcm));
do_one(add_strikethrough(alpha_mask, fg->fcm));
// BEAM_IDX in shader.c and STRIKE_SPRITE_INDEX in
// shaders.py and MISSING_GLYPH in font.c and dec_idx above
do_one(add_missing_glyph(alpha_mask, fg->fcm));
do_one(add_beam_cursor(alpha_mask, fg->fcm, fg->logical_dpi_x));
do_one(add_underline_cursor(alpha_mask, fg->fcm, fg->logical_dpi_y));
do_one(add_hollow_cursor(alpha_mask, fg->fcm, fg->logical_dpi_x, fg->logical_dpi_y));
increment_sprite_index;
RunFont rf = {.scale=1};
Region rg = {.bottom = fg->fcm.cell_height, .right = fg->fcm.cell_width};
sprite_index actual_dec_idx = index_for_decorations(fg, rf, rg, rg, fg->fcm);
if (actual_dec_idx != dec_idx) fatal("dec_idx: %u != actual_dec_idx: %u", dec_idx, actual_dec_idx);
#undef increment_sprite_index
#undef do_one
@@ -1832,6 +1908,7 @@ initialize_font_group(FontGroup *fg) {
if (!init_hash_tables(fg->fonts)) fatal("Out of memory");
vt_init(&fg->fallback_font_map);
vt_init(&fg->scaled_font_map);
vt_init(&fg->decorations_index_map);
#define I(attr) if (descriptor_indices.attr) fg->attr##_font_idx = initialize_font(fg, descriptor_indices.attr, #attr); else fg->attr##_font_idx = -1;
fg->medium_font_idx = initialize_font(fg, 0, "medium");
I(bold); I(italic); I(bi);

View File

@@ -14,6 +14,7 @@
// TODO: Handle selection with multicell
// TODO: URL detection with multicell
// TODO: Cursor rendering over multicell
// TODO: Wrapping of multicell draw commands
// TODO: Handle rewrap and restitch of multiline chars
// TODO: Handle rewrap when a character is too wide/tall to fit on resized screen
// TODO: Implement baseline align for box drawing
@@ -35,7 +36,6 @@ static_assert(sizeof(CellAttrs) == sizeof(uint32_t), "Fix the ordering of CellAt
#define WIDTH_MASK (3u)
#define DECORATION_MASK (7u)
#define NUM_UNDERLINE_STYLES (5u)
#define SGR_MASK (~(((CellAttrs){.mark=MARK_MASK}).val))
// Text presentation selector
#define VS15 0xfe0e

View File

@@ -143,7 +143,7 @@ ensure_sprite_map(FONTS_DATA_HANDLE fg) {
}
void
send_sprite_to_gpu(FONTS_DATA_HANDLE fg, unsigned int idx, pixel *buf) {
send_sprite_to_gpu(FONTS_DATA_HANDLE fg, sprite_index idx, pixel *buf) {
SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
unsigned int xnum, ynum, znum, x, y, z;
sprite_tracker_current_layout(fg, &xnum, &ynum, &znum);
@@ -330,7 +330,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c
}
rd->use_cell_for_selection_bg = IS_SPECIAL_COLOR(highlight_bg) ? 1. : 0.;
// Cursor position
enum { BLOCK_IDX = 0, BEAM_IDX = NUM_UNDERLINE_STYLES + 3, UNDERLINE_IDX = NUM_UNDERLINE_STYLES + 4, UNFOCUSED_IDX = NUM_UNDERLINE_STYLES + 5 };
enum { BLOCK_IDX = 0, BEAM_IDX = 2, UNDERLINE_IDX = 3, UNFOCUSED_IDX = 4 };
Line *line_for_cursor = NULL;
if (cursor->opacity > 0) {
rd->cursor_x = cursor->x, rd->cursor_y = cursor->y;

View File

@@ -23,7 +23,6 @@ from .fast_data_types import (
GRAPHICS_PROGRAM,
MARK,
MARK_MASK,
NUM_UNDERLINE_STYLES,
REVERSE,
STRIKETHROUGH,
TINT_PROGRAM,
@@ -162,7 +161,7 @@ class LoadShaderPrograms:
MARK_SHIFT=MARK,
MARK_MASK=MARK_MASK,
DECORATION_MASK=DECORATION_MASK,
STRIKE_SPRITE_INDEX=NUM_UNDERLINE_STYLES + 1,
STRIKE_SPRITE_INDEX=0,
)
def resolve_cell_defines(which: str, src: str) -> str:

View File

@@ -387,7 +387,7 @@ void update_surface_size(int, int, uint32_t);
void free_texture(uint32_t*);
void free_framebuffer(uint32_t*);
void send_image_to_gpu(uint32_t*, const void*, int32_t, int32_t, bool, bool, bool, RepeatStrategy);
void send_sprite_to_gpu(FONTS_DATA_HANDLE fg, unsigned int, pixel*);
void send_sprite_to_gpu(FONTS_DATA_HANDLE fg, sprite_index, pixel*);
void blank_canvas(float, color_type);
void blank_os_window(OSWindow *);
void set_os_window_chrome(OSWindow *w);

View File

@@ -270,7 +270,7 @@ class FontBaseTest(BaseTest):
tc = setup_for_testing(size=self.font_size, dpi=self.dpi, main_face_path=path)
self.sprites, self.sprite_metadata, self.cell_width, self.cell_height = tc.__enter__()
self.addCleanup(tc.__exit__)
self.assertEqual([k[0] for k in self.sprites], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
self.assertEqual([k[0] for k in self.sprites], list(range(11)))
def tearDown(self):
del self.sprites, self.cell_width, self.cell_height
@@ -296,8 +296,8 @@ class Rendering(FontBaseTest):
self.ae(test_sprite_position_for(0, 2), (1, 0, 2))
def test_box_drawing(self):
prerendered = len(self.sprites)
s = self.create_screen(cols=len(box_chars) + 1, lines=1, scrollback=0)
prerendered = len(self.sprites)
s.draw(''.join(box_chars))
line = s.line(0)
test_render_line(line)