Skip to content

Commit

Permalink
Merge pull request #67626 from bruvzg/improve_font_packing_and_delay_…
Browse files Browse the repository at this point in the history
…texture_update_3

[3.x] Improve font glyph cache packing and texture update.
  • Loading branch information
akien-mga committed Nov 3, 2022
2 parents 45b1a5c + 46e2e0f commit e0682f6
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 77 deletions.
116 changes: 50 additions & 66 deletions scene/resources/dynamic_font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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) {
Expand All @@ -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

{
Expand All @@ -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;
Expand All @@ -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();

Expand Down Expand Up @@ -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;
Expand Down
88 changes: 77 additions & 11 deletions scene/resources/dynamic_font.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint8_t> imgdata;
int texture_size;
Vector<int> offsets;
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<CharTexture> textures;
Vector<ShelfPackTexture> textures;

struct Character {
bool found;
Expand All @@ -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;
Expand Down

0 comments on commit e0682f6

Please sign in to comment.