[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:
parent
71f5bb3916
commit
46e2e0f62e
2 changed files with 131 additions and 81 deletions
|
@ -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<ImageTexture> &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<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();
|
||||
}
|
||||
}
|
||||
|
@ -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<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();
|
||||
}
|
||||
}
|
||||
|
@ -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<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;
|
||||
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[i].texture_size || mh > ct[i].texture_size) { // Too big for this texture.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mw > ct.texture_size || mh > ct.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.index = i;
|
||||
ret = ct[i].pack_rect(i, mh, mw);
|
||||
if (ret.index != -1) {
|
||||
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<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;
|
||||
chr.h_align = xofs * scale_color_font / oversampling;
|
||||
chr.v_align = ascent - (yofs * scale_color_font / oversampling); // + ascent - descent;
|
||||
|
|
|
@ -134,14 +134,86 @@ class DynamicFontAtSize : public Reference {
|
|||
|
||||
bool valid;
|
||||
|
||||
struct CharTexture {
|
||||
PoolVector<uint8_t> imgdata;
|
||||
int texture_size;
|
||||
Vector<int> offsets;
|
||||
Ref<ImageTexture> texture;
|
||||
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) {}
|
||||
};
|
||||
|
||||
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 {
|
||||
bool found;
|
||||
|
@ -160,16 +232,10 @@ class DynamicFontAtSize : public Reference {
|
|||
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;
|
||||
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<int32_t, Character> char_map;
|
||||
|
|
Loading…
Reference in a new issue