Checkpoint

Antonio Scandurra created

Change summary

crates/gpui3/src/platform.rs                 |  19 +-
crates/gpui3/src/platform/mac/metal_atlas.rs |  27 ++-
crates/gpui3/src/platform/mac/text_system.rs | 149 ++++++++-------------
crates/gpui3/src/text_system.rs              |  34 ++++
crates/gpui3/src/text_system/line.rs         |   4 
crates/gpui3/src/window.rs                   |  42 +++++
6 files changed, 148 insertions(+), 127 deletions(-)

Detailed changes

crates/gpui3/src/platform.rs 🔗

@@ -6,8 +6,8 @@ mod mac;
 mod test;
 
 use crate::{
-    AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId, MonochromeSprite,
-    Pixels, Point, RasterizedGlyphId, Result, Scene, ShapedLine, SharedString, Size,
+    AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId, Pixels, Point,
+    RasterizedGlyphId, Result, Scene, ShapedLine, SharedString, Size,
 };
 use anyhow::anyhow;
 use async_task::Runnable;
@@ -165,13 +165,8 @@ pub trait PlatformTextSystem: Send + Sync {
     fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
     fn rasterize_glyph(
         &self,
-        font_id: FontId,
-        font_size: f32,
-        glyph_id: GlyphId,
-        subpixel_shift: Point<Pixels>,
-        scale_factor: f32,
-        options: RasterizationOptions,
-    ) -> Option<(Bounds<u32>, Vec<u8>)>;
+        glyph_id: &RasterizedGlyphId,
+    ) -> Result<(Bounds<DevicePixels>, Vec<u8>)>;
     fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> ShapedLine;
     fn wrap_line(
         &self,
@@ -185,9 +180,9 @@ pub trait PlatformTextSystem: Send + Sync {
 pub trait PlatformAtlas<Key>: Send + Sync {
     fn get_or_insert_with(
         &self,
-        key: Key,
-        build: &dyn Fn() -> (Size<DevicePixels>, Vec<u8>),
-    ) -> AtlasTile;
+        key: &Key,
+        build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Vec<u8>)>,
+    ) -> Result<AtlasTile>;
 
     fn clear(&self);
 }

crates/gpui3/src/platform/mac/metal_atlas.rs 🔗

@@ -1,4 +1,5 @@
 use crate::{AtlasTextureId, AtlasTile, Bounds, DevicePixels, PlatformAtlas, Point, Size};
+use anyhow::{anyhow, Result};
 use collections::HashMap;
 use derive_more::{Deref, DerefMut};
 use etagere::BucketedAtlasAllocator;
@@ -38,28 +39,30 @@ struct MetalAtlasState<Key> {
 
 impl<Key> PlatformAtlas<Key> for MetalAtlas<Key>
 where
-    Key: Eq + Hash + Send,
+    Key: Clone + Eq + Hash + Send,
 {
     fn get_or_insert_with(
         &self,
-        key: Key,
-        build: &dyn Fn() -> (Size<DevicePixels>, Vec<u8>),
-    ) -> AtlasTile {
+        key: &Key,
+        build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Vec<u8>)>,
+    ) -> Result<AtlasTile> {
         let mut lock = self.0.lock();
-        if let Some(tile) = lock.tiles_by_key.get(&key) {
-            return tile.clone();
+        if let Some(tile) = lock.tiles_by_key.get(key) {
+            return Ok(tile.clone());
         } else {
-            let (size, bytes) = build();
-            lock.textures
+            let (size, bytes) = build()?;
+            let tile = lock
+                .textures
                 .iter_mut()
                 .rev()
                 .find_map(|texture| texture.allocate(size, &bytes))
-                .unwrap_or_else(|| {
+                .or_else(|| {
                     let texture = lock.push_texture(size);
-                    texture
-                        .allocate(size, &bytes)
-                        .expect("could not allocate a tile in new texture")
+                    texture.allocate(size, &bytes)
                 })
+                .ok_or_else(|| anyhow!("could not allocate in new texture"))?;
+            lock.tiles_by_key.insert(key.clone(), tile.clone());
+            Ok(tile)
         }
     }
 

crates/gpui3/src/platform/mac/text_system.rs 🔗

@@ -1,8 +1,9 @@
 use crate::{
-    point, px, size, Bounds, Font, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight,
-    GlyphId, Pixels, PlatformTextSystem, Point, RasterizationOptions, Result, ShapedGlyph,
-    ShapedLine, ShapedRun, SharedString, Size,
+    point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle,
+    FontWeight, GlyphId, Pixels, PlatformTextSystem, Point, RasterizedGlyphId, Result, ShapedGlyph,
+    ShapedLine, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
 };
+use anyhow::anyhow;
 use cocoa::appkit::{CGFloat, CGPoint};
 use collections::HashMap;
 use core_foundation::{
@@ -11,11 +12,7 @@ use core_foundation::{
     base::{CFRange, TCFType},
     string::CFString,
 };
-use core_graphics::{
-    base::{kCGImageAlphaPremultipliedLast, CGGlyph},
-    color_space::CGColorSpace,
-    context::CGContext,
-};
+use core_graphics::{base::CGGlyph, color_space::CGColorSpace, context::CGContext};
 use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
 use font_kit::{
     font::Font as FontKitFont,
@@ -139,21 +136,9 @@ impl PlatformTextSystem for MacTextSystem {
 
     fn rasterize_glyph(
         &self,
-        font_id: FontId,
-        font_size: f32,
-        glyph_id: GlyphId,
-        subpixel_shift: Point<Pixels>,
-        scale_factor: f32,
-        options: RasterizationOptions,
-    ) -> Option<(Bounds<u32>, Vec<u8>)> {
-        self.0.read().rasterize_glyph(
-            font_id,
-            font_size,
-            glyph_id,
-            subpixel_shift,
-            scale_factor,
-            options,
-        )
+        glyph_id: &RasterizedGlyphId,
+    ) -> Result<(Bounds<DevicePixels>, Vec<u8>)> {
+        self.0.read().rasterize_glyph(glyph_id)
     }
 
     fn layout_line(
@@ -247,63 +232,41 @@ impl MacTextSystemState {
 
     fn rasterize_glyph(
         &self,
-        font_id: FontId,
-        font_size: f32,
-        glyph_id: GlyphId,
-        subpixel_shift: Point<Pixels>,
-        scale_factor: f32,
-        options: RasterizationOptions,
-    ) -> Option<(Bounds<u32>, Vec<u8>)> {
-        let font = &self.fonts[font_id.0];
-        let scale = Transform2F::from_scale(scale_factor);
-        let glyph_bounds = font
-            .raster_bounds(
-                glyph_id.into(),
-                font_size,
-                scale,
-                HintingOptions::None,
-                font_kit::canvas::RasterizationOptions::GrayscaleAa,
-            )
-            .ok()?;
+        glyph_id: &RasterizedGlyphId,
+    ) -> Result<(Bounds<DevicePixels>, Vec<u8>)> {
+        let font = &self.fonts[glyph_id.font_id.0];
+        let scale = Transform2F::from_scale(glyph_id.scale_factor);
+        let glyph_bounds = font.raster_bounds(
+            glyph_id.glyph_id.into(),
+            glyph_id.font_size.into(),
+            scale,
+            HintingOptions::None,
+            font_kit::canvas::RasterizationOptions::GrayscaleAa,
+        )?;
 
         if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
-            None
+            Err(anyhow!("glyph bounds are empty"))
         } else {
             // Make room for subpixel variants.
-            let subpixel_padding = subpixel_shift.map(|v| f32::from(v).ceil() as u32);
+            let subpixel_padding = Vector2I::new(
+                glyph_id.subpixel_variant.x.min(1) as i32,
+                glyph_id.subpixel_variant.y.min(1) as i32,
+            );
             let cx_bounds = RectI::new(
                 glyph_bounds.origin(),
-                glyph_bounds.size() + Vector2I::from(subpixel_padding),
+                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,
-                    );
-                }
-            }
+            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,
+            );
 
             // Move the origin to bottom left and account for scaling, this
             // makes drawing text consistent with the font-kit's raster_bounds.
@@ -311,35 +274,31 @@ impl MacTextSystemState {
                 -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.scale(
+                glyph_id.scale_factor as CGFloat,
+                glyph_id.scale_factor as CGFloat,
+            );
+
+            let subpixel_shift = glyph_id
+                .subpixel_variant
+                .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32 / glyph_id.scale_factor);
 
             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(f32::from(glyph_id.font_size) as CGFloat)
                 .draw_glyphs(
-                    &[u32::from(glyph_id) as CGGlyph],
+                    &[u32::from(glyph_id.glyph_id) as CGGlyph],
                     &[CGPoint::new(
-                        (f32::from(subpixel_shift.x) / scale_factor) as CGFloat,
-                        (f32::from(subpixel_shift.y) / scale_factor) as CGFloat,
+                        subpixel_shift.x as CGFloat,
+                        subpixel_shift.y as CGFloat,
                     )],
                     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.into(), bytes))
+            Ok((cx_bounds.into(), bytes))
         }
     }
 
@@ -549,11 +508,17 @@ impl From<RectF> for Bounds<f32> {
     }
 }
 
-impl From<RectI> for Bounds<u32> {
+impl From<RectI> for Bounds<DevicePixels> {
     fn from(rect: RectI) -> Self {
         Bounds {
-            origin: point(rect.origin_x() as u32, rect.origin_y() as u32),
-            size: size(rect.width() as u32, rect.height() as u32),
+            origin: point(
+                DevicePixels(rect.origin_x() as u32),
+                DevicePixels(rect.origin_y() as u32),
+            ),
+            size: size(
+                DevicePixels(rect.width() as u32),
+                DevicePixels(rect.height() as u32),
+            ),
         }
     }
 }

crates/gpui3/src/text_system.rs 🔗

@@ -11,7 +11,8 @@ use line_wrapper::*;
 pub use text_layout_cache::*;
 
 use crate::{
-    px, Bounds, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size, UnderlineStyle,
+    px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, RasterizationOptions,
+    Result, SharedString, Size, UnderlineStyle,
 };
 use collections::HashMap;
 use core::fmt;
@@ -30,6 +31,8 @@ pub struct FontId(pub usize);
 #[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
 pub struct FontFamilyId(pub usize);
 
+pub const SUBPIXEL_VARIANTS: u8 = 4;
+
 pub struct TextSystem {
     text_layout_cache: Arc<TextLayoutCache>,
     platform_text_system: Arc<dyn PlatformTextSystem>,
@@ -212,6 +215,13 @@ impl TextSystem {
             text_system: self.clone(),
         })
     }
+
+    pub fn rasterize_glyph(
+        &self,
+        glyph_id: &RasterizedGlyphId,
+    ) -> Result<(Bounds<DevicePixels>, Vec<u8>)> {
+        self.platform_text_system.rasterize_glyph(glyph_id)
+    }
 }
 
 #[derive(Hash, Eq, PartialEq)]
@@ -370,11 +380,25 @@ pub struct ShapedGlyph {
     pub is_emoji: bool,
 }
 
-#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+#[derive(Clone, Debug, PartialEq)]
 pub struct RasterizedGlyphId {
-    font_id: FontId,
-    glyph_id: GlyphId,
-    font_size: Pixels,
+    pub(crate) font_id: FontId,
+    pub(crate) glyph_id: GlyphId,
+    pub(crate) font_size: Pixels,
+    pub(crate) subpixel_variant: Point<u8>,
+    pub(crate) scale_factor: f32,
+}
+
+impl Eq for RasterizedGlyphId {}
+
+impl Hash for RasterizedGlyphId {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.font_id.0.hash(state);
+        self.glyph_id.0.hash(state);
+        self.font_size.0.to_bits().hash(state);
+        self.subpixel_variant.hash(state);
+        self.scale_factor.to_bits().hash(state);
+    }
 }
 
 #[derive(Clone, Debug, Eq, PartialEq, Hash)]

crates/gpui3/src/text_system/line.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RunStyle, ShapedBoundary,
-    ShapedLine, ShapedRun, UnderlineStyle, WindowContext,
+    black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RasterizedGlyphId, RunStyle,
+    ShapedBoundary, ShapedLine, ShapedRun, UnderlineStyle, WindowContext,
 };
 use anyhow::Result;
 use smallvec::SmallVec;

crates/gpui3/src/window.rs 🔗

@@ -1,8 +1,9 @@
 use crate::{
-    px, AnyView, AppContext, AvailableSpace, Bounds, Context, Effect, Element, EntityId, FontId,
-    GlyphId, Handle, LayoutId, MainThread, MainThreadOnly, Pixels, PlatformAtlas, PlatformWindow,
-    Point, RasterizedGlyphId, Reference, Scene, Size, StackContext, StackingOrder, Style,
-    TaffyLayoutEngine, WeakHandle, WindowOptions,
+    px, AnyView, AppContext, AtlasTile, AvailableSpace, Bounds, Context, DevicePixels, Effect,
+    Element, EntityId, FontId, GlyphId, Handle, LayoutId, MainThread, MainThreadOnly,
+    MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, RasterizedGlyphId, Reference,
+    Scene, Size, StackContext, StackingOrder, Style, TaffyLayoutEngine, WeakHandle, WindowOptions,
+    SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use futures::Future;
@@ -161,6 +162,39 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         })
     }
 
+    pub fn rasterize_glyph(
+        &self,
+        font_id: FontId,
+        glyph_id: GlyphId,
+        font_size: Pixels,
+        scale_factor: f32,
+        target_position: Point<Pixels>,
+    ) -> Result<(AtlasTile, Point<DevicePixels>)> {
+        let target_position = target_position * scale_factor;
+        let subpixel_variant = Point {
+            x: (target_position.x.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
+            y: (target_position.y.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
+        };
+        let rasterized_glyph_id = RasterizedGlyphId {
+            font_id,
+            glyph_id,
+            font_size,
+            subpixel_variant,
+            scale_factor,
+        };
+        let mut offset = Default::default();
+        let tile = self
+            .window
+            .glyph_atlas
+            .get_or_insert_with(&rasterized_glyph_id, &mut || {
+                let (bounds, pixels) = self.text_system().rasterize_glyph(&rasterized_glyph_id)?;
+                offset = bounds.origin;
+                Ok((bounds.size, pixels))
+            })?;
+
+        Ok((tile, offset))
+    }
+
     pub(crate) fn draw(&mut self) -> Result<()> {
         let unit_entity = self.unit_entity.clone();
         self.update_entity(&unit_entity, |_, cx| {