From 6b629dfa5cecd0b866292de137145bd4edc6974f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 12 Apr 2022 19:37:26 +0200 Subject: [PATCH 1/6] Use `CTFont::draw_glyphs` to rasterize glyphs This API supports rendering emojis in addition to normal glyphs. --- crates/gpui/src/platform/mac/fonts.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/crates/gpui/src/platform/mac/fonts.rs b/crates/gpui/src/platform/mac/fonts.rs index 1706134f6709af5b1915f19674b1d41d3ac69e7a..23639fa077d4b4f5d01afd6956494dd319c047de 100644 --- a/crates/gpui/src/platform/mac/fonts.rs +++ b/crates/gpui/src/platform/mac/fonts.rs @@ -198,15 +198,18 @@ impl FontSystemState { ty: -transform.vector.y() as CGFloat, }); - cx.set_font(&font.native_font().copy_to_CGFont()); - cx.set_font_size(font_size as CGFloat); - cx.show_glyphs_at_positions( - &[glyph_id as CGGlyph], - &[CGPoint::new( - (subpixel_shift.x() / scale_factor) as CGFloat, - (subpixel_shift.y() / scale_factor) as CGFloat, - )], - ); + cx.set_should_subpixel_position_fonts(true); + cx.set_should_subpixel_quantize_fonts(false); + font.native_font() + .clone_with_font_size(font_size as CGFloat) + .draw_glyphs( + &[glyph_id as CGGlyph], + &[CGPoint::new( + (subpixel_shift.x() / scale_factor) as CGFloat, + (subpixel_shift.y() / scale_factor) as CGFloat, + )], + cx, + ); Some((bounds, pixels)) } From 6cc9306f0077d4605805fb6cf24f846608aeb4aa Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 Apr 2022 11:36:54 +0200 Subject: [PATCH 2/6] Make room for font subpixel variants correctly --- crates/gpui/src/platform/mac/fonts.rs | 30 ++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/crates/gpui/src/platform/mac/fonts.rs b/crates/gpui/src/platform/mac/fonts.rs index 23639fa077d4b4f5d01afd6956494dd319c047de..cf0f0c8b27747430d402dc284c83cc2747e5db79 100644 --- a/crates/gpui/src/platform/mac/fonts.rs +++ b/crates/gpui/src/platform/mac/fonts.rs @@ -159,7 +159,7 @@ impl FontSystemState { ) -> Option<(RectI, Vec)> { let font = &self.fonts[font_id.0]; let scale = Transform2F::from_scale(scale_factor); - let bounds = font + let glyph_bounds = font .raster_bounds( glyph_id, font_size, @@ -169,26 +169,26 @@ impl FontSystemState { ) .ok()?; - if bounds.width() == 0 || bounds.height() == 0 { + if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 { None } else { // Make room for subpixel variants. - let bounds = RectI::new(bounds.origin(), bounds.size() + vec2i(1, 1)); - let mut pixels = vec![0; bounds.width() as usize * bounds.height() as usize]; + let cx_bounds = RectI::new(glyph_bounds.origin(), glyph_bounds.size() + vec2i(1, 1)); + let mut bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize]; let cx = CGContext::create_bitmap_context( - Some(pixels.as_mut_ptr() as *mut _), - bounds.width() as usize, - bounds.height() as usize, + Some(bytes.as_mut_ptr() as *mut _), + cx_bounds.width() as usize, + cx_bounds.height() as usize, 8, - bounds.width() as usize, + cx_bounds.width() as usize, &CGColorSpace::create_device_gray(), kCGImageAlphaOnly, ); // Move the origin to bottom left and account for scaling, this // makes drawing text consistent with the font-kit's raster_bounds. - cx.translate(0.0, bounds.height() as CGFloat); - let transform = scale.translate(-bounds.origin().to_f32()); + cx.translate(0.0, glyph_bounds.height() as CGFloat); + let transform = Transform2F::from_translation(-glyph_bounds.origin().to_f32()); cx.set_text_matrix(&CGAffineTransform { a: transform.matrix.m11() as CGFloat, b: -transform.matrix.m21() as CGFloat, @@ -198,20 +198,22 @@ impl FontSystemState { ty: -transform.vector.y() as CGFloat, }); + cx.set_allows_font_subpixel_positioning(true); cx.set_should_subpixel_position_fonts(true); + cx.set_allows_font_subpixel_quantization(false); cx.set_should_subpixel_quantize_fonts(false); font.native_font() - .clone_with_font_size(font_size as CGFloat) + .clone_with_font_size((font_size * scale_factor) as CGFloat) .draw_glyphs( &[glyph_id as CGGlyph], &[CGPoint::new( - (subpixel_shift.x() / scale_factor) as CGFloat, - (subpixel_shift.y() / scale_factor) as CGFloat, + subpixel_shift.x() as CGFloat, + subpixel_shift.y() as CGFloat, )], cx, ); - Some((bounds, pixels)) + Some((cx_bounds, bytes)) } } From 4249b5687e83899084833e7855317ea54ab598c1 Mon Sep 17 00:00:00 2001 From: Keith Simmons Date: Tue, 12 Apr 2022 14:09:29 -0700 Subject: [PATCH 3/6] Implement font fallback in layout_line Co-authored-by: Max Brunsfeld --- crates/gpui/src/platform/mac/fonts.rs | 50 ++++++++++++++++----------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/crates/gpui/src/platform/mac/fonts.rs b/crates/gpui/src/platform/mac/fonts.rs index cf0f0c8b27747430d402dc284c83cc2747e5db79..8ea596369725bcf4083ccbb49220a4dc9012fd7e 100644 --- a/crates/gpui/src/platform/mac/fonts.rs +++ b/crates/gpui/src/platform/mac/fonts.rs @@ -13,13 +13,12 @@ use core_foundation::{ array::CFIndex, attributed_string::{CFAttributedStringRef, CFMutableAttributedString}, base::{CFRange, TCFType}, - number::CFNumber, string::CFString, }; use core_graphics::{ base::CGGlyph, color_space::CGColorSpace, context::CGContext, geometry::CGAffineTransform, }; -use core_text::{line::CTLine, string_attributes::kCTFontAttributeName}; +use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName}; use font_kit::{ canvas::RasterizationOptions, handle::Handle, hinting::HintingOptions, source::SystemSource, sources::mem::MemSource, @@ -91,7 +90,7 @@ impl platform::FontSystem for FontSystem { } fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout { - self.0.read().layout_line(text, font_size, runs) + self.0.write().layout_line(text, font_size, runs) } fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec { @@ -149,6 +148,19 @@ impl FontSystemState { self.fonts[font_id.0].glyph_for_char(ch) } + fn id_for_font(&mut self, requested_font: font_kit::font::Font) -> FontId { + // TODO: don't allocate the postscript name + // Note: Coretext always returns a Some option for postscript_name + let requested_font_name = requested_font.postscript_name(); + for (id, font) in self.fonts.iter().enumerate() { + if font.postscript_name() == requested_font_name { + return FontId(id); + } + } + self.fonts.push(requested_font); + FontId(self.fonts.len() - 1) + } + fn rasterize_glyph( &self, font_id: FontId, @@ -217,9 +229,12 @@ impl FontSystemState { } } - fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout { - let font_id_attr_name = CFString::from_static_string("zed_font_id"); - + fn layout_line( + &mut self, + text: &str, + font_size: f32, + runs: &[(usize, RunStyle)], + ) -> LineLayout { // Construct the attributed string, converting UTF8 ranges to UTF16 ranges. let mut string = CFMutableAttributedString::new(); { @@ -269,11 +284,6 @@ impl FontSystemState { kCTFontAttributeName, &font.native_font().clone_with_font_size(font_size as f64), ); - string.set_attribute( - cf_range, - font_id_attr_name.as_concrete_TypeRef(), - &CFNumber::from(font_id.0 as i64), - ); } if utf16_end == utf16_line_len { @@ -287,15 +297,15 @@ impl FontSystemState { let mut runs = Vec::new(); for run in line.glyph_runs().into_iter() { - let font_id = FontId( - run.attributes() - .unwrap() - .get(&font_id_attr_name) - .downcast::() - .unwrap() - .to_i64() - .unwrap() as usize, - ); + let attributes = run.attributes().unwrap(); + let font = unsafe { + let native_font = attributes + .get(kCTFontAttributeName) + .downcast::() + .unwrap(); + font_kit::font::Font::from_native_font(native_font) + }; + let font_id = self.id_for_font(font); let mut ix_converter = StringIndexConverter::new(text); let mut glyphs = Vec::new(); From fff1d9c63177896fe6f7d81ebf278df46aa70e25 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 Apr 2022 15:56:32 +0200 Subject: [PATCH 4/6] Implement glyph scaling in a way that works with emojis as well --- crates/gpui/src/platform/mac/fonts.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/crates/gpui/src/platform/mac/fonts.rs b/crates/gpui/src/platform/mac/fonts.rs index 8ea596369725bcf4083ccbb49220a4dc9012fd7e..31f39d5851249e407ea20a85a682ca4e0710edc4 100644 --- a/crates/gpui/src/platform/mac/fonts.rs +++ b/crates/gpui/src/platform/mac/fonts.rs @@ -199,28 +199,23 @@ impl FontSystemState { // Move the origin to bottom left and account for scaling, this // makes drawing text consistent with the font-kit's raster_bounds. - cx.translate(0.0, glyph_bounds.height() as CGFloat); - let transform = Transform2F::from_translation(-glyph_bounds.origin().to_f32()); - cx.set_text_matrix(&CGAffineTransform { - a: transform.matrix.m11() as CGFloat, - b: -transform.matrix.m21() as CGFloat, - c: -transform.matrix.m12() as CGFloat, - d: transform.matrix.m22() as CGFloat, - tx: transform.vector.x() as CGFloat, - ty: -transform.vector.y() as CGFloat, - }); + cx.translate( + -glyph_bounds.origin_x() as CGFloat, + (glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat, + ); + cx.scale(scale_factor as CGFloat, scale_factor as CGFloat); cx.set_allows_font_subpixel_positioning(true); cx.set_should_subpixel_position_fonts(true); cx.set_allows_font_subpixel_quantization(false); cx.set_should_subpixel_quantize_fonts(false); font.native_font() - .clone_with_font_size((font_size * scale_factor) as CGFloat) + .clone_with_font_size(font_size as CGFloat) .draw_glyphs( &[glyph_id as CGGlyph], &[CGPoint::new( - subpixel_shift.x() as CGFloat, - subpixel_shift.y() as CGFloat, + (subpixel_shift.x() / scale_factor) as CGFloat, + (subpixel_shift.y() / scale_factor) as CGFloat, )], cx, ); From cdcdccfb89d2eece1fe9ab69c7148a261e369643 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 Apr 2022 17:31:10 +0200 Subject: [PATCH 5/6] Render emojis Co-Authored-By: Nathan Sobo --- crates/gpui/src/platform.rs | 7 ++ crates/gpui/src/platform/mac/fonts.rs | 98 +++++++++++++++----- crates/gpui/src/platform/mac/image_cache.rs | 75 +++++++++++++-- crates/gpui/src/platform/mac/renderer.rs | 45 +++++++-- crates/gpui/src/platform/mac/sprite_cache.rs | 3 +- crates/gpui/src/scene.rs | 36 +++++-- crates/gpui/src/text_layout.rs | 47 +++++++--- 7 files changed, 255 insertions(+), 56 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 6afdfb4637ad7778e6fc086a2588a56df310cd76..9abff5b0f79b0b73df9c07f34f7f6cb8f6b8a1cf 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -129,6 +129,12 @@ pub enum CursorStyle { PointingHand, } +#[derive(Copy, Clone, Debug)] +pub enum RasterizationOptions { + Alpha, + Bgra, +} + pub trait FontSystem: Send + Sync { fn add_fonts(&self, fonts: &[Arc>]) -> anyhow::Result<()>; fn load_family(&self, name: &str) -> anyhow::Result>; @@ -148,6 +154,7 @@ pub trait FontSystem: Send + Sync { glyph_id: GlyphId, subpixel_shift: Vector2F, scale_factor: f32, + options: RasterizationOptions, ) -> Option<(RectI, Vec)>; fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout; fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec; diff --git a/crates/gpui/src/platform/mac/fonts.rs b/crates/gpui/src/platform/mac/fonts.rs index 31f39d5851249e407ea20a85a682ca4e0710edc4..417bfe6728abcad67526c40c4f238523ce698ed7 100644 --- a/crates/gpui/src/platform/mac/fonts.rs +++ b/crates/gpui/src/platform/mac/fonts.rs @@ -3,9 +3,9 @@ use crate::{ geometry::{ rect::{RectF, RectI}, transform2d::Transform2F, - vector::{vec2f, vec2i, Vector2F}, + vector::{vec2f, Vector2F}, }, - platform, + platform::{self, RasterizationOptions}, text_layout::{Glyph, LineLayout, Run, RunStyle}, }; use cocoa::appkit::{CGFloat, CGPoint}; @@ -16,12 +16,13 @@ use core_foundation::{ string::CFString, }; use core_graphics::{ - base::CGGlyph, color_space::CGColorSpace, context::CGContext, geometry::CGAffineTransform, + base::{kCGImageAlphaPremultipliedLast, CGGlyph}, + color_space::CGColorSpace, + context::CGContext, }; use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName}; use font_kit::{ - canvas::RasterizationOptions, handle::Handle, hinting::HintingOptions, source::SystemSource, - sources::mem::MemSource, + handle::Handle, hinting::HintingOptions, source::SystemSource, sources::mem::MemSource, }; use parking_lot::RwLock; use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc}; @@ -35,15 +36,19 @@ struct FontSystemState { memory_source: MemSource, system_source: SystemSource, fonts: Vec, + emoji_font_id: FontId, } impl FontSystem { pub fn new() -> Self { - Self(RwLock::new(FontSystemState { + let mut state = FontSystemState { memory_source: MemSource::empty(), system_source: SystemSource::new(), fonts: Vec::new(), - })) + emoji_font_id: FontId(0), // This will be the first font that we load. + }; + state.load_family("Apple Color Emoji").unwrap(); + Self(RwLock::new(state)) } } @@ -83,10 +88,16 @@ impl platform::FontSystem for FontSystem { glyph_id: GlyphId, subpixel_shift: Vector2F, scale_factor: f32, + options: RasterizationOptions, ) -> Option<(RectI, Vec)> { - self.0 - .read() - .rasterize_glyph(font_id, font_size, glyph_id, subpixel_shift, scale_factor) + self.0.read().rasterize_glyph( + font_id, + font_size, + glyph_id, + subpixel_shift, + scale_factor, + options, + ) } fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout { @@ -168,6 +179,7 @@ impl FontSystemState { glyph_id: GlyphId, subpixel_shift: Vector2F, scale_factor: f32, + options: RasterizationOptions, ) -> Option<(RectI, Vec)> { let font = &self.fonts[font_id.0]; let scale = Transform2F::from_scale(scale_factor); @@ -177,7 +189,7 @@ impl FontSystemState { font_size, scale, HintingOptions::None, - RasterizationOptions::GrayscaleAa, + font_kit::canvas::RasterizationOptions::GrayscaleAa, ) .ok()?; @@ -185,18 +197,41 @@ impl FontSystemState { None } else { // Make room for subpixel variants. - let cx_bounds = RectI::new(glyph_bounds.origin(), glyph_bounds.size() + vec2i(1, 1)); - let mut bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize]; - let cx = CGContext::create_bitmap_context( - Some(bytes.as_mut_ptr() as *mut _), - cx_bounds.width() as usize, - cx_bounds.height() as usize, - 8, - cx_bounds.width() as usize, - &CGColorSpace::create_device_gray(), - kCGImageAlphaOnly, + let subpixel_padding = subpixel_shift.ceil().to_i32(); + let cx_bounds = RectI::new( + glyph_bounds.origin(), + glyph_bounds.size() + subpixel_padding, ); + let mut bytes; + let cx; + match options { + RasterizationOptions::Alpha => { + bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize]; + cx = CGContext::create_bitmap_context( + Some(bytes.as_mut_ptr() as *mut _), + cx_bounds.width() as usize, + cx_bounds.height() as usize, + 8, + cx_bounds.width() as usize, + &CGColorSpace::create_device_gray(), + kCGImageAlphaOnly, + ); + } + RasterizationOptions::Bgra => { + bytes = vec![0; cx_bounds.width() as usize * 4 * cx_bounds.height() as usize]; + cx = CGContext::create_bitmap_context( + Some(bytes.as_mut_ptr() as *mut _), + cx_bounds.width() as usize, + cx_bounds.height() as usize, + 8, + cx_bounds.width() as usize * 4, + &CGColorSpace::create_device_rgb(), + kCGImageAlphaPremultipliedLast, + ); + } + } + // Move the origin to bottom left and account for scaling, this // makes drawing text consistent with the font-kit's raster_bounds. cx.translate( @@ -220,6 +255,17 @@ impl FontSystemState { cx, ); + if let RasterizationOptions::Bgra = options { + // Convert from RGBA with premultiplied alpha to BGRA with straight alpha. + for pixel in bytes.chunks_exact_mut(4) { + pixel.swap(0, 2); + let a = pixel[3] as f32 / 255.; + pixel[0] = (pixel[0] as f32 / a) as u8; + pixel[1] = (pixel[1] as f32 / a) as u8; + pixel[2] = (pixel[2] as f32 / a) as u8; + } + } + Some((cx_bounds, bytes)) } } @@ -316,6 +362,7 @@ impl FontSystemState { id: *glyph_id as GlyphId, position: vec2f(position.x as f32, position.y as f32), index: ix_converter.utf8_ix, + is_emoji: font_id == self.emoji_font_id, }); } @@ -520,7 +567,14 @@ mod tests { for i in 0..VARIANTS { let variant = i as f32 / VARIANTS as f32; let (bounds, bytes) = fonts - .rasterize_glyph(font_id, 16.0, glyph_id, vec2f(variant, variant), 2.) + .rasterize_glyph( + font_id, + 16.0, + glyph_id, + vec2f(variant, variant), + 2., + RasterizationOptions::Alpha, + ) .unwrap(); let name = format!("/Users/as-cii/Desktop/twog-{}.png", i); diff --git a/crates/gpui/src/platform/mac/image_cache.rs b/crates/gpui/src/platform/mac/image_cache.rs index 37129d4feafae5d782affd522d5e0573134ae8cf..f16c62462b5c2c445eac9aa207457e90741e4c7e 100644 --- a/crates/gpui/src/platform/mac/image_cache.rs +++ b/crates/gpui/src/platform/mac/image_cache.rs @@ -1,21 +1,39 @@ -use anyhow::anyhow; -use metal::{MTLPixelFormat, TextureDescriptor, TextureRef}; - use super::atlas::{AllocId, AtlasAllocator}; use crate::{ + fonts::{FontId, GlyphId}, geometry::{rect::RectI, vector::Vector2I}, - ImageData, + platform::RasterizationOptions, + scene::ImageGlyph, + FontSystem, ImageData, }; -use std::{collections::HashMap, mem}; +use anyhow::anyhow; +use metal::{MTLPixelFormat, TextureDescriptor, TextureRef}; +use ordered_float::OrderedFloat; +use std::{collections::HashMap, mem, sync::Arc}; + +#[derive(Hash, Eq, PartialEq)] +struct GlyphDescriptor { + font_id: FontId, + font_size: OrderedFloat, + glyph_id: GlyphId, +} pub struct ImageCache { prev_frame: HashMap, curr_frame: HashMap, + image_glyphs: HashMap>, atlases: AtlasAllocator, + scale_factor: f32, + fonts: Arc, } impl ImageCache { - pub fn new(device: metal::Device, size: Vector2I) -> Self { + pub fn new( + device: metal::Device, + size: Vector2I, + scale_factor: f32, + fonts: Arc, + ) -> Self { let descriptor = TextureDescriptor::new(); descriptor.set_pixel_format(MTLPixelFormat::BGRA8Unorm); descriptor.set_width(size.x() as u64); @@ -23,7 +41,21 @@ impl ImageCache { Self { prev_frame: Default::default(), curr_frame: Default::default(), + image_glyphs: Default::default(), atlases: AtlasAllocator::new(device, descriptor), + scale_factor, + fonts, + } + } + + pub fn set_scale_factor(&mut self, scale_factor: f32) { + if scale_factor != self.scale_factor { + self.scale_factor = scale_factor; + for (_, glyph) in self.image_glyphs.drain() { + if let Some((alloc_id, _, _)) = glyph { + self.atlases.deallocate(alloc_id); + } + } } } @@ -39,6 +71,37 @@ impl ImageCache { (alloc_id, atlas_bounds) } + pub fn render_glyph(&mut self, image_glyph: &ImageGlyph) -> Option<(AllocId, RectI, Vector2I)> { + *self + .image_glyphs + .entry(GlyphDescriptor { + font_id: image_glyph.font_id, + font_size: OrderedFloat(image_glyph.font_size), + glyph_id: image_glyph.id, + }) + .or_insert_with(|| { + let (glyph_bounds, bytes) = self.fonts.rasterize_glyph( + image_glyph.font_id, + image_glyph.font_size, + image_glyph.id, + Default::default(), + self.scale_factor, + RasterizationOptions::Bgra, + )?; + let (alloc_id, atlas_bounds) = self + .atlases + .upload(glyph_bounds.size(), &bytes) + .ok_or_else(|| { + anyhow!( + "could not upload image glyph of size {:?}", + glyph_bounds.size() + ) + }) + .unwrap(); + Some((alloc_id, atlas_bounds, glyph_bounds.origin())) + }) + } + pub fn finish_frame(&mut self) { mem::swap(&mut self.prev_frame, &mut self.curr_frame); for (_, (id, _)) in self.curr_frame.drain() { diff --git a/crates/gpui/src/platform/mac/renderer.rs b/crates/gpui/src/platform/mac/renderer.rs index b06dabc738c4c86aadc18d0491c88e3cbb316f74..99dbd2030e0ed2fb3bea88bd295055024d9c879b 100644 --- a/crates/gpui/src/platform/mac/renderer.rs +++ b/crates/gpui/src/platform/mac/renderer.rs @@ -6,7 +6,7 @@ use crate::{ vector::{vec2f, vec2i, Vector2F}, }, platform, - scene::{Glyph, Icon, Image, Layer, Quad, Scene, Shadow, Underline}, + scene::{Glyph, Icon, Image, ImageGlyph, Layer, Quad, Scene, Shadow, Underline}, }; use cocoa::foundation::NSUInteger; use log::warn; @@ -67,8 +67,13 @@ impl Renderer { MTLResourceOptions::StorageModeManaged, ); - let sprite_cache = SpriteCache::new(device.clone(), vec2i(1024, 768), scale_factor, fonts); - let image_cache = ImageCache::new(device.clone(), vec2i(1024, 768)); + let sprite_cache = SpriteCache::new( + device.clone(), + vec2i(1024, 768), + scale_factor, + fonts.clone(), + ); + let image_cache = ImageCache::new(device.clone(), vec2i(1024, 768), scale_factor, fonts); let path_atlases = AtlasAllocator::new(device.clone(), build_path_atlas_texture_descriptor()); let quad_pipeline_state = build_pipeline_state( @@ -141,6 +146,9 @@ impl Renderer { command_buffer: &metal::CommandBufferRef, output: &metal::TextureRef, ) { + self.sprite_cache.set_scale_factor(scene.scale_factor()); + self.image_cache.set_scale_factor(scene.scale_factor()); + let mut offset = 0; let path_sprites = self.render_path_atlases(scene, &mut offset, command_buffer); @@ -359,6 +367,7 @@ impl Renderer { ); self.render_images( layer.images(), + layer.image_glyphs(), scale_factor, offset, drawable_size, @@ -541,8 +550,6 @@ impl Renderer { return; } - self.sprite_cache.set_scale_factor(scale_factor); - let mut sprites_by_atlas = HashMap::new(); for glyph in glyphs { @@ -653,12 +660,13 @@ impl Renderer { fn render_images( &mut self, images: &[Image], + image_glyphs: &[ImageGlyph], scale_factor: f32, offset: &mut usize, drawable_size: Vector2F, command_encoder: &metal::RenderCommandEncoderRef, ) { - if images.is_empty() { + if images.is_empty() && image_glyphs.is_empty() { return; } @@ -686,6 +694,31 @@ impl Renderer { }); } + for image_glyph in image_glyphs { + let origin = (image_glyph.origin * scale_factor).floor(); + if let Some((alloc_id, atlas_bounds, glyph_origin)) = + self.image_cache.render_glyph(image_glyph) + { + images_by_atlas + .entry(alloc_id.atlas_id) + .or_insert_with(Vec::new) + .push(shaders::GPUIImage { + origin: (origin + glyph_origin.to_f32()).to_float2(), + target_size: atlas_bounds.size().to_float2(), + source_size: atlas_bounds.size().to_float2(), + atlas_origin: atlas_bounds.origin().to_float2(), + border_top: 0., + border_right: 0., + border_bottom: 0., + border_left: 0., + border_color: Default::default(), + corner_radius: 0., + }); + } else { + log::warn!("could not render glyph with id {}", image_glyph.id); + } + } + command_encoder.set_render_pipeline_state(&self.image_pipeline_state); command_encoder.set_vertex_buffer( shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexVertices as u64, diff --git a/crates/gpui/src/platform/mac/sprite_cache.rs b/crates/gpui/src/platform/mac/sprite_cache.rs index a9e6acb53fd895bf9a170f109522b2a4fa041be3..a93df3b187bbc07fd334efb571563ded383e8131 100644 --- a/crates/gpui/src/platform/mac/sprite_cache.rs +++ b/crates/gpui/src/platform/mac/sprite_cache.rs @@ -2,7 +2,7 @@ use super::atlas::AtlasAllocator; use crate::{ fonts::{FontId, GlyphId}, geometry::vector::{vec2f, Vector2F, Vector2I}, - platform, + platform::{self, RasterizationOptions}, }; use collections::hash_map::Entry; use metal::{MTLPixelFormat, TextureDescriptor}; @@ -113,6 +113,7 @@ impl SpriteCache { glyph_id, subpixel_shift, scale_factor, + RasterizationOptions::Alpha, )?; let (alloc_id, atlas_bounds) = atlases diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 450edc6d8f6ea09c52d43025446c390c98361af8..6c23a731bcfe7bc13cc2421cc856cdf28df93eba 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -29,6 +29,7 @@ pub struct Layer { images: Vec, shadows: Vec, glyphs: Vec, + image_glyphs: Vec, icons: Vec, paths: Vec, } @@ -58,6 +59,14 @@ pub struct Glyph { pub color: Color, } +#[derive(Debug)] +pub struct ImageGlyph { + pub font_id: FontId, + pub font_size: f32, + pub id: GlyphId, + pub origin: Vector2F, +} + pub struct Icon { pub bounds: RectF, pub svg: usvg::Tree, @@ -204,6 +213,10 @@ impl Scene { self.active_layer().push_glyph(glyph) } + pub fn push_image_glyph(&mut self, image_glyph: ImageGlyph) { + self.active_layer().push_image_glyph(image_glyph) + } + pub fn push_icon(&mut self, icon: Icon) { self.active_layer().push_icon(icon) } @@ -264,13 +277,14 @@ impl Layer { pub fn new(clip_bounds: Option) -> Self { Self { clip_bounds, - quads: Vec::new(), - underlines: Vec::new(), - images: Vec::new(), - shadows: Vec::new(), - glyphs: Vec::new(), - icons: Vec::new(), - paths: Vec::new(), + quads: Default::default(), + underlines: Default::default(), + images: Default::default(), + shadows: Default::default(), + image_glyphs: Default::default(), + glyphs: Default::default(), + icons: Default::default(), + paths: Default::default(), } } @@ -318,6 +332,14 @@ impl Layer { self.shadows.as_slice() } + fn push_image_glyph(&mut self, glyph: ImageGlyph) { + self.image_glyphs.push(glyph); + } + + pub fn image_glyphs(&self) -> &[ImageGlyph] { + self.image_glyphs.as_slice() + } + fn push_glyph(&mut self, glyph: Glyph) { self.glyphs.push(glyph); } diff --git a/crates/gpui/src/text_layout.rs b/crates/gpui/src/text_layout.rs index a866ea0a85086f810a130d9dcb477a18834a5043..2d8672aab3dc9e283e7d3faee856bc22a31b54fc 100644 --- a/crates/gpui/src/text_layout.rs +++ b/crates/gpui/src/text_layout.rs @@ -191,6 +191,7 @@ pub struct Glyph { pub id: GlyphId, pub position: Vector2F, pub index: usize, + pub is_emoji: bool, } impl Line { @@ -323,13 +324,22 @@ impl Line { }); } - cx.scene.push_glyph(scene::Glyph { - font_id: run.font_id, - font_size: self.layout.font_size, - id: glyph.id, - origin: glyph_origin, - color, - }); + if glyph.is_emoji { + cx.scene.push_image_glyph(scene::ImageGlyph { + font_id: run.font_id, + font_size: self.layout.font_size, + id: glyph.id, + origin: glyph_origin, + }); + } else { + cx.scene.push_glyph(scene::Glyph { + font_id: run.font_id, + font_size: self.layout.font_size, + id: glyph.id, + origin: glyph_origin, + color, + }); + } } } @@ -389,13 +399,22 @@ impl Line { .bounding_box(run.font_id, self.layout.font_size), ); if glyph_bounds.intersects(visible_bounds) { - cx.scene.push_glyph(scene::Glyph { - font_id: run.font_id, - font_size: self.layout.font_size, - id: glyph.id, - origin: glyph_bounds.origin() + baseline_origin, - color, - }); + if glyph.is_emoji { + cx.scene.push_image_glyph(scene::ImageGlyph { + font_id: run.font_id, + font_size: self.layout.font_size, + id: glyph.id, + origin: glyph_bounds.origin() + baseline_origin, + }); + } else { + cx.scene.push_glyph(scene::Glyph { + font_id: run.font_id, + font_size: self.layout.font_size, + id: glyph.id, + origin: glyph_bounds.origin() + baseline_origin, + color, + }); + } } } } From 8e890747145574c2a75e8766be60a75e561e149d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 13 Apr 2022 19:06:34 +0200 Subject: [PATCH 6/6] Reduce allocations when caching fonts Co-Authored-By: Nathan Sobo --- crates/gpui/src/platform/mac/fonts.rs | 63 +++++++++++++++++---------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/crates/gpui/src/platform/mac/fonts.rs b/crates/gpui/src/platform/mac/fonts.rs index 417bfe6728abcad67526c40c4f238523ce698ed7..5a08cacf09d78aa36dc1158ff7de61686c36c5c7 100644 --- a/crates/gpui/src/platform/mac/fonts.rs +++ b/crates/gpui/src/platform/mac/fonts.rs @@ -9,6 +9,7 @@ use crate::{ text_layout::{Glyph, LineLayout, Run, RunStyle}, }; use cocoa::appkit::{CGFloat, CGPoint}; +use collections::HashMap; use core_foundation::{ array::CFIndex, attributed_string::{CFAttributedStringRef, CFMutableAttributedString}, @@ -36,19 +37,19 @@ struct FontSystemState { memory_source: MemSource, system_source: SystemSource, fonts: Vec, - emoji_font_id: FontId, + font_ids_by_postscript_name: HashMap, + postscript_names_by_font_id: HashMap, } impl FontSystem { pub fn new() -> Self { - let mut state = FontSystemState { + Self(RwLock::new(FontSystemState { memory_source: MemSource::empty(), system_source: SystemSource::new(), fonts: Vec::new(), - emoji_font_id: FontId(0), // This will be the first font that we load. - }; - state.load_family("Apple Color Emoji").unwrap(); - Self(RwLock::new(state)) + font_ids_by_postscript_name: Default::default(), + postscript_names_by_font_id: Default::default(), + })) } } @@ -128,7 +129,13 @@ impl FontSystemState { .or_else(|_| self.system_source.select_family_by_name(name))?; for font in family.fonts() { let font = font.load()?; - font_ids.push(FontId(self.fonts.len())); + let font_id = FontId(self.fonts.len()); + font_ids.push(font_id); + let postscript_name = font.postscript_name().unwrap(); + self.font_ids_by_postscript_name + .insert(postscript_name.clone(), font_id); + self.postscript_names_by_font_id + .insert(font_id, postscript_name); self.fonts.push(font); } Ok(font_ids) @@ -159,17 +166,30 @@ impl FontSystemState { self.fonts[font_id.0].glyph_for_char(ch) } - fn id_for_font(&mut self, requested_font: font_kit::font::Font) -> FontId { - // TODO: don't allocate the postscript name - // Note: Coretext always returns a Some option for postscript_name - let requested_font_name = requested_font.postscript_name(); - for (id, font) in self.fonts.iter().enumerate() { - if font.postscript_name() == requested_font_name { - return FontId(id); - } + fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId { + let postscript_name = requested_font.postscript_name(); + if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) { + *font_id + } else { + let font_id = FontId(self.fonts.len()); + self.font_ids_by_postscript_name + .insert(postscript_name.clone(), font_id); + self.postscript_names_by_font_id + .insert(font_id, postscript_name); + self.fonts + .push(font_kit::font::Font::from_core_graphics_font( + requested_font.copy_to_CGFont(), + )); + font_id } - self.fonts.push(requested_font); - FontId(self.fonts.len() - 1) + } + + fn is_emoji(&self, font_id: FontId) -> bool { + self.postscript_names_by_font_id + .get(&font_id) + .map_or(false, |postscript_name| { + postscript_name == "AppleColorEmoji" + }) } fn rasterize_glyph( @@ -340,13 +360,12 @@ impl FontSystemState { for run in line.glyph_runs().into_iter() { let attributes = run.attributes().unwrap(); let font = unsafe { - let native_font = attributes + attributes .get(kCTFontAttributeName) .downcast::() - .unwrap(); - font_kit::font::Font::from_native_font(native_font) + .unwrap() }; - let font_id = self.id_for_font(font); + let font_id = self.id_for_native_font(font); let mut ix_converter = StringIndexConverter::new(text); let mut glyphs = Vec::new(); @@ -362,7 +381,7 @@ impl FontSystemState { id: *glyph_id as GlyphId, position: vec2f(position.x as f32, position.y as f32), index: ix_converter.utf8_ix, - is_emoji: font_id == self.emoji_font_id, + is_emoji: self.is_emoji(font_id), }); }