From 46e2e0f62e881f8d741e93e4acf75e0d6d73b625 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed, 19 Oct 2022 14:31:41 +0300 Subject: [PATCH] [3.x] Improve font glyph cache packing shelf best height fit heuristic, delay font texture update, until texture is accessed. --- scene/resources/dynamic_font.cpp | 116 +++++++++++++------------------ scene/resources/dynamic_font.h | 88 ++++++++++++++++++++--- 2 files changed, 127 insertions(+), 77 deletions(-) diff --git a/scene/resources/dynamic_font.cpp b/scene/resources/dynamic_font.cpp index 01f571989c6c..4bf2074501df 100644 --- a/scene/resources/dynamic_font.cpp +++ b/scene/resources/dynamic_font.cpp @@ -328,6 +328,7 @@ void DynamicFontAtSize::set_texture_flags(uint32_t p_flags) { texture_flags = p_flags; for (int i = 0; i < textures.size(); i++) { Ref &tex = textures.write[i].texture; + textures.write[i].dirty = true; if (!tex.is_null()) { tex->set_flags(p_flags); } @@ -358,6 +359,17 @@ RID DynamicFontAtSize::get_char_texture(CharType p_char, CharType p_next, const ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= font->textures.size(), RID()); if (ch->texture_idx != -1) { + if (font->textures[ch->texture_idx].dirty) { + ShelfPackTexture &tex = font->textures.write[ch->texture_idx]; + Ref img = memnew(Image(tex.texture_size, tex.texture_size, 0, tex.format, tex.imgdata)); + if (tex.texture.is_null()) { + tex.texture.instance(); + tex.texture->create_from_image(img, Texture::FLAG_VIDEO_SURFACE | texture_flags); + } else { + tex.texture->set_data(img); //update + } + tex.dirty = false; + } return font->textures[ch->texture_idx].texture->get_rid(); } } @@ -388,6 +400,17 @@ Size2 DynamicFontAtSize::get_char_texture_size(CharType p_char, CharType p_next, ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= font->textures.size(), Size2()); if (ch->texture_idx != -1) { + if (font->textures[ch->texture_idx].dirty) { + ShelfPackTexture &tex = font->textures.write[ch->texture_idx]; + Ref img = memnew(Image(tex.texture_size, tex.texture_size, 0, tex.format, tex.imgdata)); + if (tex.texture.is_null()) { + tex.texture.instance(); + tex.texture->create_from_image(img, Texture::FLAG_VIDEO_SURFACE | texture_flags); + } else { + tex.texture->set_data(img); //update + } + tex.dirty = false; + } return font->textures[ch->texture_idx].texture->get_size(); } } @@ -518,6 +541,17 @@ float DynamicFontAtSize::draw_char(RID p_canvas_item, const Point2 &p_pos, CharT ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= font->textures.size(), 0); if (!p_advance_only && ch->texture_idx != -1) { + if (font->textures[ch->texture_idx].dirty) { + ShelfPackTexture &tex = font->textures.write[ch->texture_idx]; + Ref img = memnew(Image(tex.texture_size, tex.texture_size, 0, tex.format, tex.imgdata)); + if (tex.texture.is_null()) { + tex.texture.instance(); + tex.texture->create_from_image(img, Texture::FLAG_VIDEO_SURFACE | texture_flags); + } else { + tex.texture->set_data(img); //update + } + tex.dirty = false; + } Point2 cpos = p_pos; cpos.x += ch->h_align; cpos.y -= font->get_ascent(); @@ -595,57 +629,29 @@ DynamicFontAtSize::Character DynamicFontAtSize::Character::not_found() { return ch; } -DynamicFontAtSize::TexturePosition DynamicFontAtSize::_find_texture_pos_for_glyph(int p_color_size, Image::Format p_image_format, int p_width, int p_height) { - TexturePosition ret; - ret.index = -1; - ret.x = 0; - ret.y = 0; +DynamicFontAtSize::FontTexturePosition DynamicFontAtSize::_find_texture_pos_for_glyph(int p_color_size, Image::Format p_image_format, int p_width, int p_height) { + FontTexturePosition ret; int mw = p_width; int mh = p_height; - for (int i = 0; i < textures.size(); i++) { - const CharTexture &ct = textures[i]; - - if (ct.texture->get_format() != p_image_format) { + ShelfPackTexture *ct = textures.ptrw(); + for (int32_t i = 0; i < textures.size(); i++) { + if (ct[i].format != p_image_format) { continue; } - - if (mw > ct.texture_size || mh > ct.texture_size) { //too big for this texture + if (mw > ct[i].texture_size || mh > ct[i].texture_size) { // Too big for this texture. continue; } - ret.y = 0x7FFFFFFF; - ret.x = 0; - - for (int j = 0; j < ct.texture_size - mw; j++) { - int max_y = 0; - - for (int k = j; k < j + mw; k++) { - int y = ct.offsets[k]; - if (y > max_y) { - max_y = y; - } - } - - if (max_y < ret.y) { - ret.y = max_y; - ret.x = j; - } - } - - if (ret.y == 0x7FFFFFFF || ret.y + mh > ct.texture_size) { - continue; //fail, could not fit it here + ret = ct[i].pack_rect(i, mh, mw); + if (ret.index != -1) { + break; } - - ret.index = i; - break; } if (ret.index == -1) { //could not find texture to fit, create one - ret.x = 0; - ret.y = 0; int texsize = MAX(id.size * oversampling * 8, 256); if (mw > texsize) { @@ -659,8 +665,8 @@ DynamicFontAtSize::TexturePosition DynamicFontAtSize::_find_texture_pos_for_glyp texsize = MIN(texsize, 4096); - CharTexture tex; - tex.texture_size = texsize; + ShelfPackTexture tex = ShelfPackTexture(texsize); + tex.format = p_image_format; tex.imgdata.resize(texsize * texsize * p_color_size); //grayscale alpha { @@ -684,13 +690,9 @@ DynamicFontAtSize::TexturePosition DynamicFontAtSize::_find_texture_pos_for_glyp } } } - tex.offsets.resize(texsize); - for (int i = 0; i < texsize; i++) { //zero offsets - tex.offsets.write[i] = 0; - } - textures.push_back(tex); - ret.index = textures.size() - 1; + int32_t idx = textures.size() - 1; + ret = textures.write[idx].pack_rect(idx, mh, mw); } return ret; @@ -709,13 +711,13 @@ DynamicFontAtSize::Character DynamicFontAtSize::_bitmap_to_character(FT_Bitmap b int color_size = bitmap.pixel_mode == FT_PIXEL_MODE_BGRA ? 4 : 2; Image::Format require_format = color_size == 4 ? Image::FORMAT_RGBA8 : Image::FORMAT_LA8; - TexturePosition tex_pos = _find_texture_pos_for_glyph(color_size, require_format, mw, mh); + FontTexturePosition tex_pos = _find_texture_pos_for_glyph(color_size, require_format, mw, mh); ERR_FAIL_COND_V(tex_pos.index < 0, Character::not_found()); //fit character in char texture - CharTexture &tex = textures.write[tex_pos.index]; - + ShelfPackTexture &tex = textures.write[tex_pos.index]; + tex.dirty = true; { PoolVector::Write wr = tex.imgdata.write(); @@ -750,24 +752,6 @@ DynamicFontAtSize::Character DynamicFontAtSize::_bitmap_to_character(FT_Bitmap b } } - //blit to image and texture - { - Ref img = memnew(Image(tex.texture_size, tex.texture_size, 0, require_format, tex.imgdata)); - - if (tex.texture.is_null()) { - tex.texture.instance(); - tex.texture->create_from_image(img, Texture::FLAG_VIDEO_SURFACE | texture_flags); - } else { - tex.texture->set_data(img); //update - } - } - - // update height array - - for (int k = tex_pos.x; k < tex_pos.x + mw; k++) { - tex.offsets.write[k] = tex_pos.y + mh; - } - Character chr; chr.h_align = xofs * scale_color_font / oversampling; chr.v_align = ascent - (yofs * scale_color_font / oversampling); // + ascent - descent; diff --git a/scene/resources/dynamic_font.h b/scene/resources/dynamic_font.h index 0e80fcc9434b..63a7da62b11d 100644 --- a/scene/resources/dynamic_font.h +++ b/scene/resources/dynamic_font.h @@ -134,14 +134,86 @@ class DynamicFontAtSize : public Reference { bool valid; - struct CharTexture { + struct FontTexturePosition { + int32_t index = -1; + int32_t x = 0; + int32_t y = 0; + + FontTexturePosition() {} + FontTexturePosition(int32_t p_id, int32_t p_x, int32_t p_y) : + index(p_id), x(p_x), y(p_y) {} + }; + + struct Shelf { + int32_t x = 0; + int32_t y = 0; + int32_t w = 0; + int32_t h = 0; + + FontTexturePosition alloc_shelf(int32_t p_id, int32_t p_w, int32_t p_h) { + if (p_w > w || p_h > h) { + return FontTexturePosition(-1, 0, 0); + } + int32_t xx = x; + x += p_w; + w -= p_w; + return FontTexturePosition(p_id, xx, y); + } + + Shelf() {} + Shelf(int32_t p_x, int32_t p_y, int32_t p_w, int32_t p_h) : + x(p_x), y(p_y), w(p_w), h(p_h) {} + }; + + struct ShelfPackTexture { + int32_t texture_size = 1024; PoolVector imgdata; - int texture_size; - Vector offsets; Ref texture; + List shelves; + Image::Format format; + bool dirty = true; + + FontTexturePosition pack_rect(int32_t p_id, int32_t p_h, int32_t p_w) { + int32_t y = 0; + int32_t waste = 0; + List::Element *best_shelf = nullptr; + int32_t best_waste = std::numeric_limits::max(); + + for (List::Element *E = shelves.front(); E; E = E->next()) { + y += E->get().h; + if (p_w > E->get().w) { + continue; + } + if (p_h == E->get().h) { + return E->get().alloc_shelf(p_id, p_w, p_h); + } + if (p_h > E->get().h) { + continue; + } + if (p_h < E->get().h) { + waste = (E->get().h - p_h) * p_w; + if (waste < best_waste) { + best_waste = waste; + best_shelf = E; + } + } + } + if (best_shelf) { + return best_shelf->get().alloc_shelf(p_id, p_w, p_h); + } + if (p_h <= (texture_size - y) && p_w <= texture_size) { + List::Element *E = shelves.push_back(Shelf(0, y, texture_size, p_h)); + return E->get().alloc_shelf(p_id, p_w, p_h); + } + return FontTexturePosition(-1, 0, 0); + } + + ShelfPackTexture() {} + ShelfPackTexture(int32_t p_size) : + texture_size(p_size) {} }; - Vector textures; + Vector textures; struct Character { bool found; @@ -160,16 +232,10 @@ class DynamicFontAtSize : public Reference { static Character not_found(); }; - struct TexturePosition { - int index; - int x; - int y; - }; - const Pair _find_char_with_font(int32_t p_char, const Vector> &p_fallbacks) const; Character _make_outline_char(int32_t p_char); float _get_kerning_advance(const DynamicFontAtSize *font, int32_t p_char, int32_t p_next) const; - TexturePosition _find_texture_pos_for_glyph(int p_color_size, Image::Format p_image_format, int p_width, int p_height); + FontTexturePosition _find_texture_pos_for_glyph(int p_color_size, Image::Format p_image_format, int p_width, int p_height); Character _bitmap_to_character(FT_Bitmap bitmap, int yofs, int xofs, float advance); HashMap char_map;