Browse Source

Merge pull request #67626 from bruvzg/improve_font_packing_and_delay_texture_update_3

[3.x] Improve font glyph cache packing and texture update.
Rémi Verschelde 2 năm trước cách đây
mục cha
commit
e0682f6d8f
2 tập tin đã thay đổi với 127 bổ sung77 xóa
  1. 50 66
      scene/resources/dynamic_font.cpp
  2. 77 11
      scene/resources/dynamic_font.h

+ 50 - 66
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<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.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<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;

+ 77 - 11
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<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;
@@ -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;