From bc607fb6075cebf78d8e562d7b7423b2650906ac Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Thu, 4 Jan 2024 14:35:00 +0000 Subject: [PATCH] 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 --- .../gles2/rasterizer_canvas_base_gles2.cpp | 1 + drivers/gles2/rasterizer_canvas_gles2.cpp | 19 +++++++++++++++ drivers/gles2/shaders/canvas.glsl | 10 +++++++- .../gles3/rasterizer_canvas_base_gles3.cpp | 1 + drivers/gles3/rasterizer_canvas_gles3.cpp | 6 +++++ drivers/gles3/shaders/canvas.glsl | 15 ++++++------ scene/2d/canvas_item.cpp | 12 ++++++++++ scene/2d/canvas_item.h | 24 ++++++++++--------- scene/gui/button.cpp | 1 + scene/gui/dialogs.cpp | 1 + scene/gui/item_list.cpp | 1 + scene/gui/label.cpp | 3 +-- scene/gui/line_edit.cpp | 1 + scene/gui/link_button.cpp | 1 + scene/gui/popup_menu.cpp | 2 ++ scene/gui/progress_bar.cpp | 1 + scene/gui/rich_text_label.cpp | 1 + scene/gui/tab_container.cpp | 1 + scene/gui/tabs.cpp | 1 + 19 files changed, 80 insertions(+), 22 deletions(-) diff --git a/drivers/gles2/rasterizer_canvas_base_gles2.cpp b/drivers/gles2/rasterizer_canvas_base_gles2.cpp index a8087eb139a..8615dc2e64e 100644 --- a/drivers/gles2/rasterizer_canvas_base_gles2.cpp +++ b/drivers/gles2/rasterizer_canvas_base_gles2.cpp @@ -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); diff --git a/drivers/gles2/rasterizer_canvas_gles2.cpp b/drivers/gles2/rasterizer_canvas_gles2.cpp index 54830f3d67b..cf54e1d6f92 100644 --- a/drivers/gles2/rasterizer_canvas_gles2.cpp +++ b/drivers/gles2/rasterizer_canvas_gles2.cpp @@ -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; diff --git a/drivers/gles2/shaders/canvas.glsl b/drivers/gles2/shaders/canvas.glsl index f112ee821d9..a1070c2c524 100644 --- a/drivers/gles2/shaders/canvas.glsl +++ b/drivers/gles2/shaders/canvas.glsl @@ -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; diff --git a/drivers/gles3/rasterizer_canvas_base_gles3.cpp b/drivers/gles3/rasterizer_canvas_base_gles3.cpp index 4c56ea4cc42..46586b674d3 100644 --- a/drivers/gles3/rasterizer_canvas_base_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_base_gles3.cpp @@ -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); diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index b9f4399e37c..163876d73b1 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -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; diff --git a/drivers/gles3/shaders/canvas.glsl b/drivers/gles3/shaders/canvas.glsl index 9f13f4f6453..95f351f52ae 100644 --- a/drivers/gles3/shaders/canvas.glsl +++ b/drivers/gles3/shaders/canvas.glsl @@ -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; diff --git a/scene/2d/canvas_item.cpp b/scene/2d/canvas_item.cpp index a78a5354b6c..0b1d0bc865d 100644 --- a/scene/2d/canvas_item.cpp +++ b/scene/2d/canvas_item.cpp @@ -955,6 +955,17 @@ void CanvasItem::draw_multimesh(const Ref &p_multimesh, const Refcanvas_item_add_multimesh(canvas_item, p_multimesh->get_rid(), texture_rid, normal_map_rid); } +void CanvasItem::select_font(const Ref &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 &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; diff --git a/scene/2d/canvas_item.h b/scene/2d/canvas_item.h index 4ee5aef59b2..c8acd5652f1 100644 --- a/scene/2d/canvas_item.h +++ b/scene/2d/canvas_item.h @@ -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; 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 &p_mesh, const Ref &p_texture, const Ref &p_normal_map, const Transform2D &p_transform = Transform2D(), const Color &p_modulate = Color(1, 1, 1)); void draw_multimesh(const Ref &p_multimesh, const Ref &p_texture, const Ref &p_normal_map); + void select_font(const Ref &p_font); void draw_string(const Ref &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 &p_font, const Point2 &p_pos, const String &p_char, const String &p_next = "", const Color &p_modulate = Color(1, 1, 1)); diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 85542879ae6..543856f3a71 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -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; diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index 3dd7c681c96..9ddc0be31f2 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -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; diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 12a6ea50936..6d9e6f648da 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -767,6 +767,7 @@ void ItemList::_notification(int p_what) { Ref cursor = has_focus() ? get_stylebox("cursor") : get_stylebox("cursor_unfocused"); Ref 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"); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 13febfff5ea..848746c93c7 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -90,6 +90,7 @@ void Label::_notification(int p_what) { Size2 size = get_size(); Ref style = get_stylebox("normal"); Ref 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; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 96345f4265c..d025709c35f 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -813,6 +813,7 @@ void LineEdit::_notification(int p_what) { } Ref font = get_font("font"); + select_font(font); style->draw(ci, Rect2(Point2(), size)); diff --git a/scene/gui/link_button.cpp b/scene/gui/link_button.cpp index 7369a07eed6..18ce50781c0 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -128,6 +128,7 @@ void LinkButton::_notification(int p_what) { } Ref font = get_font("font"); + select_font(font); draw_string(font, Vector2(0, font->get_ascent()), xl_text, color); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 0dfd56cdd8a..81229f6ecc4 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -466,6 +466,8 @@ void PopupMenu::_draw_items() { Ref style = get_stylebox("panel"); Ref hover = get_stylebox("hover"); Ref font = get_font("font"); + select_font(font); + // In Item::checkable_type enum order (less the non-checkable member) Ref check[] = { get_icon("checked"), get_icon("radio_checked") }; Ref uncheck[] = { get_icon("unchecked"), get_icon("radio_unchecked") }; diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 9b3f18e1365..c3826f8c826 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -52,6 +52,7 @@ void ProgressBar::_notification(int p_what) { Ref bg = get_stylebox("bg"); Ref fg = get_stylebox("fg"); Ref font = get_font("font"); + select_font(font); Color font_color = get_color("font_color"); draw_style_box(bg, Rect2(Point2(), get_size())); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 0e57d4e759d..e22a799b48d 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -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 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"); diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 7c403608426..7b36c54cc19 100644 --- a/scene/gui/tab_container.cpp +++ b/scene/gui/tab_container.cpp @@ -279,6 +279,7 @@ void TabContainer::_notification(int p_what) { Ref menu = get_icon("menu"); Ref menu_hl = get_icon("menu_highlight"); Ref 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"); diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp index 1ca13e13d56..0205a894cd2 100644 --- a/scene/gui/tabs.cpp +++ b/scene/gui/tabs.cpp @@ -243,6 +243,7 @@ void Tabs::_notification(int p_what) { Ref tab_fg = get_stylebox("tab_fg"); Ref tab_disabled = get_stylebox("tab_disabled"); Ref 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");