From 5ec21ff5f1468b052d104bd84e5ae07b02a3afe8 Mon Sep 17 00:00:00 2001 From: kohad1ck <163426863+kohad1ck@users.noreply.github.com> Date: Mon, 26 Oct 2026 16:47:07 +0800 Subject: [PATCH] Font: menus keep their run-time glyphs when the renderer rebuilds its atlas Menu text drawn with characters the bitmap sheets lack (Chinese, Japanese, Korean) is rasterized into a reserve of the shared atlas at run time, and the font caches the atlas rect it got. The loading screen hands over to the game about two seconds after the title shows by building a new renderer, whose atlas texture is made from HudAtlas.pixels alone: those never hold a run-time glyph, while the cache still answers with the old rects, so every such character already drawn went blank. Latin text, straight from the sheets, stayed. build_renderer and loading_renderer now forget the cache before the new texture exists, so the characters that are drawn again are placed into it, as ui_theme clears its own cache when the atlas changes. Fixes #1824, fixes #2040. --- crates/horizon-client/src/font.rs | 25 +++++++++++++++++++++++++ crates/horizon-client/src/game.rs | 14 ++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/crates/horizon-client/src/font.rs b/crates/horizon-client/src/font.rs index 1903cab7e..d0e8c8499 100644 --- a/crates/horizon-client/src/font.rs +++ b/crates/horizon-client/src/font.rs @@ -1201,6 +1201,31 @@ mod tests { assert!(!font.take_patches().is_empty()); } + /// A renderer rebuilt over the same font (the loading screen's handoff) + /// makes its atlas texture from the textures alone, so the glyphs it had + /// rasterized into the previous texture must be placed into the new one. + #[test] + fn a_rebuilt_atlas_gets_its_glyphs_again() { + let sheet = png(16, 8, |x, _| x < 2 || (8..14).contains(&x)); + let default = br#"{"providers":[{"type":"bitmap","file":"minecraft:font/a.png","ascent":7,"chars":["ab"]}]}"#; + let root = root( + "rebuild", + &[ + ("assets/minecraft/font/default.json", default), + ("assets/minecraft/textures/font/a.png", &sheet), + ], + ); + let font = load(&root); + let _ = std::fs::remove_dir_all(&root); + let first = font.glyph('a'); + assert_eq!(font.take_patches().len(), 1); + font.reset_cache(); + // Nothing is left over: drawing the character again places its pixels. + assert!(font.take_patches().is_empty()); + assert_eq!(font.glyph('a').advance, first.advance); + assert_eq!(font.take_patches().len(), 1); + } + #[test] fn provider_filters_follow_the_font_options() { let sheet = png(8, 8, |x, _| x < 4); diff --git a/crates/horizon-client/src/game.rs b/crates/horizon-client/src/game.rs index cdbd72d7b..1bd288bb7 100644 --- a/crates/horizon-client/src/game.rs +++ b/crates/horizon-client/src/game.rs @@ -12995,6 +12995,14 @@ fn build_renderer( ) -> Result { let assets = &pack.render; let atlas = &gui.atlas; + // The new renderer's atlas texture starts from `HudAtlas.pixels`, which + // holds the textures but never a glyph rasterized at run time. The font's + // cache still answers with the rects it drew into the previous renderer's + // texture, so forget them: the glyphs that are drawn again are placed into + // this texture (as `ui_theme` does when its atlas changes). Without this, + // Chinese and Japanese menu labels go blank when the loading screen hands + // over to the game. + atlas.font.reset_cache(); let mut renderer = pollster::block_on(Renderer::new( window, offscreen, @@ -13041,6 +13049,12 @@ fn loading_renderer( Some(atlas) => (atlas.pixels.as_slice(), atlas.width, atlas.height), None => (EMPTY_HUD.as_slice(), 1, 1), }; + // A glyph rasterized into an earlier texture is not in these pixels; the + // new texture gets it again when the character is drawn (see + // `build_renderer`). + if let Some(atlas) = atlas { + atlas.font.reset_cache(); + } let mut renderer = pollster::block_on(Renderer::new( window, offscreen,