Give sprites a metadata row accessible in the shaders

Will allow sprites to point to where their decorations should be read
from, for instance. Needed for scaled text and also if we want to
implement decoration avoidance.
This commit is contained in:
Kovid Goyal
2024-12-12 09:59:27 +05:30
parent 6a169eedd5
commit 5d195bf50b
7 changed files with 42 additions and 25 deletions

View File

@@ -8,7 +8,7 @@ layout(std140) uniform CellRenderData {
uint default_fg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted;
uint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx;
uint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx, cell_height;
float cursor_x, cursor_y, cursor_w, cursor_opacity;
// must have unique entries with 0 being default_bg and unset being UINT32_MAX
@@ -96,15 +96,23 @@ vec3 to_color(uint c, uint defval) {
return color_to_vec(resolve_color(c, defval));
}
vec3 to_sprite_pos(uvec2 pos, uint idx) {
uvec3 to_sprite_coords(uint idx) {
uint sprites_per_page = sprites_xnum * sprites_ynum;
uint z = idx / sprites_per_page;
uint num_on_last_page = idx % sprites_per_page;
uint y = num_on_last_page / sprites_xnum;
uint x = num_on_last_page % sprites_xnum;
vec2 s_xpos = vec2(x, float(x) + 1.0f) * (1.0f / float(sprites_xnum));
vec2 s_ypos = vec2(y, float(y) + 1.0f) * (1.0f / float(sprites_ynum));
return vec3(s_xpos[pos.x], s_ypos[pos.y], z);
return uvec3(x, y, z);
}
vec3 to_sprite_pos(uvec2 pos, uint idx) {
uvec3 c = to_sprite_coords(idx);
vec2 s_xpos = vec2(c.x, float(c.x) + 1.0f) * (1.0f / float(sprites_xnum));
vec2 s_ypos = vec2(c.y, float(c.y) + 1.0f) * (1.0f / float(sprites_ynum));
uint texture_height_px = (cell_height + 1u) * sprites_ynum;
float row_height = 1.0f / float(texture_height_px);
s_ypos[1] -= row_height; // skip the metadata row
return vec3(s_xpos[pos.x], s_ypos[pos.y], c.z);
}
vec3 choose_color(float q, vec3 a, vec3 b) {

View File

@@ -1126,7 +1126,7 @@ def sprite_map_set_limits(w: int, h: int) -> None:
def set_send_sprite_to_gpu(
func: Optional[Callable[[int, int, int, bytes], None]]
func: Optional[Callable[[int, int, int, bytes, bytes], None]]
) -> None:
pass

View File

@@ -111,7 +111,7 @@ static void initialize_font_group(FontGroup *fg);
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 * scale * scale)
#define cs(cells, scale) (sizeof(fg->canvas.buf[0]) * 3u * cells * fg->fcm.cell_width * (fg->fcm.cell_height + 1) * scale * scale)
size_t size_in_bytes = cs(cells, scale);
if (size_in_bytes > fg->canvas.size_in_bytes) {
free(fg->canvas.buf);
@@ -294,7 +294,7 @@ sprite_tracker_current_layout(FONTS_DATA_HANDLE data, unsigned int *x, unsigned
static void
sprite_tracker_set_layout(GPUSpriteTracker *sprite_tracker, unsigned int cell_width, unsigned int cell_height) {
sprite_tracker->xnum = MIN(MAX(1u, max_texture_size / cell_width), (size_t)UINT16_MAX);
sprite_tracker->max_y = MIN(MAX(1u, max_texture_size / cell_height), (size_t)UINT16_MAX);
sprite_tracker->max_y = MIN(MAX(1u, max_texture_size / (cell_height + 1)), (size_t)UINT16_MAX);
sprite_tracker->ynum = 1;
sprite_tracker->x = 0; sprite_tracker->y = 0; sprite_tracker->z = 0;
}
@@ -415,7 +415,8 @@ python_send_to_gpu(FONTS_DATA_HANDLE fg_, unsigned int idx, pixel *buf) {
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);
PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIN", x, y, z, PyBytes_FromStringAndSize((const char*)buf, sizeof(pixel) * fg->fcm.cell_width * fg->fcm.cell_height));
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);
}
@@ -795,9 +796,14 @@ apply_scale_to_font_group(FontGroup *fg, RunFont *rf) {
return scale;
}
static pixel*
pointer_to_space_for_last_sprite(Canvas *canvas, FontCellMetrics fcm) {
return canvas->buf + (canvas->size_in_bytes / sizeof(canvas->buf[0]) - fcm.cell_width * (fcm.cell_height + 1));
}
static pixel*
extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells) {
pixel *ans = fg->canvas.buf + (fg->canvas.size_in_bytes / sizeof(fg->canvas.buf[0]) - fg->fcm.cell_width * fg->fcm.cell_height);
pixel *ans = pointer_to_space_for_last_sprite(&fg->canvas, fg->fcm);
pixel *dest = ans, *src = fg->canvas.buf + (i * fg->fcm.cell_width);
unsigned int stride = fg->fcm.cell_width * num_cells;
for (unsigned int r = 0; r < fg->fcm.cell_height; r++, dest += fg->fcm.cell_width, src += stride) memcpy(dest, src, fg->fcm.cell_width * sizeof(fg->canvas.buf[0]));
@@ -834,7 +840,7 @@ static pixel*
extract_cell_region(Canvas *canvas, unsigned i, Region *src, const Region *dest, unsigned src_width, FontCellMetrics unscaled_metrics) {
src->left = i * unscaled_metrics.cell_width; src->right = MIN(src_width, src->left + unscaled_metrics.cell_width);
unsigned unscaled_cell_area = unscaled_metrics.cell_width * unscaled_metrics.cell_height;
pixel *ans = canvas->buf + (canvas->size_in_bytes / sizeof(canvas->buf[0]) - unscaled_cell_area);
pixel *ans = pointer_to_space_for_last_sprite(canvas, unscaled_metrics);
memset(ans, 0, sizeof(ans[0]) * unscaled_cell_area);
unsigned width = MIN(src->right - src->left, unscaled_metrics.cell_width);
for (unsigned srcy = src->top, desty = dest->top; srcy < src->bottom && desty < dest->bottom; srcy++, desty++) {

View File

@@ -1439,7 +1439,7 @@ def test_char(ch: str, sz: int = 48) -> None:
from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu
from .render import display_bitmap, setup_for_testing
with setup_for_testing('monospace', sz) as (_, width, height):
with setup_for_testing('monospace', sz) as (_, _, width, height):
buf = bytearray(width * height)
try:
render_box_char(ch, buf, width, height)
@@ -1460,7 +1460,7 @@ def test_drawing(sz: int = 48, family: str = 'monospace', start: int = 0x2500, n
from .render import display_bitmap, setup_for_testing
with setup_for_testing(family, sz) as (_, width, height):
with setup_for_testing(family, sz) as (_, _, width, height):
space = bytearray(width * height)
def join_cells(cells: Iterable[bytes]) -> bytes:

View File

@@ -236,14 +236,16 @@ class setup_for_testing:
self.family, self.size, self.dpi = family, size, dpi
self.main_face_path = main_face_path
def __enter__(self) -> tuple[dict[tuple[int, int, int], bytes], int, int]:
def __enter__(self) -> tuple[dict[tuple[int, int, int], bytes], dict[tuple[int, int, int], bytes], int, int]:
global descriptor_overrides
opts = defaults._replace(font_family=parse_font_spec(self.family), font_size=self.size)
set_options(opts)
sprites = {}
sprite_metadata = {}
def send_to_gpu(x: int, y: int, z: int, data: bytes) -> None:
def send_to_gpu(x: int, y: int, z: int, data: bytes, metadata: bytes) -> None:
sprites[(x, y, z)] = data
sprite_metadata[(x, y, z)] = metadata
sprite_map_set_limits(self.xnum, self.ynum)
set_send_sprite_to_gpu(send_to_gpu)
@@ -254,7 +256,7 @@ class setup_for_testing:
try:
set_font_family(opts)
cell_width, cell_height = create_test_font_group(self.size, self.dpi, self.dpi)
return sprites, cell_width, cell_height
return sprites, sprite_metadata, cell_width, cell_height
except Exception:
set_send_sprite_to_gpu(None)
raise
@@ -266,7 +268,7 @@ class setup_for_testing:
def render_string(text: str, family: str = 'monospace', size: float = 11.0, dpi: float = 96.0) -> tuple[int, int, list[bytes]]:
with setup_for_testing(family, size, dpi) as (sprites, cell_width, cell_height):
with setup_for_testing(family, size, dpi) as (sprites, sprite_metadata, cell_width, cell_height):
s = Screen(None, 1, len(text)*2)
line = s.line(0)
s.draw(text)
@@ -286,7 +288,7 @@ def render_string(text: str, family: str = 'monospace', size: float = 11.0, dpi:
def shape_string(
text: str = "abcd", family: str = 'monospace', size: float = 11.0, dpi: float = 96.0, path: Optional[str] = None
) -> list[tuple[int, int, int, tuple[int, ...]]]:
with setup_for_testing(family, size, dpi) as (sprites, cell_width, cell_height):
with setup_for_testing(family, size, dpi) as (sprites, sprite_metadata, cell_width, cell_height):
s = Screen(None, 1, len(text)*2)
line = s.line(0)
s.draw(text)

View File

@@ -118,12 +118,12 @@ realloc_sprite_texture(FONTS_DATA_HANDLE fg) {
sprite_tracker_current_layout(fg, &xnum, &ynum, &z);
znum = z + 1;
SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
width = xnum * fg->fcm.cell_width; height = ynum * fg->fcm.cell_height;
width = xnum * fg->fcm.cell_width; height = ynum * (fg->fcm.cell_height + 1);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_SRGB8_ALPHA8, width, height, znum);
if (sprite_map->texture_id) {
// need to re-alloc
src_ynum = MAX(1, sprite_map->last_ynum);
copy_image_sub_data(sprite_map->texture_id, tex, width, src_ynum * fg->fcm.cell_height, sprite_map->last_num_of_layers);
copy_image_sub_data(sprite_map->texture_id, tex, width, src_ynum * (fg->fcm.cell_height + 1), sprite_map->last_num_of_layers);
glDeleteTextures(1, &sprite_map->texture_id);
}
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
@@ -154,8 +154,8 @@ send_sprite_to_gpu(FONTS_DATA_HANDLE fg, unsigned int idx, pixel *buf) {
glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
sprite_index_to_pos(idx, xnum, ynum, &x, &y, &z);
x *= fg->fcm.cell_width; y *= fg->fcm.cell_height;
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, fg->fcm.cell_width, fg->fcm.cell_height, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buf);
x *= fg->fcm.cell_width; y *= fg->fcm.cell_height + 1;
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, fg->fcm.cell_width, fg->fcm.cell_height + 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buf);
}
void
@@ -299,7 +299,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c
GLuint default_fg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted;
GLuint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx;
GLuint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx, cell_height;
GLfloat cursor_x, cursor_y, cursor_w, cursor_opacity;
GLuint bg_colors0, bg_colors1, bg_colors2, bg_colors3, bg_colors4, bg_colors5, bg_colors6, bg_colors7;
GLfloat bg_opacities0, bg_opacities1, bg_opacities2, bg_opacities3, bg_opacities4, bg_opacities5, bg_opacities6, bg_opacities7;
@@ -389,6 +389,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c
unsigned int x, y, z;
sprite_tracker_current_layout(os_window->fonts_data, &x, &y, &z);
rd->sprites_xnum = x; rd->sprites_ynum = y;
rd->cell_height = os_window->fonts_data->fcm.cell_height;
rd->inverted = screen_invert_colors(screen) ? 1 : 0;
#undef COLOR

View File

@@ -268,7 +268,7 @@ class FontBaseTest(BaseTest):
self.addCleanup(self.rmtree_ignoring_errors, self.tdir)
path = self.path_for_font(self.font_name) if self.font_name else ''
tc = setup_for_testing(size=self.font_size, dpi=self.dpi, main_face_path=path)
self.sprites, self.cell_width, self.cell_height = tc.__enter__()
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])
@@ -283,7 +283,7 @@ class Rendering(FontBaseTest):
def test_sprite_map(self):
sprite_map_set_limits(10, 2)
sprite_map_set_layout(5, 5)
sprite_map_set_layout(5, 4) # 4 because of metadata row
self.ae(test_sprite_position_for(0), (0, 0, 0))
self.ae(test_sprite_position_for(1), (1, 0, 0))
self.ae(test_sprite_position_for(2), (0, 1, 0))