Read decorations indices from a separate texture

This commit is contained in:
Kovid Goyal 2024-12-14 17:37:07 +05:30
parent b2a4db2028
commit 88f796a94c
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
7 changed files with 154 additions and 62 deletions

View file

@ -16,16 +16,18 @@ layout(std140) uniform CellRenderData {
float bg_opacities0, bg_opacities1, bg_opacities2, bg_opacities3, bg_opacities4, bg_opacities5, bg_opacities6, bg_opacities7;
uint color_table[NUM_COLORS + MARK_MASK + MARK_MASK + 2];
};
uniform float gamma_lut[256];
#ifdef NEEDS_FOREGROUND
uniform usampler2D sprite_decorations_map;
#endif
#if (PHASE == PHASE_BACKGROUND)
uniform uint draw_bg_bitfield;
#endif
// Have to use fixed locations here as all variants of the cell program share the same VAO
// Have to use fixed locations here as all variants of the cell program share the same VAOs
layout(location=0) in uvec3 colors;
layout(location=1) in uvec2 sprite_idx;
layout(location=2) in uint is_selected;
uniform float gamma_lut[256];
const int fg_index_map[] = int[3](0, 1, 0);
const uvec2 cell_pos_map[] = uvec2[4](
@ -58,13 +60,12 @@ out float effective_text_alpha;
// Utility functions {{{
const uint BYTE_MASK = uint(0xFF);
const uint Z_MASK = uint(0x7fffffff);
const uint COLOR_MASK = uint(0x80000000);
const uint COLOR_SHIFT = uint(31);
const uint SPRITE_INDEX_MASK = uint(0x7fffffff);
const uint SPRITE_COLORED_MASK = uint(0x80000000);
const uint SPRITE_COLORED_SHIFT = uint(31);
const uint ZERO = uint(0);
const uint ONE = uint(1);
const uint TWO = uint(2);
const uint STRIKE_SPRITE_INDEX = uint({STRIKE_SPRITE_INDEX});
const uint DECORATION_MASK = uint({DECORATION_MASK});
vec3 color_to_vec(uint c) {
@ -112,6 +113,26 @@ vec3 to_sprite_pos(uvec2 pos, uint idx) {
return vec3(s_xpos[pos.x], s_ypos[pos.y], c.z);
}
#ifdef NEEDS_FOREGROUND
uint read_sprite_decorations_idx() {
int idx = int(sprite_idx[0] & SPRITE_INDEX_MASK);
ivec2 sz = textureSize(sprite_decorations_map, 0);
int y = idx / sz[0];
int x = idx % sz[0];
return texelFetch(sprite_decorations_map, ivec2(x, y), 0).r;
}
uvec2 get_decorations_indices(uint in_url /* [0, 1] */, uint text_attrs) {
uint decorations_idx = read_sprite_decorations_idx();
uint strike_style = ((text_attrs >> STRIKE_SHIFT) & ONE); // 0 or 1
uint strike_idx = decorations_idx * strike_style;
uint underline_style = ((text_attrs >> DECORATION_SHIFT) & DECORATION_MASK);
underline_style = in_url * url_style + (1u - in_url) * underline_style; // [0, 5]
uint has_underline = uint(step(0.5f, float(underline_style))); // [0, 1]
return uvec2(strike_idx, has_underline * (decorations_idx + underline_style));
}
#endif
vec3 choose_color(float q, vec3 a, vec3 b) {
return mix(b, a, q);
}
@ -151,8 +172,8 @@ CellData set_vertex_position() {
gl_Position = vec4(xpos[pos.x], ypos[pos.y], 0, 1);
#ifdef NEEDS_FOREGROUND
// The character sprite being rendered
sprite_pos = to_sprite_pos(pos, sprite_idx[0] & Z_MASK);
colored_sprite = float((sprite_idx[0] & COLOR_MASK) >> COLOR_SHIFT);
sprite_pos = to_sprite_pos(pos, sprite_idx[0] & SPRITE_INDEX_MASK);
colored_sprite = float((sprite_idx[0] & SPRITE_COLORED_MASK) >> SPRITE_COLORED_SHIFT);
#endif
float is_block_cursor = step(float(cursor_fg_sprite_idx), 0.5);
float has_cursor = is_cursor(c, r);
@ -198,8 +219,6 @@ void main() {
// Foreground {{{
#ifdef NEEDS_FOREGROUND
// Foreground
fg_as_uint = has_mark * color_table[NUM_COLORS + MARK_MASK + mark] + (ONE - has_mark) * fg_as_uint;
foreground = color_to_vec(fg_as_uint);
@ -213,8 +232,9 @@ void main() {
foreground = choose_color(float(is_selected & ONE), selection_color, foreground);
decoration_fg = choose_color(float(is_selected & ONE), selection_color, decoration_fg);
// Underline and strike through (rendered via sprites)
underline_pos = choose_color(in_url, to_sprite_pos(cell_data.pos, url_style), to_sprite_pos(cell_data.pos, (text_attrs >> DECORATION_SHIFT) & DECORATION_MASK));
strike_pos = to_sprite_pos(cell_data.pos, ((text_attrs >> STRIKE_SHIFT) & ONE) * STRIKE_SPRITE_INDEX);
uvec2 decs = get_decorations_indices(uint(in_url), text_attrs);
strike_pos = to_sprite_pos(cell_data.pos, decs[0]);
underline_pos = to_sprite_pos(cell_data.pos, decs[1]);
// Cursor
cursor_color_premult = vec4(color_to_vec(cursor_bg) * cursor_opacity, cursor_opacity);

View file

@ -331,7 +331,7 @@ void request_window_attention(id_type, bool);
void play_canberra_sound(const char *which_sound, const char *event_id, bool is_path, const char *role, const char *theme_name);
#endif
SPRITE_MAP_HANDLE alloc_sprite_map(void);
SPRITE_MAP_HANDLE free_sprite_map(SPRITE_MAP_HANDLE);
void free_sprite_data(FONTS_DATA_HANDLE);
const char* get_hyperlink_for_id(const HYPERLINK_POOL_HANDLE, hyperlink_id_type id, bool only_url);
#define memset_array(array, val, count) if ((count) > 0) { \

View file

@ -137,9 +137,8 @@ python_send_to_gpu(FontGroup *fg, sprite_index idx, pixel *buf) {
static void
current_send_sprite_to_gpu(FontGroup *fg, sprite_index idx, pixel *buf, sprite_index decorations_idx) {
(void)decorations_idx;
if (python_send_to_gpu_impl) python_send_to_gpu(fg, idx, buf);
else send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, idx, buf);
if (python_send_to_gpu_impl) { python_send_to_gpu(fg, idx, buf); return; }
send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, idx, buf, decorations_idx);
}
static void
@ -209,7 +208,7 @@ del_font(Font *f) {
static void
del_font_group(FontGroup *fg) {
free(fg->canvas.buf); fg->canvas.buf = NULL; fg->canvas = (Canvas){0};
fg->sprite_map = free_sprite_map(fg->sprite_map);
free_sprite_data((FONTS_DATA_HANDLE)fg);
vt_cleanup(&fg->fallback_font_map);
vt_cleanup(&fg->scaled_font_map);
vt_cleanup(&fg->decorations_index_map);

View file

@ -10,6 +10,7 @@
#include "text-cache.h"
// TODO: Test setting of ch_and_idx to make sure the right ch_is_idx bit is set
// TODO: Test multiple OS windows with decorations also with changing font size and test on macOS and Linux for re-alloc
// TODO: Test handling of calt ligatures with scale see is_group_calt_ligature()
// TODO: Handle selection with multicell
// TODO: URL detection with multicell

View file

@ -19,13 +19,18 @@
#define BLEND_PREMULT glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // blending of pre-multiplied colors
enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_ALPHA_MASK_PROGRAM, BGIMAGE_PROGRAM, TINT_PROGRAM, TRAIL_PROGRAM, NUM_PROGRAMS };
enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, BGIMAGE_UNIT };
enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, BGIMAGE_UNIT, SPRITE_DECORATIONS_MAP_UNIT };
// Sprites {{{
typedef struct {
int xnum, ynum, x, y, z, last_num_of_layers, last_ynum;
GLuint texture_id;
GLint max_texture_size, max_array_texture_layers;
struct decorations_map {
GLuint texture_id;
unsigned width, height;
size_t count;
} decorations_map;
} SpriteMap;
static const SpriteMap NEW_SPRITE_MAP = { .xnum = 1, .ynum = 1, .last_num_of_layers = 1, .last_ynum = -1 };
@ -68,65 +73,115 @@ alloc_sprite_map(void) {
return (SPRITE_MAP_HANDLE)ans;
}
SPRITE_MAP_HANDLE
free_sprite_map(SPRITE_MAP_HANDLE sm) {
SpriteMap *sprite_map = (SpriteMap*)sm;
void
free_sprite_data(FONTS_DATA_HANDLE fg) {
SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
if (sprite_map) {
if (sprite_map->texture_id) free_texture(&sprite_map->texture_id);
if (sprite_map->decorations_map.texture_id) free_texture(&sprite_map->texture_id);
free(sprite_map);
fg->sprite_map = NULL;
}
return NULL;
}
static bool copy_image_warned = false;
static void
copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int width, unsigned int height, unsigned int num_levels) {
if (!GLAD_GL_ARB_copy_image) {
// ARB_copy_image not available, do a slow roundtrip copy
if (!copy_image_warned) {
copy_image_warned = true;
log_error("WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation");
}
size_t sz = (size_t)width * height * num_levels;
pixel *src = malloc(sz * sizeof(pixel));
if (src == NULL) { fatal("Out of memory."); }
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, 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);
copy_32bit_texture(GLuint old_texture, GLuint new_texture, GLenum texture_type) {
// requires new texture to be at least as big as old texture. Assumes textures are 32bits per pixel
GLint width, height, layers;
glBindTexture(texture_type, old_texture);
glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_WIDTH, &width);
glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_HEIGHT, &height);
glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_DEPTH, &layers);
if (GLAD_GL_ARB_copy_image) { glCopyImageSubData(old_texture, texture_type, 0, 0, 0, 0, new_texture, texture_type, 0, 0, 0, 0, width, height, layers); return; }
static bool copy_image_warned = false;
// ARB_copy_image not available, do a slow roundtrip copy
if (!copy_image_warned) {
copy_image_warned = true;
log_error("WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation");
}
GLint internal_format;
glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format);
GLenum format, type;
switch(internal_format) {
case GL_R8UI: case GL_R8I: case GL_R16UI: case GL_R16I: case GL_R32UI: case GL_R32I: case GL_RG8UI: case GL_RG8I:
case GL_RG16UI: case GL_RG16I: case GL_RG32UI: case GL_RG32I: case GL_RGB8UI: case GL_RGB8I: case GL_RGB16UI:
case GL_RGB16I: case GL_RGB32UI: case GL_RGB32I: case GL_RGBA8UI: case GL_RGBA8I: case GL_RGBA16UI: case GL_RGBA16I:
case GL_RGBA32UI: case GL_RGBA32I:
format = GL_RED_INTEGER;
type = GL_UNSIGNED_INT;
break;
default:
format = GL_RGBA;
type = GL_UNSIGNED_INT_8_8_8_8;
break;
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
RAII_ALLOC(uint8_t, pixels, malloc(width * height * layers * 4));
if (!pixels) fatal("Out of memory");
glGetTexImage(texture_type, 0, format, type, pixels);
glBindTexture(texture_type, new_texture);
glPixelStorei(GL_PACK_ALIGNMENT, 4);
if (texture_type == GL_TEXTURE_2D_ARRAY) glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, layers, format, type, pixels);
else glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, pixels);
}
static GLuint
setup_new_sprites_texture(GLenum texture_type) {
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(texture_type, tex);
// We use GL_NEAREST otherwise glyphs that touch the edge of the cell
// often show a border between cells
glTexParameteri(texture_type, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(texture_type, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(texture_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(texture_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return tex;
}
static void
realloc_sprite_decorations_texture_if_needed(FONTS_DATA_HANDLE fg) {
#define dm (sm->decorations_map)
SpriteMap *sm = (SpriteMap*)fg->sprite_map;
size_t current_capacity = dm.width * dm.height;
if (dm.count < current_capacity && dm.texture_id) return;
GLint new_capacity = dm.count + 256;
GLint width = new_capacity, height = 1;
if (new_capacity > sm->max_texture_size) {
width = sm->max_texture_size;
height = 1 + new_capacity / width;
}
if (height > sm->max_texture_size) fatal("Max texture size too small for sprite decorations map, maybe switch to using a GL_TEXTURE_2D_ARRAY");
const GLenum texture_type = GL_TEXTURE_2D;
GLuint tex = setup_new_sprites_texture(texture_type);
glTexImage2D(texture_type, 0, GL_R32UI, width, height, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, NULL);
if (dm.texture_id) { // copy data from old texture
copy_32bit_texture(dm.texture_id, tex, texture_type);
glDeleteTextures(1, &dm.texture_id);
}
glBindTexture(texture_type, 0);
dm.texture_id = tex; dm.width = width; dm.height = height;
#undef dm
}
static void
realloc_sprite_texture(FONTS_DATA_HANDLE fg) {
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
// We use GL_NEAREST otherwise glyphs that touch the edge of the cell
// often show a border between cells
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
unsigned int xnum, ynum, z, znum, width, height, src_ynum;
unsigned int xnum, ynum, z, znum, width, height;
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;
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);
const GLenum texture_type = GL_TEXTURE_2D_ARRAY;
GLuint tex = setup_new_sprites_texture(texture_type);
glTexStorage3D(texture_type, 1, GL_SRGB8_ALPHA8, width, height, znum);
if (sprite_map->texture_id) { // copy old texture data into new texture
copy_32bit_texture(sprite_map->texture_id, tex, texture_type);
glDeleteTextures(1, &sprite_map->texture_id);
}
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
glBindTexture(texture_type, 0);
sprite_map->last_num_of_layers = znum;
sprite_map->last_ynum = ynum;
sprite_map->texture_id = tex;
@ -136,21 +191,37 @@ static void
ensure_sprite_map(FONTS_DATA_HANDLE fg) {
SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
if (!sprite_map->texture_id) realloc_sprite_texture(fg);
if (!sprite_map->decorations_map.texture_id) realloc_sprite_decorations_texture_if_needed(fg);
// We have to rebind since we don't know if the texture was ever bound
// in the context of the current OSWindow
glActiveTexture(GL_TEXTURE0 + SPRITE_DECORATIONS_MAP_UNIT);
glBindTexture(GL_TEXTURE_2D, sprite_map->decorations_map.texture_id);
glActiveTexture(GL_TEXTURE0 + SPRITE_MAP_UNIT);
glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id);
}
void
send_sprite_to_gpu(FONTS_DATA_HANDLE fg, sprite_index idx, pixel *buf) {
send_sprite_to_gpu(FONTS_DATA_HANDLE fg, sprite_index idx, pixel *buf, sprite_index decoration_idx) {
SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
unsigned int xnum, ynum, znum, x, y, z;
#define dm (sprite_map->decorations_map)
realloc_sprite_decorations_texture_if_needed(fg);
if (idx >= dm.count) {
dm.count = idx + 1;
div_t d = div(idx, dm.width);
x = d.rem; y = d.quot;
glActiveTexture(GL_TEXTURE0 + SPRITE_DECORATIONS_MAP_UNIT);
glBindTexture(GL_TEXTURE_2D, dm.texture_id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &decoration_idx);
}
#undef dm
sprite_tracker_current_layout(fg, &xnum, &ynum, &znum);
if ((int)znum >= sprite_map->last_num_of_layers || (znum == 0 && (int)ynum > sprite_map->last_ynum)) {
realloc_sprite_texture(fg);
sprite_tracker_current_layout(fg, &xnum, &ynum, &znum);
}
glActiveTexture(GL_TEXTURE0 + SPRITE_MAP_UNIT);
glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
sprite_index_to_pos(idx, xnum, ynum, &x, &y, &z);
@ -232,7 +303,7 @@ init_cell_program(void) {
// Sanity check to ensure the attribute location binding worked
#define C(p, name, expected) { int aloc = attrib_location(p, #name); if (aloc != expected && aloc != -1) fatal("The attribute location for %s is %d != %d in program: %d", #name, aloc, expected, p); }
for (int p = CELL_PROGRAM; p < BORDERS_PROGRAM; p++) {
C(p, colors, 0); C(p, sprite_idx, 1); C(p, is_selected, 2);
C(p, colors, 0); C(p, sprite_idx, 1); C(p, is_selected, 2); C(p, decorations_sprite_map, 3);
}
#undef C
for (int i = GRAPHICS_PROGRAM; i <= GRAPHICS_ALPHA_MASK_PROGRAM; i++) {
@ -455,6 +526,7 @@ cell_prepare_to_render(ssize_t vao_idx, Screen *screen, GLfloat xstart, GLfloat
#undef update_cell_data
screen->last_rendered.columns = screen->columns;
screen->last_rendered.lines = screen->lines;
return changed;
}
@ -653,6 +725,7 @@ set_cell_uniforms(float current_inactive_text_alpha, bool force) {
switch(i) {
case CELL_PROGRAM: case CELL_FG_PROGRAM:
glUniform1i(cu->sprites, SPRITE_MAP_UNIT);
glUniform1i(cu->sprite_decorations_map, SPRITE_DECORATIONS_MAP_UNIT);
glUniform1f(cu->dim_opacity, OPT(dim_opacity));
glUniform1f(cu->text_contrast, text_contrast);
glUniform1f(cu->text_gamma_adjustment, text_gamma_adjustment);

View file

@ -161,7 +161,6 @@ def __call__(self, semi_transparent: bool = False, allow_recompile: bool = False
MARK_SHIFT=MARK,
MARK_MASK=MARK_MASK,
DECORATION_MASK=DECORATION_MASK,
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, sprite_index, pixel*);
void send_sprite_to_gpu(FONTS_DATA_HANDLE fg, sprite_index, pixel*, sprite_index);
void blank_canvas(float, color_type);
void blank_os_window(OSWindow *);
void set_os_window_chrome(OSWindow *w);