From 8fbc88b7087170246d345e88c95c225e4d89b251 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 29 Jan 2024 10:06:57 +0100 Subject: [PATCH] Load embedded fonts directly from .rdata instead of cloning (#6932) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fonts we embed in Zed binary (Zed Sans & Zed Mono) weigh about 30Mb in total and we are cloning them several times during startup and loading of embedded assets (once explicitly in Zed and then under the hood in font-kit). Moreover, after loading we have at least 2 copies of each font in our program; one in .rdata and the other on the heap for use by font-kit. This commit does away with that distinction (we're no longer allocating the font data) and slightly relaxes the interface of `TextSystem::add_fonts` by expecting one to pass `Cow<[u8]>` instead of `Arc>`. Additionally, `AssetSource::get` now returns `Cow<'static, [u8]>` instead of `Cow<'self, [u8]>`; all existing implementations conform with that change. Note that this optimization takes effect only in Release builds, as the library we use for asset embedding - rust-embed - embeds the assets only in Release mode and in Dev builds it simply loads data from disk. Thus it returnsĀ `Cow<[u8]>` in it's interface. Therefore, we still copy that memory around in Dev builds, but that's not really an issue. This patch makes no assumptions about the build profile we're running under, that's just an intrinsic property of rust-embed. Tl;dr: this should shave off about 30Mb of memory usage and a fair chunk (~30ms) of startup time. Release Notes: - Improved startup time and memory usage. --- crates/assets/src/lib.rs | 2 +- crates/gpui/src/assets.rs | 4 +-- crates/gpui/src/platform.rs | 2 +- crates/gpui/src/platform/mac/text_system.rs | 27 +++++++++++++++------ crates/gpui/src/text_system.rs | 3 ++- crates/storybook/src/assets.rs | 2 +- crates/storybook/src/storybook.rs | 8 +++--- crates/theme_importer/src/assets.rs | 2 +- crates/zed/src/main.rs | 6 ++--- crates/zed/src/zed.rs | 14 +++-------- 10 files changed, 36 insertions(+), 34 deletions(-) diff --git a/crates/assets/src/lib.rs b/crates/assets/src/lib.rs index 010b7ebda3..4f013dd5af 100644 --- a/crates/assets/src/lib.rs +++ b/crates/assets/src/lib.rs @@ -16,7 +16,7 @@ use rust_embed::RustEmbed; pub struct Assets; impl AssetSource for Assets { - fn load(&self, path: &str) -> Result> { + fn load(&self, path: &str) -> Result> { Self::get(path) .map(|f| f.data) .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path)) diff --git a/crates/gpui/src/assets.rs b/crates/gpui/src/assets.rs index b5e3735eb5..e2667e67bb 100644 --- a/crates/gpui/src/assets.rs +++ b/crates/gpui/src/assets.rs @@ -11,14 +11,14 @@ use std::{ /// A source of assets for this app to use. pub trait AssetSource: 'static + Send + Sync { /// Load the given asset from the source path. - fn load(&self, path: &str) -> Result>; + fn load(&self, path: &str) -> Result>; /// List the assets at the given path. fn list(&self, path: &str) -> Result>; } impl AssetSource for () { - fn load(&self, path: &str) -> Result> { + fn load(&self, path: &str) -> Result> { Err(anyhow!( "get called on empty asset provider with \"{}\"", path diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index a7b71c7885..92dd08f059 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -204,7 +204,7 @@ pub trait PlatformDispatcher: Send + Sync { } pub(crate) trait PlatformTextSystem: Send + Sync { - fn add_fonts(&self, fonts: &[Arc>]) -> Result<()>; + fn add_fonts(&self, fonts: Vec>) -> Result<()>; fn all_font_names(&self) -> Vec; fn all_font_families(&self) -> Vec; fn font_id(&self, descriptor: &Font) -> Result; diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index e8c8b45311..053e5859be 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -34,7 +34,7 @@ use pathfinder_geometry::{ vector::{Vector2F, Vector2I}, }; use smallvec::SmallVec; -use std::{char, cmp, convert::TryFrom, ffi::c_void, sync::Arc}; +use std::{borrow::Cow, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc}; use super::open_type; @@ -74,7 +74,7 @@ impl Default for MacTextSystem { } impl PlatformTextSystem for MacTextSystem { - fn add_fonts(&self, fonts: &[Arc>]) -> Result<()> { + fn add_fonts(&self, fonts: Vec>) -> Result<()> { self.0.write().add_fonts(fonts) } @@ -183,12 +183,23 @@ impl PlatformTextSystem for MacTextSystem { } impl MacTextSystemState { - fn add_fonts(&mut self, fonts: &[Arc>]) -> Result<()> { - self.memory_source.add_fonts( - fonts - .iter() - .map(|bytes| Handle::from_memory(bytes.clone(), 0)), - )?; + fn add_fonts(&mut self, fonts: Vec>) -> Result<()> { + let fonts = fonts + .into_iter() + .map(|bytes| match bytes { + Cow::Borrowed(embedded_font) => { + let data_provider = unsafe { + core_graphics::data_provider::CGDataProvider::from_slice(embedded_font) + }; + let font = core_graphics::font::CGFont::from_data_provider(data_provider) + .map_err(|_| anyhow!("Could not load an embedded font."))?; + let font = font_kit::loaders::core_text::Font::from_core_graphics_font(font); + Ok(Handle::from_native(&font)) + } + Cow::Owned(bytes) => Ok(Handle::from_memory(Arc::new(bytes), 0)), + }) + .collect::>>()?; + self.memory_source.add_fonts(fonts.into_iter())?; Ok(()) } diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 15042cbf59..d5180e30c0 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -19,6 +19,7 @@ use itertools::Itertools; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use smallvec::{smallvec, SmallVec}; use std::{ + borrow::Cow, cmp, fmt::{Debug, Display, Formatter}, hash::{Hash, Hasher}, @@ -85,7 +86,7 @@ impl TextSystem { } /// Add a font's data to the text system. - pub fn add_fonts(&self, fonts: &[Arc>]) -> Result<()> { + pub fn add_fonts(&self, fonts: Vec>) -> Result<()> { self.platform_text_system.add_fonts(fonts) } diff --git a/crates/storybook/src/assets.rs b/crates/storybook/src/assets.rs index 9fc71917b4..c8dbe9ec60 100644 --- a/crates/storybook/src/assets.rs +++ b/crates/storybook/src/assets.rs @@ -15,7 +15,7 @@ use rust_embed::RustEmbed; pub struct Assets; impl AssetSource for Assets { - fn load(&self, path: &str) -> Result> { + fn load(&self, path: &str) -> Result> { Self::get(path) .map(|f| f.data) .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path)) diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index 70e830222f..47fc3b042a 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -2,8 +2,6 @@ mod assets; mod stories; mod story_selector; -use std::sync::Arc; - use clap::Parser; use dialoguer::FuzzySelect; use gpui::{ @@ -128,10 +126,10 @@ fn load_embedded_fonts(cx: &AppContext) -> gpui::Result<()> { let mut embedded_fonts = Vec::new(); for font_path in font_paths { if font_path.ends_with(".ttf") { - let font_bytes = cx.asset_source().load(&font_path)?.to_vec(); - embedded_fonts.push(Arc::from(font_bytes)); + let font_bytes = cx.asset_source().load(&font_path)?; + embedded_fonts.push(font_bytes); } } - cx.text_system().add_fonts(&embedded_fonts) + cx.text_system().add_fonts(embedded_fonts) } diff --git a/crates/theme_importer/src/assets.rs b/crates/theme_importer/src/assets.rs index 9009b4c144..171912f6b8 100644 --- a/crates/theme_importer/src/assets.rs +++ b/crates/theme_importer/src/assets.rs @@ -11,7 +11,7 @@ use rust_embed::RustEmbed; pub struct Assets; impl AssetSource for Assets { - fn load(&self, path: &str) -> Result> { + fn load(&self, path: &str) -> Result> { Self::get(path) .map(|f| f.data) .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path)) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 0b7a983cb6..3e546ca547 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -815,14 +815,14 @@ fn load_embedded_fonts(cx: &AppContext) { } scope.spawn(async { - let font_bytes = asset_source.load(font_path).unwrap().to_vec(); - embedded_fonts.lock().push(Arc::from(font_bytes)); + let font_bytes = asset_source.load(font_path).unwrap(); + embedded_fonts.lock().push(font_bytes); }); } })); cx.text_system() - .add_fonts(&embedded_fonts.into_inner()) + .add_fonts(embedded_fonts.into_inner()) .unwrap(); } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index bf001dac72..f045e62f83 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2676,17 +2676,9 @@ mod tests { #[gpui::test] fn test_bundled_settings_and_themes(cx: &mut AppContext) { cx.text_system() - .add_fonts(&[ - Assets - .load("fonts/zed-sans/zed-sans-extended.ttf") - .unwrap() - .to_vec() - .into(), - Assets - .load("fonts/zed-mono/zed-mono-extended.ttf") - .unwrap() - .to_vec() - .into(), + .add_fonts(vec![ + Assets.load("fonts/zed-sans/zed-sans-extended.ttf").unwrap(), + Assets.load("fonts/zed-mono/zed-mono-extended.ttf").unwrap(), ]) .unwrap(); let themes = ThemeRegistry::default();