Fix signed distance field font rendering

This fix works in both GLES3 and GLES2.

The rendering formula in the shader was adjusted to further improve the
sharpness/antialiasing quality balance.

Co-authored-by: Hugo Locurcio <hugo.locurcio@hugo.pro>
This commit is contained in:
lawnjelly 2024-01-04 14:35:00 +00:00
parent a81d96c637
commit bc607fb607
19 changed files with 80 additions and 22 deletions

View file

@ -58,6 +58,7 @@ void RasterizerCanvasBaseGLES2::canvas_begin() {
state.using_large_vertex = false;
state.using_modulate = false;
state.canvas_shader.set_conditional(CanvasShaderGLES2::USE_DISTANCE_FIELD, false);
state.canvas_shader.set_conditional(CanvasShaderGLES2::USE_ATTRIB_LIGHT_ANGLE, false);
state.canvas_shader.set_conditional(CanvasShaderGLES2::USE_ATTRIB_MODULATE, false);
state.canvas_shader.set_conditional(CanvasShaderGLES2::USE_ATTRIB_LARGE_VERTEX, false);

View file

@ -1225,6 +1225,7 @@ void RasterizerCanvasGLES2::canvas_render_items_implementation(Item *p_item_list
ris.item_group_modulate = p_modulate;
ris.item_group_light = p_light;
ris.item_group_base_transform = p_base_transform;
ris.prev_distance_field = false;
state.canvas_shader.set_conditional(CanvasShaderGLES2::USE_SKELETON, false);
@ -1558,6 +1559,12 @@ bool RasterizerCanvasGLES2::try_join_item(Item *p_ci, RenderItemState &r_ris, bo
join = false;
}
if (r_ris.prev_distance_field != p_ci->distance_field) {
r_ris.prev_distance_field = p_ci->distance_field;
join = false;
r_batch_break = true;
}
// non rects will break the batching anyway, we don't want to record item changes, detect this
if (!r_batch_break && _detect_item_batch_break(r_ris, p_ci, r_batch_break)) {
join = false;
@ -1573,6 +1580,12 @@ bool RasterizerCanvasGLES2::try_join_item(Item *p_ci, RenderItemState &r_ris, bo
void RasterizerCanvasGLES2::_legacy_canvas_render_item(Item *p_ci, RenderItemState &r_ris) {
storage->info.render._2d_item_count++;
if (r_ris.prev_distance_field != p_ci->distance_field) {
state.canvas_shader.set_conditional(CanvasShaderGLES2::USE_DISTANCE_FIELD, p_ci->distance_field);
r_ris.prev_distance_field = p_ci->distance_field;
r_ris.rebind_shader = true;
}
if (r_ris.current_clip != p_ci->final_clip_owner) {
r_ris.current_clip = p_ci->final_clip_owner;
@ -1936,6 +1949,12 @@ void RasterizerCanvasGLES2::render_joined_item(const BItemJoined &p_bij, RenderI
// all the joined items will share the same state with the first item
Item *ci = bdata.item_refs[p_bij.first_item_ref].item;
if (r_ris.prev_distance_field != ci->distance_field) {
state.canvas_shader.set_conditional(CanvasShaderGLES2::USE_DISTANCE_FIELD, ci->distance_field);
r_ris.prev_distance_field = ci->distance_field;
r_ris.rebind_shader = true;
}
if (r_ris.current_clip != ci->final_clip_owner) {
r_ris.current_clip = ci->final_clip_owner;

View file

@ -443,10 +443,18 @@ void main() {
uv = mod(uv, vec2(1.0, 1.0));
#endif
#ifdef USE_DISTANCE_FIELD
// Higher is smoother, but also more blurry. Lower is crisper, but also more aliased.
// TODO: Adjust automatically based on screen resolution/font size ratio.
const float smoothing = 0.125;
float dist = texture2D(color_texture, uv).a;
color.a = smoothstep(0.5 - smoothing, 0.5 + smoothing, dist);
#else
#if !defined(COLOR_USED)
//default behavior, texture by color
// Default behavior, texture by color.
color *= texture2D(color_texture, uv);
#endif
#endif
#ifdef SCREEN_UV_USED
vec2 screen_uv = gl_FragCoord.xy * screen_pixel_size;

View file

@ -165,6 +165,7 @@ void RasterizerCanvasBaseGLES3::canvas_begin() {
state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_DISTANCE_FIELD, false);
state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_NINEPATCH, false);
state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_DISTANCE_FIELD, false);
state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_ATTRIB_LIGHT_ANGLE, false);
state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_ATTRIB_MODULATE, false);
state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_ATTRIB_LARGE_VERTEX, false);

View file

@ -1878,6 +1878,12 @@ bool RasterizerCanvasGLES3::try_join_item(Item *p_ci, RenderItemState &r_ris, bo
join = false;
}
if (r_ris.prev_distance_field != p_ci->distance_field) {
r_ris.prev_distance_field = p_ci->distance_field;
join = false;
r_batch_break = true;
}
// non rects will break the batching anyway, we don't want to record item changes, detect this
if (!r_batch_break && _detect_item_batch_break(r_ris, p_ci, r_batch_break)) {
join = false;

View file

@ -581,18 +581,17 @@ void main() {
#endif
#if !defined(COLOR_USED)
//default behavior, texture by color
#ifdef USE_DISTANCE_FIELD
const float smoothing = 1.0 / 32.0;
float distance = textureLod(color_texture, uv, 0.0).a;
color.a = smoothstep(0.5 - smoothing, 0.5 + smoothing, distance) * color.a;
// Higher is smoother, but also more blurry. Lower is crisper, but also more aliased.
// TODO: Adjust automatically based on screen resolution/font size ratio.
const float smoothing = 0.125;
float dist = texture(color_texture, uv, 0.0).a;
color.a = smoothstep(0.5 - smoothing, 0.5 + smoothing, dist);
#else
#if !defined(COLOR_USED)
// Default behavior, texture by color.
color *= texture(color_texture, uv);
#endif
#endif
vec3 normal;

View file

@ -955,6 +955,17 @@ void CanvasItem::draw_multimesh(const Ref<MultiMesh> &p_multimesh, const Ref<Tex
VisualServer::get_singleton()->canvas_item_add_multimesh(canvas_item, p_multimesh->get_rid(), texture_rid, normal_map_rid);
}
void CanvasItem::select_font(const Ref<Font> &p_font) {
// Purely to keep canvas item SDF state up to date for now.
bool new_font_sdf_selected = p_font.is_valid() && p_font->is_distance_field_hint();
if (font_sdf_selected != new_font_sdf_selected) {
ERR_FAIL_COND(!get_canvas_item().is_valid());
font_sdf_selected = new_font_sdf_selected;
VisualServer::get_singleton()->canvas_item_set_distance_field_mode(get_canvas_item(), font_sdf_selected);
}
}
void CanvasItem::draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, const Color &p_modulate, int p_clip_w) {
ERR_FAIL_COND_MSG(!drawing, "Drawing is only allowed inside NOTIFICATION_DRAW, _draw() function or 'draw' signal.");
@ -1351,6 +1362,7 @@ CanvasItem::CanvasItem() :
global_invalid = true;
notify_local_transform = false;
notify_transform = false;
font_sdf_selected = false;
light_mask = 1;
C = nullptr;

View file

@ -191,21 +191,22 @@ private:
int light_mask;
bool first_draw;
bool visible;
bool pending_update;
bool toplevel;
bool drawing;
bool block_transform_notify;
bool behind;
bool use_parent_material;
bool notify_local_transform;
bool notify_transform;
bool first_draw : 1;
bool visible : 1;
bool pending_update : 1;
bool toplevel : 1;
bool drawing : 1;
bool block_transform_notify : 1;
bool behind : 1;
bool use_parent_material : 1;
bool notify_local_transform : 1;
bool notify_transform : 1;
bool font_sdf_selected : 1;
mutable bool global_invalid : 1;
Ref<Material> material;
mutable Transform2D global_transform;
mutable bool global_invalid;
void _toplevel_raise_self();
void _toplevel_visibility_changed(bool p_visible);
@ -335,6 +336,7 @@ public:
void draw_mesh(const Ref<Mesh> &p_mesh, const Ref<Texture> &p_texture, const Ref<Texture> &p_normal_map, const Transform2D &p_transform = Transform2D(), const Color &p_modulate = Color(1, 1, 1));
void draw_multimesh(const Ref<MultiMesh> &p_multimesh, const Ref<Texture> &p_texture, const Ref<Texture> &p_normal_map);
void select_font(const Ref<Font> &p_font);
void draw_string(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_text, const Color &p_modulate = Color(1, 1, 1), int p_clip_w = -1);
float draw_char(const Ref<Font> &p_font, const Point2 &p_pos, const String &p_char, const String &p_next = "", const Color &p_modulate = Color(1, 1, 1));

View file

@ -286,6 +286,7 @@ void Button::_notification(int p_what) {
text_ofs.y += font->get_ascent();
text_ofs.y += line_height * (((float)i) - (((float)(num_lines - 1)) / 2.0));
select_font(font);
font->draw(ci, text_ofs.floor(), line_text, color, clip_text ? text_clip : -1);
}
} break;

View file

@ -206,6 +206,7 @@ void WindowDialog::_notification(int p_what) {
int font_height = title_font->get_height() - title_font->get_descent() * 2;
int x = (size.x - title_font->get_string_size(xl_title).x) / 2;
int y = (-title_height + font_height) / 2;
select_font(title_font);
title_font->draw(canvas, Point2(x, y), xl_title, title_color, size.x - panel->get_minimum_size().x);
} break;

View file

@ -767,6 +767,7 @@ void ItemList::_notification(int p_what) {
Ref<StyleBox> cursor = has_focus() ? get_stylebox("cursor") : get_stylebox("cursor_unfocused");
Ref<Font> font = get_font("font");
select_font(font);
Color guide_color = get_color("guide_color");
Color font_color = get_color("font_color");
Color font_color_selected = get_color("font_color_selected");

View file

@ -90,6 +90,7 @@ void Label::_notification(int p_what) {
Size2 size = get_size();
Ref<StyleBox> style = get_stylebox("normal");
Ref<Font> font = get_font("font");
select_font(font);
Color font_color = get_color("font_color");
Color font_color_shadow = get_color("font_color_shadow");
bool use_outline = get_constant("shadow_as_outline");
@ -99,8 +100,6 @@ void Label::_notification(int p_what) {
style->draw(ci, Rect2(Point2(0, 0), get_size()));
VisualServer::get_singleton()->canvas_item_set_distance_field_mode(get_canvas_item(), font.is_valid() && font->is_distance_field_hint());
int font_h = font->get_height() + line_spacing;
int lines_visible = (size.y + line_spacing) / font_h;

View file

@ -813,6 +813,7 @@ void LineEdit::_notification(int p_what) {
}
Ref<Font> font = get_font("font");
select_font(font);
style->draw(ci, Rect2(Point2(), size));

View file

@ -128,6 +128,7 @@ void LinkButton::_notification(int p_what) {
}
Ref<Font> font = get_font("font");
select_font(font);
draw_string(font, Vector2(0, font->get_ascent()), xl_text, color);

View file

@ -466,6 +466,8 @@ void PopupMenu::_draw_items() {
Ref<StyleBox> style = get_stylebox("panel");
Ref<StyleBox> hover = get_stylebox("hover");
Ref<Font> font = get_font("font");
select_font(font);
// In Item::checkable_type enum order (less the non-checkable member)
Ref<Texture> check[] = { get_icon("checked"), get_icon("radio_checked") };
Ref<Texture> uncheck[] = { get_icon("unchecked"), get_icon("radio_unchecked") };

View file

@ -52,6 +52,7 @@ void ProgressBar::_notification(int p_what) {
Ref<StyleBox> bg = get_stylebox("bg");
Ref<StyleBox> fg = get_stylebox("fg");
Ref<Font> font = get_font("font");
select_font(font);
Color font_color = get_color("font_color");
draw_style_box(bg, Rect2(Point2(), get_size()));

View file

@ -1077,6 +1077,7 @@ void RichTextLabel::_notification(int p_what) {
}
int y = (main->lines[from_line].height_accum_cache - main->lines[from_line].height_cache) - ofs;
Ref<Font> base_font = get_font("normal_font");
select_font(base_font);
Color base_color = get_color("default_color");
Color font_color_shadow = get_color("font_color_shadow");
bool use_outline = get_constant("shadow_as_outline");

View file

@ -279,6 +279,7 @@ void TabContainer::_notification(int p_what) {
Ref<Texture> menu = get_icon("menu");
Ref<Texture> menu_hl = get_icon("menu_highlight");
Ref<Font> font = get_font("font");
select_font(font);
Color font_color_fg = get_color("font_color_fg");
Color font_color_bg = get_color("font_color_bg");
Color font_color_disabled = get_color("font_color_disabled");

View file

@ -243,6 +243,7 @@ void Tabs::_notification(int p_what) {
Ref<StyleBox> tab_fg = get_stylebox("tab_fg");
Ref<StyleBox> tab_disabled = get_stylebox("tab_disabled");
Ref<Font> font = get_font("font");
select_font(font);
Color color_fg = get_color("font_color_fg");
Color color_bg = get_color("font_color_bg");
Color color_disabled = get_color("font_color_disabled");