perf(text): applying preferences no longer waits for the system font scan (#4519) (#4679)

Session::apply_prefs set the shared text engine's missing-glyph
protection through photocraft_text::shared(), which blocks until the
system font scan that launch starts in the background is done. It runs
on the UI thread at launch and on every preferences change, so the first
frame waited for the rest of the scan: 248–257 ms on this machine
(release, launch order replayed), more with many fonts installed.

set_shared_missing_glyph_protection applies it at once when the engine
is built, and otherwise from one helper thread once the scan finishes.
Both read the newest setting under the engine's lock, so a late change
is never overwritten by an older one. Measured the same way: 0.1 ms.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
FinnNk 2026-10-13 06:11:31 +01:00 committed by GitHub
parent 5cff9ebed2
commit 233b6c63f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 54 additions and 3 deletions

View File

@ -1509,9 +1509,8 @@ impl Session {
st.history.trim(&st.doc);
}
photocraft_compose::set_effect_cache_budget(budget << 20);
if let Ok(mut engine) = photocraft_text::shared().lock() {
engine.fonts.set_missing_glyph_protection(self.prefs().type_.missing_glyph_protection());
}
// Without waiting for the system font scan: this runs on the UI thread at launch.
photocraft_text::set_shared_missing_glyph_protection(self.prefs().type_.missing_glyph_protection());
crate::plugin_cmds::sync_prefs(self);
}

View File

@ -167,6 +167,43 @@ pub fn try_shared() -> Option<&'static std::sync::Mutex<TextEngine>> {
SHARED.try_get(new_shared, uses_system_fonts())
}
/// Preferences › Type › Enable Missing Glyph Protection for [`shared`]: 0 = not set yet,
/// 1 = off, 2 = on. Read under the engine's lock, so the newest setting always wins.
static WANT_GLYPH_PROTECTION: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(0);
/// A thread is already waiting for the font scan to apply [`WANT_GLYPH_PROTECTION`].
static GLYPH_PROTECTION_WAITING: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
fn apply_glyph_protection(engine: &std::sync::Mutex<TextEngine>) {
let mut e = engine.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
match WANT_GLYPH_PROTECTION.load(std::sync::atomic::Ordering::SeqCst) {
1 => e.fonts.set_missing_glyph_protection(false),
2 => e.fonts.set_missing_glyph_protection(true),
_ => {}
}
}
/// Sets [`shared`]'s missing-glyph protection without waiting for the system font scan (#4519):
/// applied now when the engine is built, otherwise by a helper thread once the scan finishes.
/// Called from the UI thread at launch and on every preferences change, where [`shared`] would
/// block until the scan is done.
pub fn set_shared_missing_glyph_protection(on: bool) {
WANT_GLYPH_PROTECTION.store(if on { 2 } else { 1 }, std::sync::atomic::Ordering::SeqCst);
if let Some(engine) = try_shared() {
apply_glyph_protection(engine);
return;
}
if GLYPH_PROTECTION_WAITING.swap(true, std::sync::atomic::Ordering::SeqCst) {
// The waiting thread reads the newest setting when it applies it.
return;
}
let spawned = std::thread::Builder::new().name("photocraft-glyph-protection".into()).spawn(|| apply_glyph_protection(shared()));
if spawned.is_err() {
// No thread: apply it here, as before.
GLYPH_PROTECTION_WAITING.store(false, std::sync::atomic::Ordering::SeqCst);
apply_glyph_protection(shared());
}
}
/// Starts building [`shared`] (the system font scan) on a background thread, so the Type tool's
/// font menus don't stall the UI when they first draw. Call once at startup; calling again does
/// nothing.

View File

@ -1838,6 +1838,21 @@ fn missing_glyph_protection_can_turn_the_fallback_stack_off() {
assert!(db.fallback_stack().any(|f| f == "Cairo"), "turning it back on restores the coverage fonts");
}
/// The shared engine's missing-glyph protection follows the setting without waiting for the
/// system font scan (#4519). Under test the shared engine has no scan, so it applies at once;
/// the newest setting wins.
#[test]
fn shared_missing_glyph_protection_follows_the_setting() {
let read = || crate::shared().lock().unwrap_or_else(std::sync::PoisonError::into_inner).fonts.missing_glyph_protection();
crate::set_shared_missing_glyph_protection(false);
assert!(!read());
crate::set_shared_missing_glyph_protection(true);
crate::set_shared_missing_glyph_protection(false);
assert!(!read(), "the newest setting wins");
crate::set_shared_missing_glyph_protection(true);
assert!(read());
}
#[test]
fn translate_tysh_moves_only_the_translation() {
let mut t = point("Hello!", 26.0);