Store sprites in an RGBA texture

Needed to support color emoji
This commit is contained in:
Kovid Goyal 2017-12-06 20:47:20 +05:30
parent b8093d6b83
commit 9cda51b5a8
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
8 changed files with 108 additions and 63 deletions

View File

@ -31,6 +31,7 @@ typedef unsigned long long id_type;
typedef uint32_t char_type;
typedef uint32_t color_type;
typedef uint32_t combining_type;
typedef uint32_t pixel;
typedef unsigned int index_type;
typedef uint16_t sprite_index;
typedef uint16_t attrs_type;
@ -127,6 +128,10 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape;
#define END_ALLOW_UNUSED_RESULT _Pragma("GCC diagnostic pop")
#endif
typedef struct {
uint32_t left, top, right, bottom;
} Region;
typedef struct {
char_type ch;
color_type fg, bg, decoration_fg;

View File

@ -12,7 +12,7 @@
#define MAX_NUM_EXTRA_GLYPHS 8
typedef uint16_t glyph_index;
typedef void (*send_sprite_to_gpu_func)(unsigned int, unsigned int, unsigned int, uint8_t*);
typedef void (*send_sprite_to_gpu_func)(unsigned int, unsigned int, unsigned int, pixel*);
send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL;
static PyObject *python_send_to_gpu_impl = NULL;
extern PyTypeObject Line_Type;
@ -261,15 +261,15 @@ del_font(Font *f) {
}
static unsigned int cell_width = 0, cell_height = 0, baseline = 0, underline_position = 0, underline_thickness = 0;
static uint8_t *canvas = NULL;
static pixel *canvas = NULL;
#define CELLS_IN_CANVAS ((MAX_NUM_EXTRA_GLYPHS + 1) * 3)
static inline void
clear_canvas(void) { memset(canvas, 0, CELLS_IN_CANVAS * cell_width * cell_height); }
clear_canvas(void) { memset(canvas, 0, CELLS_IN_CANVAS * cell_width * cell_height * sizeof(pixel)); }
static void
python_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, uint8_t* buf) {
python_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, pixel* buf) {
if (python_send_to_gpu_impl != NULL && python_send_to_gpu_impl != Py_None) {
PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIN", x, y, z, PyBytes_FromStringAndSize((const char*)buf, cell_width * cell_height));
PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIN", x, y, z, PyBytes_FromStringAndSize((const char*)buf, sizeof(pixel) * cell_width * cell_height));
if (ret == NULL) PyErr_Print();
else Py_DECREF(ret);
}
@ -296,7 +296,7 @@ update_cell_metrics() {
}
sprite_tracker_set_layout(cell_width, cell_height);
global_state.cell_width = cell_width; global_state.cell_height = cell_height;
free(canvas); canvas = malloc(CELLS_IN_CANVAS * cell_width * cell_height);
free(canvas); canvas = malloc(CELLS_IN_CANVAS * cell_width * cell_height * sizeof(pixel));
if (canvas == NULL) return PyErr_NoMemory();
for (ssize_t i = 0, j = fonts.first_symbol_font_idx; i < (ssize_t)fonts.symbol_map_fonts_count; i++, j++) {
CALL(j, cell_height, true);
@ -429,6 +429,21 @@ END_ALLOW_CASE_RANGE
static PyObject* box_drawing_function = NULL;
void
render_alpha_mask(uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride) {
for (size_t sr = src_rect->top, dr = dest_rect->top; sr < src_rect->bottom && dr < dest_rect->bottom; sr++, dr++) {
pixel *d = dest + dest_stride * dr;
uint8_t *s = alpha_mask + src_stride * sr;
for(size_t sc = src_rect->left, dc = dest_rect->left; sc < src_rect->right && dc < dest_rect->right; sc++, dc++) {
pixel val = d[dc];
uint8_t alpha = s[sc];
#define C(shift) ((MIN(0xff, alpha + ((val >> shift) & 0xff))) << shift)
d[dc] = C(24) | C(16) | C(8) | MIN(0xff, alpha + (val & 0xff));
#undef C
}
}
}
static void
render_box_cell(Cell *cell) {
int error = 0;
@ -445,7 +460,11 @@ render_box_cell(Cell *cell) {
sp->rendered = true;
PyObject *ret = PyObject_CallFunction(box_drawing_function, "I", cell->ch);
if (ret == NULL) { PyErr_Print(); return; }
current_send_sprite_to_gpu(sp->x, sp->y, sp->z, PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0)));
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0));
clear_canvas();
Region r = { .right = cell_width, .bottom = cell_height };
render_alpha_mask(alpha_mask, canvas, &r, &r, cell_width, cell_width);
current_send_sprite_to_gpu(sp->x, sp->y, sp->z, canvas);
Py_DECREF(ret);
}
@ -476,11 +495,11 @@ set_cell_sprite(Cell *cell, SpritePosition *sp) {
cell->sprite_x = sp->x; cell->sprite_y = sp->y; cell->sprite_z = sp->z;
}
static inline uint8_t*
static inline pixel*
extract_cell_from_canvas(unsigned int i, unsigned int num_cells) {
uint8_t *ans = canvas + (cell_width * cell_height * (CELLS_IN_CANVAS - 1)), *dest = ans, *src = canvas + (i * cell_width);
pixel *ans = canvas + (cell_width * cell_height * (CELLS_IN_CANVAS - 1)), *dest = ans, *src = canvas + (i * cell_width);
unsigned int stride = cell_width * num_cells;
for (unsigned int r = 0; r < cell_height; r++, dest += cell_width, src += stride) memcpy(dest, src, cell_width);
for (unsigned int r = 0; r < cell_height; r++, dest += cell_width, src += stride) memcpy(dest, src, cell_width * sizeof(pixel));
return ans;
}
@ -504,7 +523,7 @@ render_group(unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_gl
for (unsigned int i = 0; i < num_cells; i++) {
sprite_position[i]->rendered = true;
set_cell_sprite(cells + i, sprite_position[i]);
uint8_t *buf = num_cells == 1 ? canvas : extract_cell_from_canvas(i, num_cells);
pixel *buf = num_cells == 1 ? canvas : extract_cell_from_canvas(i, num_cells);
current_send_sprite_to_gpu(sprite_position[i]->x, sprite_position[i]->y, sprite_position[i]->z, buf);
}
@ -640,8 +659,8 @@ shape_run(Cell *first_cell, index_type num_cells, Font *font) {
unsigned int num_glyphs = shape(first_cell, num_cells, font->hb_font, &info, &positions);
#if 0
// You can also generate this easily using hb-shape --show-flags --show-extents --cluster-level=1 --shapers=ot /path/to/font/file text
hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, num_glyphs, (char*)canvas, CELLS_IN_CANVAS * cell_width * cell_height, NULL, font->hb_font, HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS | HB_BUFFER_SERIALIZE_FLAG_GLYPH_FLAGS);
printf("\n%s\n", canvas);
hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, num_glyphs, (char*)canvas, sizeof(pixel) * CELLS_IN_CANVAS * cell_width * cell_height, NULL, font->hb_font, HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS | HB_BUFFER_SERIALIZE_FLAG_GLYPH_FLAGS);
printf("\n%s\n", (char*)canvas);
clear_canvas();
#endif
unsigned int run_pos = 0, cell_pos = 0, num_group_glyphs, num_group_cells;
@ -815,7 +834,11 @@ send_prerendered_sprites(PyObject UNUSED *s, PyObject *args) {
x = sprite_tracker.x; y = sprite_tracker.y; z = sprite_tracker.z;
do_increment(&error);
if (error != 0) { sprite_map_set_error(error); return NULL; }
current_send_sprite_to_gpu(x, y, z, PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i)));
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i));
clear_canvas();
Region r = { .right = cell_width, .bottom = cell_height };
render_alpha_mask(alpha_mask, canvas, &r, &r, cell_width, cell_width);
current_send_sprite_to_gpu(x, y, z, canvas);
}
return Py_BuildValue("H", x);
}
@ -839,18 +862,41 @@ test_render_line(PyObject UNUSED *self, PyObject *args) {
static PyObject*
concat_cells(PyObject UNUSED *self, PyObject *args) {
// Concatenate cells returning RGBA data
unsigned int cell_width, cell_height;
int is_32_bit;
PyObject *cells;
if (!PyArg_ParseTuple(args, "IIO!", &cell_width, &cell_height, &PyTuple_Type, &cells)) return NULL;
if (!PyArg_ParseTuple(args, "IIpO!", &cell_width, &cell_height, &is_32_bit, &PyTuple_Type, &cells)) return NULL;
size_t num_cells = PyTuple_GET_SIZE(cells), r, c, i;
PyObject *ans = PyBytes_FromStringAndSize(NULL, 3 * cell_width * cell_height * num_cells);
PyObject *ans = PyBytes_FromStringAndSize(NULL, 4 * cell_width * cell_height * num_cells);
if (ans == NULL) return PyErr_NoMemory();
uint8_t *dest = (uint8_t*)PyBytes_AS_STRING(ans), *src;
pixel *dest = (pixel*)PyBytes_AS_STRING(ans);
for (r = 0; r < cell_height; r++) {
for (c = 0; c < num_cells; c++) {
src = ((uint8_t*)PyBytes_AS_STRING(PyTuple_GET_ITEM(cells, c))) + cell_width * r;
for (i = 0; i < cell_width; i++, dest += 3) {
dest[0] = src[i]; dest[1] = src[i]; dest[2] = src[i];
void *s = ((uint8_t*)PyBytes_AS_STRING(PyTuple_GET_ITEM(cells, c)));
if (is_32_bit) {
pixel *src = (pixel*)s + cell_width * r;
for (i = 0; i < cell_width; i++, dest++) {
uint8_t *rgba = (uint8_t*)dest;
rgba[0] = (src[i] >> 24) & 0xff;
rgba[1] = (src[i] >> 16) & 0xff;
rgba[2] = (src[i] >> 8) & 0xff;
rgba[3] = src[i] & 0xff;
// pre-multiplied, so de-multiply
if (rgba[3]) {
float f = 255.f / (float)rgba[3];
rgba[0] = MAX(255.f, ((float)rgba[0] * f));
rgba[1] = MAX(255.f, ((float)rgba[1] * f));
rgba[2] = MAX(255.f, ((float)rgba[2] * f));
}
}
} else {
uint8_t *src = (uint8_t*)s + cell_width * r;
for (i = 0; i < cell_width; i++, dest++) {
uint8_t *rgba = (uint8_t*)dest;
if (src[i]) { memset(rgba, 0xff, 3); rgba[3] = src[i]; }
else *dest = 0;
}
}
}

View File

@ -19,7 +19,8 @@ hb_font_t* harfbuzz_font_for_face(PyObject*);
bool set_size_for_face(PyObject*, unsigned int, bool);
void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*);
void sprite_tracker_current_layout(unsigned int *x, unsigned int *y, unsigned int *z);
bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, uint8_t *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline);
bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline);
void render_alpha_mask(uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride);
void render_line(Line *line);
void sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len);
void sprite_tracker_set_layout(unsigned int cell_width, unsigned int cell_height);

View File

@ -435,7 +435,7 @@ def render_missing_glyph(buf, width, height):
draw_vline(buf, width, vgap, height - vgap + 1, width - hgap, 0)
def test_drawing(sz=32, family='monospace'):
def test_drawing(sz=48, family='monospace'):
from .render import display_bitmap, setup_for_testing
from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu
@ -444,7 +444,7 @@ def test_drawing(sz=32, family='monospace'):
def join_cells(cells):
cells = tuple(bytes(x) for x in cells)
return concat_cells(width, height, cells)
return concat_cells(width, height, False, cells)
def render_chr(ch):
if ch in box_chars:
@ -469,7 +469,7 @@ def test_drawing(sz=32, family='monospace'):
rgb_data = b''.join(rows)
width *= 32
height *= len(rows)
assert len(rgb_data) == width * height * 3, '{} != {}'.format(len(rgb_data), width * height * 3)
assert len(rgb_data) == width * height * 4, '{} != {}'.format(len(rgb_data), width * height * 4)
display_bitmap(rgb_data, width, height)
finally:
set_send_sprite_to_gpu(None)

View File

@ -220,16 +220,17 @@ def display_bitmap(rgb_data, width, height):
if not hasattr(display_bitmap, 'detected') and not detect_support():
raise SystemExit('Your terminal does not support the graphics protocol')
display_bitmap.detected = True
with NamedTemporaryFile(delete=False) as f:
with NamedTemporaryFile(suffix='.rgba', delete=False) as f:
f.write(rgb_data)
show(f.name, width, height, 24)
assert len(rgb_data) == 4 * width * height
show(f.name, width, height, 32)
def test_render_string(text='Hello, world!', family='monospace', size=144.0, dpi=96.0):
from kitty.fast_data_types import concat_cells, current_fonts
cell_width, cell_height, cells = render_string(text, family, size, dpi)
rgb_data = concat_cells(cell_width, cell_height, tuple(cells))
rgb_data = concat_cells(cell_width, cell_height, True, tuple(cells))
cf = current_fonts()
fonts = [cf['medium'].display_name()]
fonts.extend(f.display_name() for f in cf['fallback'])

View File

@ -363,57 +363,50 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_
}
static inline void
place_bitmap_in_canvas(unsigned char *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, FT_Glyph_Metrics *metrics, size_t baseline) {
place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, FT_Glyph_Metrics *metrics, size_t baseline) {
// We want the glyph to be positioned inside the cell based on the bearingX
// and bearingY values, making sure that it does not overflow the cell.
Region src = { .left = bm->start_x, .bottom = bm->rows, .right = bm->width }, dest = { .bottom = cell_height, .right = cell_width };
// Calculate column bounds
float bearing_x = (float)metrics->horiBearingX / 64.f;
ssize_t xoff = (ssize_t)(x_offset + bearing_x);
size_t src_start_column = bm->start_x, dest_start_column = 0, extra;
if (xoff < 0) src_start_column += -xoff;
else dest_start_column = xoff;
int32_t xoff = (ssize_t)(x_offset + bearing_x);
uint32_t extra;
if (xoff < 0) src.left += -xoff;
else dest.left = xoff;
// Move the dest start column back if the width overflows because of it
if (dest_start_column > 0 && dest_start_column + bm->width > cell_width) {
extra = dest_start_column + bm->width - cell_width;
dest_start_column = extra > dest_start_column ? 0 : dest_start_column - extra;
if (dest.left > 0 && dest.left + bm->width > cell_width) {
extra = dest.left + bm->width - cell_width;
dest.left = extra > dest.left ? 0 : dest.left - extra;
}
// Calculate row bounds
float bearing_y = (float)metrics->horiBearingY / 64.f;
ssize_t yoff = (ssize_t)(y_offset + bearing_y);
size_t src_start_row, dest_start_row;
int32_t yoff = (ssize_t)(y_offset + bearing_y);
if (yoff > 0 && (size_t)yoff > baseline) {
src_start_row = 0;
dest_start_row = 0;
dest.top = 0;
} else {
src_start_row = 0;
dest_start_row = baseline - yoff;
dest.top = baseline - yoff;
}
/* printf("x_offset: %f bearing_x: %f y_offset: %f bearing_y: %f src_start_row: %zu src_start_column: %zu dest_start_row: %zu dest_start_column: %zu bm_width: %lu bitmap_rows: %lu\n", x_offset, bearing_x, y_offset, bearing_y, src_start_row, src_start_column, dest_start_row, dest_start_column, bm->width, bm->rows); */
for (size_t sr = src_start_row, dr = dest_start_row; sr < bm->rows && dr < cell_height; sr++, dr++) {
for(size_t sc = src_start_column, dc = dest_start_column; sc < bm->width && dc < cell_width; sc++, dc++) {
uint16_t val = cell[dr * cell_width + dc];
val = MIN(255, ((uint16_t)val + (uint16_t)bm->buf[sr * bm->stride + sc]));
cell[dr * cell_width + dc] = val;
}
}
render_alpha_mask(bm->buf, cell, &src, &dest, bm->stride, cell_width);
}
static inline void
right_shift_canvas(uint8_t *canvas, size_t width, size_t height, size_t amt) {
uint8_t *src;
right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) {
pixel *src;
size_t r;
for (r = 0, src = canvas; r < height; r++, src += width) {
memmove(src + amt, src, width - amt);
memset(src, 0, amt);
memmove(src + amt, src, sizeof(pixel) * (width - amt));
memset(src, 0, sizeof(pixel) * amt);
}
}
bool
render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, uint8_t *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline) {
render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline) {
Face *self = (Face*)f;
float x = 0.f, y = 0.f, x_offset = 0.f;
ProcessedBitmap bm;

View File

@ -32,15 +32,14 @@ copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int
copy_image_warned = true;
fprintf(stderr, "WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation.\n");
}
uint8_t *src = malloc(5 * width * height * num_levels);
size_t sz = width * height * num_levels;
pixel *src = malloc(sz * sizeof(pixel));
if (src == NULL) { fatal("Out of memory."); }
uint8_t *dest = src + (4 * width * height * num_levels);
glBindTexture(GL_TEXTURE_2D_ARRAY, src_texture_id);
glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, src);
glBindTexture(GL_TEXTURE_2D_ARRAY, dest_texture_id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
for(size_t i = 0; i < width * height * num_levels; i++) dest[i] = src[4*i];
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels, GL_RED, GL_UNSIGNED_BYTE, dest);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels, GL_RGBA, GL_UNSIGNED_BYTE, src);
free(src);
} else {
glCopyImageSubData(src_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, dest_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels);
@ -63,7 +62,7 @@ realloc_sprite_texture() {
sprite_tracker_current_layout(&xnum, &ynum, &z);
znum = z + 1;
width = xnum * global_state.cell_width; height = ynum * global_state.cell_height;
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_R8, width, height, znum);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, width, height, znum);
if (sprite_map.texture_id) {
// need to re-alloc
src_ynum = MAX(1, sprite_map.last_ynum);
@ -86,14 +85,14 @@ ensure_sprite_map() {
}
void
send_sprite_to_gpu(unsigned int x, unsigned int y, unsigned int z, uint8_t *buf) {
send_sprite_to_gpu(unsigned int x, unsigned int y, unsigned int z, pixel *buf) {
unsigned int xnum, ynum, znum;
sprite_tracker_current_layout(&xnum, &ynum, &znum);
if ((int)znum >= sprite_map.last_num_of_layers || (znum == 0 && (int)ynum > sprite_map.last_ynum)) realloc_sprite_texture();
glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map.texture_id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
x *= global_state.cell_width; y *= global_state.cell_height;
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, global_state.cell_width, global_state.cell_height, 1, GL_RED, GL_UNSIGNED_BYTE, buf);
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, global_state.cell_width, global_state.cell_height, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buf);
}
void

View File

@ -165,4 +165,4 @@ void draw_cursor(CursorRenderInfo *, bool);
void update_surface_size(int, int, uint32_t);
void free_texture(uint32_t*);
void send_image_to_gpu(uint32_t*, const void*, int32_t, int32_t, bool, bool);
void send_sprite_to_gpu(unsigned int, unsigned int, unsigned int, uint8_t*);
void send_sprite_to_gpu(unsigned int, unsigned int, unsigned int, pixel*);