[3.x] Improve font glyph cache packing shelf best height fit heuristic, delay font texture update, until texture is accessed.

This commit is contained in:
bruvzg 2022-10-19 14:31:41 +03:00
parent 71f5bb3916
commit 46e2e0f62e
No known key found for this signature in database
GPG key ID: 7960FCF39844EC38
2 changed files with 131 additions and 81 deletions

View file

@ -328,6 +328,7 @@ void DynamicFontAtSize::set_texture_flags(uint32_t p_flags) {
texture_flags = p_flags; texture_flags = p_flags;
for (int i = 0; i < textures.size(); i++) { for (int i = 0; i < textures.size(); i++) {
Ref<ImageTexture> &tex = textures.write[i].texture; Ref<ImageTexture> &tex = textures.write[i].texture;
textures.write[i].dirty = true;
if (!tex.is_null()) { if (!tex.is_null()) {
tex->set_flags(p_flags); 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()); ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= font->textures.size(), RID());
if (ch->texture_idx != -1) { if (ch->texture_idx != -1) {
if (font->textures[ch->texture_idx].dirty) {
ShelfPackTexture &tex = font->textures.write[ch->texture_idx];
Ref<Image> 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(); 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()); ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= font->textures.size(), Size2());
if (ch->texture_idx != -1) { if (ch->texture_idx != -1) {
if (font->textures[ch->texture_idx].dirty) {
ShelfPackTexture &tex = font->textures.write[ch->texture_idx];
Ref<Image> 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(); 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); ERR_FAIL_COND_V(ch->texture_idx < -1 || ch->texture_idx >= font->textures.size(), 0);
if (!p_advance_only && ch->texture_idx != -1) { if (!p_advance_only && ch->texture_idx != -1) {
if (font->textures[ch->texture_idx].dirty) {
ShelfPackTexture &tex = font->textures.write[ch->texture_idx];
Ref<Image> 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; Point2 cpos = p_pos;
cpos.x += ch->h_align; cpos.x += ch->h_align;
cpos.y -= font->get_ascent(); cpos.y -= font->get_ascent();
@ -595,57 +629,29 @@ DynamicFontAtSize::Character DynamicFontAtSize::Character::not_found() {
return ch; 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) { DynamicFontAtSize::FontTexturePosition DynamicFontAtSize::_find_texture_pos_for_glyph(int p_color_size, Image::Format p_image_format, int p_width, int p_height) {
TexturePosition ret; FontTexturePosition ret;
ret.index = -1;
ret.x = 0;
ret.y = 0;
int mw = p_width; int mw = p_width;
int mh = p_height; int mh = p_height;
for (int i = 0; i < textures.size(); i++) { ShelfPackTexture *ct = textures.ptrw();
const CharTexture &ct = textures[i]; for (int32_t i = 0; i < textures.size(); i++) {
if (ct[i].format != p_image_format) {
if (ct.texture->get_format() != p_image_format) { continue;
}
if (mw > ct[i].texture_size || mh > ct[i].texture_size) { // Too big for this texture.
continue; continue;
} }
if (mw > ct.texture_size || mh > ct.texture_size) { //too big for this texture ret = ct[i].pack_rect(i, mh, mw);
continue; if (ret.index != -1) {
break;
} }
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.index = i;
break;
} }
if (ret.index == -1) { if (ret.index == -1) {
//could not find texture to fit, create one //could not find texture to fit, create one
ret.x = 0;
ret.y = 0;
int texsize = MAX(id.size * oversampling * 8, 256); int texsize = MAX(id.size * oversampling * 8, 256);
if (mw > texsize) { if (mw > texsize) {
@ -659,8 +665,8 @@ DynamicFontAtSize::TexturePosition DynamicFontAtSize::_find_texture_pos_for_glyp
texsize = MIN(texsize, 4096); texsize = MIN(texsize, 4096);
CharTexture tex; ShelfPackTexture tex = ShelfPackTexture(texsize);
tex.texture_size = texsize; tex.format = p_image_format;
tex.imgdata.resize(texsize * texsize * p_color_size); //grayscale alpha 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); 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; 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; 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; 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()); ERR_FAIL_COND_V(tex_pos.index < 0, Character::not_found());
//fit character in char texture //fit character in char texture
CharTexture &tex = textures.write[tex_pos.index]; ShelfPackTexture &tex = textures.write[tex_pos.index];
tex.dirty = true;
{ {
PoolVector<uint8_t>::Write wr = tex.imgdata.write(); PoolVector<uint8_t>::Write wr = tex.imgdata.write();
@ -750,24 +752,6 @@ DynamicFontAtSize::Character DynamicFontAtSize::_bitmap_to_character(FT_Bitmap b
} }
} }
//blit to image and texture
{
Ref<Image> 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; Character chr;
chr.h_align = xofs * scale_color_font / oversampling; chr.h_align = xofs * scale_color_font / oversampling;
chr.v_align = ascent - (yofs * scale_color_font / oversampling); // + ascent - descent; chr.v_align = ascent - (yofs * scale_color_font / oversampling); // + ascent - descent;

View file

@ -134,14 +134,86 @@ class DynamicFontAtSize : public Reference {
bool valid; bool valid;
struct CharTexture { struct FontTexturePosition {
PoolVector<uint8_t> imgdata; int32_t index = -1;
int texture_size; int32_t x = 0;
Vector<int> offsets; int32_t y = 0;
Ref<ImageTexture> texture;
FontTexturePosition() {}
FontTexturePosition(int32_t p_id, int32_t p_x, int32_t p_y) :
index(p_id), x(p_x), y(p_y) {}
}; };
Vector<CharTexture> textures; 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<uint8_t> imgdata;
Ref<ImageTexture> texture;
List<Shelf> 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<Shelf>::Element *best_shelf = nullptr;
int32_t best_waste = std::numeric_limits<std::int32_t>::max();
for (List<Shelf>::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<Shelf>::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<ShelfPackTexture> textures;
struct Character { struct Character {
bool found; bool found;
@ -160,16 +232,10 @@ class DynamicFontAtSize : public Reference {
static Character not_found(); static Character not_found();
}; };
struct TexturePosition {
int index;
int x;
int y;
};
const Pair<const Character *, DynamicFontAtSize *> _find_char_with_font(int32_t p_char, const Vector<Ref<DynamicFontAtSize>> &p_fallbacks) const; const Pair<const Character *, DynamicFontAtSize *> _find_char_with_font(int32_t p_char, const Vector<Ref<DynamicFontAtSize>> &p_fallbacks) const;
Character _make_outline_char(int32_t p_char); Character _make_outline_char(int32_t p_char);
float _get_kerning_advance(const DynamicFontAtSize *font, int32_t p_char, int32_t p_next) const; 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); Character _bitmap_to_character(FT_Bitmap bitmap, int yofs, int xofs, float advance);
HashMap<int32_t, Character> char_map; HashMap<int32_t, Character> char_map;