Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for subpixel rendering on Linux #1604

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion kitty/cell_fragment.glsl
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#version GLSL_VERSION
#define WHICH_PROGRAM
#define NOT_TRANSPARENT
#define NOT_SUBPIXEL

#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL)
#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL) || defined(SUBPIXEL)
#define NEEDS_BACKROUND
#endif

Expand All @@ -28,6 +29,7 @@ in vec3 foreground;
in vec4 cursor_color_vec;
in vec3 decoration_fg;
in float colored_sprite;
in float subpixel;
#endif

out vec4 final_color;
Expand Down Expand Up @@ -94,8 +96,23 @@ vec4 blend_onto_opaque_premul(vec3 over, float over_alpha, vec3 under) {
vec4 calculate_foreground() {
// returns the effective foreground color in pre-multiplied form
vec4 text_fg = texture(sprites, sprite_pos);
#ifdef SUBPIXEL
vec3 unblended_fg = mix(foreground, text_fg.rgb, colored_sprite);
#ifdef TRANSPARENT
float alpha = text_fg.g; // Cairo uses green channel to convert FreeType's subpixel buffer to ARGB
float scale_coeff = mix(1, alpha, alpha > 0);
vec3 scaled_mask = text_fg.rgb / scale_coeff;
vec3 blended_fg = foreground * scaled_mask;
float text_alpha = mix(text_fg.a, alpha, subpixel);
#else
vec3 blended_fg = mix(background, foreground, text_fg.rgb);
float text_alpha = text_fg.a;
#endif
vec3 fg = mix(unblended_fg, blended_fg, subpixel);
#else
vec3 fg = mix(foreground, text_fg.rgb, colored_sprite);
float text_alpha = text_fg.a;
#endif
float underline_alpha = texture(sprites, underline_pos).a;
float strike_alpha = texture(sprites, strike_pos).a;
float cursor_alpha = texture(sprites, cursor_pos).a;
Expand Down
6 changes: 5 additions & 1 deletion kitty/cell_vertex.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define STRIKE_SHIFT {STRIKE_SHIFT}
#define DIM_SHIFT {DIM_SHIFT}
#define USE_SELECTION_FG
#define NOT_SUBPIXEL

// Inputs {{{
layout(std140) uniform CellRenderData {
Expand Down Expand Up @@ -35,7 +36,7 @@ const uvec2 cell_pos_map[] = uvec2[4](
// }}}


#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL)
#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL) || defined(SUBPIXEL)
#define NEEDS_BACKROUND
#endif

Expand All @@ -61,6 +62,7 @@ out vec3 strike_pos;
out vec3 foreground;
out vec3 decoration_fg;
out float colored_sprite;
out float subpixel;
out float effective_text_alpha;
#endif

Expand All @@ -69,6 +71,7 @@ out float effective_text_alpha;
const uint BYTE_MASK = uint(0xFF);
const uint Z_MASK = uint(0xFFF);
const uint COLOR_MASK = uint(0x4000);
const uint SUBPIXEL_MASK = uint(0x8000);
const uint ZERO = uint(0);
const uint ONE = uint(1);
const uint TWO = uint(2);
Expand Down Expand Up @@ -166,6 +169,7 @@ void main() {
// The character sprite being rendered
sprite_pos = to_sprite_pos(pos, sprite_coords.x, sprite_coords.y, sprite_coords.z & Z_MASK);
colored_sprite = float((sprite_coords.z & COLOR_MASK) >> 14);
subpixel = float((sprite_coords.z & SUBPIXEL_MASK) >> 15);

// Foreground
uint resolved_fg = resolve_color(colors[fg_index], default_colors[fg_index]);
Expand Down
6 changes: 6 additions & 0 deletions kitty/config_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,12 @@ def box_drawing_scale(x):
and very thick lines.
'''))

o('subpixel_rendering', False, long_text=_('''
Use subpixel rendering instead of grayscale in freetype. Impacts performance,
but may look better on low DPI screens. Possible values are :code:`none`,
:code:`lcd`, and :code:`lcd_v`.
'''))

# }}}

g('cursor') # {{{
Expand Down
2 changes: 1 addition & 1 deletion kitty/core_text.m
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@
}

bool
render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, bool center_glyph) {
render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool UNUSED *was_subpixel, FONTS_DATA_HANDLE fg, bool center_glyph) {
CTFace *self = (CTFace*)s;
for (unsigned i=0; i < num_glyphs; i++) glyphs[i] = info[i].codepoint;
return do_render(self->ct_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, center_glyph);
Expand Down
2 changes: 1 addition & 1 deletion kitty/fontconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pattern_as_dict(FcPattern *pat) {
I(FC_SLANT, slant);
I(FC_HINT_STYLE, hint_style);
I(FC_INDEX, index);
I(FC_RGBA, subpixel);
I(FC_RGBA, rgba);
I(FC_LCD_FILTER, lcdfilter);
B(FC_HINTING, hinting);
B(FC_SCALABLE, scalable);
Expand Down
11 changes: 8 additions & 3 deletions kitty/fonts.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ typedef struct {
typedef struct SpritePosition SpritePosition;
struct SpritePosition {
SpritePosition *next;
bool filled, rendered, colored;
bool filled, rendered, colored, subpixel;
sprite_index x, y, z;
uint8_t ligature_index;
glyph_index glyph;
Expand Down Expand Up @@ -247,6 +247,7 @@ sprite_position_for(FontGroup *fg, Font *font, glyph_index glyph, ExtraGlyphs *e
s->filled = true;
s->rendered = false;
s->colored = false;
s->subpixel = false;
s->x = fg->sprite_tracker.x; s->y = fg->sprite_tracker.y; s->z = fg->sprite_tracker.z;
do_increment(fg, error);
return s;
Expand Down Expand Up @@ -301,7 +302,7 @@ free_maps(Font *font) {

void
clear_sprite_map(Font *font) {
#define CLEAR(s) s->filled = false; s->rendered = false; s->colored = false; s->glyph = 0; memset(&s->extra_glyphs, 0, sizeof(ExtraGlyphs)); s->x = 0; s->y = 0; s->z = 0; s->ligature_index = 0;
#define CLEAR(s) s->filled = false; s->rendered = false; s->colored = false; s->subpixel = false; s->glyph = 0; memset(&s->extra_glyphs, 0, sizeof(ExtraGlyphs)); s->x = 0; s->y = 0; s->z = 0; s->ligature_index = 0;
SpritePosition *s;
for (size_t i = 0; i < sizeof(font->sprite_map)/sizeof(font->sprite_map[0]); i++) {
s = font->sprite_map + i;
Expand Down Expand Up @@ -606,6 +607,7 @@ render_box_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
if (sp->rendered) return;
sp->rendered = true;
sp->colored = false;
sp->subpixel = false;
PyObject *ret = PyObject_CallFunction(box_drawing_function, "IIId", cpu_cell->ch, fg->cell_width, fg->cell_height, (fg->logical_dpi_x + fg->logical_dpi_y) / 2.0);
if (ret == NULL) { PyErr_Print(); return; }
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0));
Expand Down Expand Up @@ -640,6 +642,7 @@ static inline void
set_cell_sprite(GPUCell *cell, SpritePosition *sp) {
cell->sprite_x = sp->x; cell->sprite_y = sp->y; cell->sprite_z = sp->z;
if (sp->colored) cell->sprite_z |= 0x4000;
if (sp->subpixel) cell->sprite_z |= 0x8000;
}

static inline pixel*
Expand Down Expand Up @@ -671,12 +674,14 @@ render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPU

clear_canvas(fg);
bool was_colored = (gpu_cells->attrs & WIDTH_MASK) == 2 && is_emoji(cpu_cells->ch);
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg, center_glyph);
bool was_subpixel = false;
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, &was_subpixel, (FONTS_DATA_HANDLE)fg, center_glyph);
if (PyErr_Occurred()) PyErr_Print();

for (unsigned int i = 0; i < num_cells; i++) {
sprite_position[i]->rendered = true;
sprite_position[i]->colored = was_colored;
sprite_position[i]->subpixel = was_subpixel;
set_cell_sprite(gpu_cells + i, sprite_position[i]);
pixel *buf = num_cells == 1 ? fg->canvas : extract_cell_from_canvas(fg, i, num_cells);
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sprite_position[i]->x, sprite_position[i]->y, sprite_position[i]->z, buf);
Expand Down
2 changes: 1 addition & 1 deletion kitty/fonts.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ bool is_glyph_empty(PyObject *, glyph_index);
hb_font_t* harfbuzz_font_for_face(PyObject*);
bool set_size_for_face(PyObject*, unsigned int, bool, FONTS_DATA_HANDLE);
void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*);
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, bool *was_colored, FONTS_DATA_HANDLE, bool center_glyph);
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, bool *was_colored, bool *was_subpixel, FONTS_DATA_HANDLE, bool center_glyph);
PyObject* create_fallback_face(PyObject *base_face, CPUCell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg);
PyObject* specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE);
PyObject* face_from_path(const char *path, int index, FONTS_DATA_HANDLE);
Expand Down
Loading