Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.x] Improve font glyph cache packing and texture update. #67626

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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