From ae7409bf52884e92678e73ac673ac24cfa2976c6 Mon Sep 17 00:00:00 2001 From: Relintai Date: Thu, 8 Feb 2024 19:11:34 +0100 Subject: [PATCH] Ported: 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. - lawnjelly and Calinou https://github.com/godotengine/godot/commit/bc607fb6075cebf78d8e562d7b7423b2650906ac --- .../gles2/rasterizer_canvas_base_gles2.cpp | 1 + drivers/gles2/rasterizer_canvas_gles2.cpp | 19 +++++++++++++++ drivers/gles2/shaders/canvas.glsl | 10 +++++++- 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 + scene/main/canvas_item.cpp | 11 +++++++++ scene/main/canvas_item.h | 24 ++++++++++--------- 16 files changed, 65 insertions(+), 14 deletions(-) diff --git a/drivers/gles2/rasterizer_canvas_base_gles2.cpp b/drivers/gles2/rasterizer_canvas_base_gles2.cpp index ea8f53c63..d47f6b342 100644 --- a/drivers/gles2/rasterizer_canvas_base_gles2.cpp +++ b/drivers/gles2/rasterizer_canvas_base_gles2.cpp @@ -59,6 +59,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 d0c71d070..2ca5e562d 100644 --- a/drivers/gles2/rasterizer_canvas_gles2.cpp +++ b/drivers/gles2/rasterizer_canvas_gles2.cpp @@ -1222,6 +1222,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); @@ -1555,6 +1556,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; @@ -1570,6 +1577,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; @@ -1934,6 +1947,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 f112ee821..a1070c2c5 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/scene/gui/button.cpp b/scene/gui/button.cpp index a84ebc12d..aa17f28f3 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -287,6 +287,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 5e0ff8308..513273111 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -212,6 +212,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 8ec623ad7..87bccac28 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -769,6 +769,7 @@ void ItemList::_notification(int p_what) { Ref cursor = has_focus() ? get_theme_stylebox("cursor") : get_theme_stylebox("cursor_unfocused"); Ref font = get_theme_font("font"); + select_font(font); Color guide_color = get_theme_color("guide_color"); Color font_color = get_theme_color("font_color"); Color font_color_selected = get_theme_color("font_color_selected"); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index f24df3c9a..f54351709 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -91,6 +91,7 @@ void Label::_notification(int p_what) { Size2 size = get_size(); Ref style = get_theme_stylebox("normal"); Ref font = get_theme_font("font"); + select_font(font); Color font_color = get_theme_color("font_color"); Color font_color_shadow = get_theme_color("font_color_shadow"); bool use_outline = get_theme_constant("shadow_as_outline"); @@ -100,8 +101,6 @@ void Label::_notification(int p_what) { style->draw(ci, Rect2(Point2(0, 0), get_size())); - RenderingServer::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 b07b57ff8..2a70c6979 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -811,6 +811,7 @@ void LineEdit::_notification(int p_what) { } Ref font = get_theme_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 0ded5a485..ad51de42f 100644 --- a/scene/gui/link_button.cpp +++ b/scene/gui/link_button.cpp @@ -130,6 +130,7 @@ void LinkButton::_notification(int p_what) { } Ref font = get_theme_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 c222a3fe2..3110db1f2 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -494,6 +494,8 @@ void PopupMenu::_draw_items() { Ref style = get_theme_stylebox("panel"); Ref hover = get_theme_stylebox("hover"); Ref font = get_theme_font("font"); + select_font(font); + // In Item::checkable_type enum order (less the non-checkable member) Ref check[] = { get_theme_icon("checked"), get_theme_icon("radio_checked") }; Ref uncheck[] = { get_theme_icon("unchecked"), get_theme_icon("radio_unchecked") }; diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index e42f99c00..23a5a4577 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -53,6 +53,7 @@ void ProgressBar::_notification(int p_what) { Ref bg = get_theme_stylebox("bg"); Ref fg = get_theme_stylebox("fg"); Ref font = get_theme_font("font"); + select_font(font); Color font_color = get_theme_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 1f5e5110a..2353fa367 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -1078,6 +1078,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_theme_font("normal_font"); + select_font(base_font); Color base_color = get_theme_color("default_color"); Color font_color_shadow = get_theme_color("font_color_shadow"); bool use_outline = get_theme_constant("shadow_as_outline"); diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp index 44b12f68d..5fb1ea4cd 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_theme_icon("menu"); Ref menu_hl = get_theme_icon("menu_highlight"); Ref font = get_theme_font("font"); + select_font(font); Color font_color_fg = get_theme_color("font_color_fg"); Color font_color_bg = get_theme_color("font_color_bg"); Color font_color_disabled = get_theme_color("font_color_disabled"); diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp index c14f845c6..7bdc8299f 100644 --- a/scene/gui/tabs.cpp +++ b/scene/gui/tabs.cpp @@ -242,6 +242,7 @@ void Tabs::_notification(int p_what) { Ref tab_fg = get_theme_stylebox("tab_fg"); Ref tab_disabled = get_theme_stylebox("tab_disabled"); Ref font = get_theme_font("font"); + select_font(font); Color color_fg = get_theme_color("font_color_fg"); Color color_bg = get_theme_color("font_color_bg"); Color color_disabled = get_theme_color("font_color_disabled"); diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index 9ccdb8b97..00483e513 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -971,6 +971,16 @@ 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; + RenderingServer::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."); @@ -1359,6 +1369,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/main/canvas_item.h b/scene/main/canvas_item.h index a79d638b3..3660ada7a 100644 --- a/scene/main/canvas_item.h +++ b/scene/main/canvas_item.h @@ -189,21 +189,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); @@ -332,6 +333,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));