Checkpoint

Antonio Scandurra created

Change summary

crates/gpui3/src/platform.rs                      | 12 ++-
crates/gpui3/src/platform/mac/metal_atlas.rs      | 50 ++++++++++++----
crates/gpui3/src/platform/mac/metal_renderer.rs   | 21 ++++++
crates/gpui3/src/platform/mac/text_system.rs      | 16 ++--
crates/gpui3/src/platform/mac/window.rs           | 15 +++-
crates/gpui3/src/text_system.rs                   | 31 ++++++----
crates/gpui3/src/text_system/line.rs              | 10 +-
crates/gpui3/src/text_system/text_layout_cache.rs | 12 ++--
crates/gpui3/src/window.rs                        | 10 ++-
9 files changed, 118 insertions(+), 59 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, LineLayout,
-    MonochromeSprite, Pixels, Point, Result, Scene, SharedString, Size,
+    AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId, MonochromeSprite,
+    Pixels, Point, RasterizedGlyphId, Result, Scene, ShapedLine, SharedString, Size,
 };
 use anyhow::anyhow;
 use async_task::Runnable;
@@ -146,6 +146,8 @@ pub trait PlatformWindow {
     fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
     fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
     fn draw(&self, scene: Scene);
+
+    fn glyph_atlas(&self) -> Arc<dyn PlatformAtlas<RasterizedGlyphId>>;
 }
 
 pub trait PlatformDispatcher: Send + Sync {
@@ -170,7 +172,7 @@ pub trait PlatformTextSystem: Send + Sync {
         scale_factor: f32,
         options: RasterizationOptions,
     ) -> Option<(Bounds<u32>, Vec<u8>)>;
-    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> LineLayout;
+    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> ShapedLine;
     fn wrap_line(
         &self,
         text: &str,
@@ -180,11 +182,11 @@ pub trait PlatformTextSystem: Send + Sync {
     ) -> Vec<usize>;
 }
 
-pub trait PlatformAtlas<Key> {
+pub trait PlatformAtlas<Key>: Send + Sync {
     fn get_or_insert_with(
         &self,
         key: Key,
-        build: impl FnOnce() -> (Size<DevicePixels>, Vec<u8>),
+        build: &dyn Fn() -> (Size<DevicePixels>, Vec<u8>),
     ) -> AtlasTile;
 
     fn clear(&self);

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

@@ -1,35 +1,54 @@
 use crate::{AtlasTextureId, AtlasTile, Bounds, DevicePixels, PlatformAtlas, Point, Size};
 use collections::HashMap;
+use derive_more::{Deref, DerefMut};
 use etagere::BucketedAtlasAllocator;
 use foreign_types::ForeignType;
-use metal::{Device, TextureDescriptor, TextureDescriptorRef};
+use metal::{Device, TextureDescriptor};
 use objc::{msg_send, sel, sel_impl};
-use parking_lot::{RwLock, RwLockUpgradableReadGuard};
+use parking_lot::Mutex;
 use std::hash::Hash;
 
-pub struct MetalAtlas<Key>(RwLock<MetalAtlasState<Key>>);
+pub struct MetalAtlas<Key>(Mutex<MetalAtlasState<Key>>);
+
+impl<Key> MetalAtlas<Key> {
+    pub fn new(
+        size: Size<DevicePixels>,
+        pixel_format: metal::MTLPixelFormat,
+        device: Device,
+    ) -> Self {
+        let texture_descriptor = metal::TextureDescriptor::new();
+        texture_descriptor.set_pixel_format(pixel_format);
+        texture_descriptor.set_width(size.width.into());
+        texture_descriptor.set_height(size.height.into());
+        MetalAtlas(Mutex::new(MetalAtlasState {
+            device: AssertSend(device),
+            texture_descriptor: AssertSend(texture_descriptor),
+            textures: Default::default(),
+            tiles_by_key: Default::default(),
+        }))
+    }
+}
 
 struct MetalAtlasState<Key> {
-    device: Device,
-    texture_descriptor: TextureDescriptor,
+    device: AssertSend<Device>,
+    texture_descriptor: AssertSend<TextureDescriptor>,
     textures: Vec<MetalAtlasTexture>,
     tiles_by_key: HashMap<Key, AtlasTile>,
 }
 
 impl<Key> PlatformAtlas<Key> for MetalAtlas<Key>
 where
-    Key: Eq + Hash,
+    Key: Eq + Hash + Send,
 {
     fn get_or_insert_with(
         &self,
         key: Key,
-        build: impl FnOnce() -> (Size<DevicePixels>, Vec<u8>),
+        build: &dyn Fn() -> (Size<DevicePixels>, Vec<u8>),
     ) -> AtlasTile {
-        let lock = self.0.upgradable_read();
+        let mut lock = self.0.lock();
         if let Some(tile) = lock.tiles_by_key.get(&key) {
             return tile.clone();
         } else {
-            let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
             let (size, bytes) = build();
             lock.textures
                 .iter_mut()
@@ -45,7 +64,7 @@ where
     }
 
     fn clear(&self) {
-        self.0.write().tiles_by_key.clear();
+        self.0.lock().tiles_by_key.clear();
     }
 }
 
@@ -62,7 +81,7 @@ impl<Key> MetalAtlasState<Key> {
         {
             let descriptor = unsafe {
                 let descriptor_ptr: *mut metal::MTLTextureDescriptor =
-                    msg_send![self.texture_descriptor, copy];
+                    msg_send![*self.texture_descriptor, copy];
                 metal::TextureDescriptor::from_ptr(descriptor_ptr)
             };
             descriptor.set_width(min_size.width.into());
@@ -78,7 +97,7 @@ impl<Key> MetalAtlasState<Key> {
         let atlas_texture = MetalAtlasTexture {
             id: AtlasTextureId(self.textures.len()),
             allocator: etagere::BucketedAtlasAllocator::new(size.into()),
-            metal_texture,
+            metal_texture: AssertSend(metal_texture),
         };
         self.textures.push(atlas_texture);
         self.textures.last_mut().unwrap()
@@ -88,7 +107,7 @@ impl<Key> MetalAtlasState<Key> {
 struct MetalAtlasTexture {
     id: AtlasTextureId,
     allocator: BucketedAtlasAllocator,
-    metal_texture: metal::Texture,
+    metal_texture: AssertSend<metal::Texture>,
 }
 
 impl MetalAtlasTexture {
@@ -162,3 +181,8 @@ impl From<etagere::Rectangle> for Bounds<DevicePixels> {
         }
     }
 }
+
+#[derive(Deref, DerefMut)]
+struct AssertSend<T>(T);
+
+unsafe impl<T> Send for AssertSend<T> {}

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

@@ -1,4 +1,6 @@
-use crate::{point, size, DevicePixels, MonochromeSprite, Quad, Scene, Size};
+use crate::{
+    point, size, DevicePixels, MetalAtlas, MonochromeSprite, Quad, RasterizedGlyphId, Scene, Size,
+};
 use bytemuck::{Pod, Zeroable};
 use cocoa::{
     base::{NO, YES},
@@ -7,7 +9,7 @@ use cocoa::{
 };
 use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
 use objc::{self, msg_send, sel, sel_impl};
-use std::{ffi::c_void, mem, ptr};
+use std::{ffi::c_void, mem, ptr, sync::Arc};
 
 const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
 const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
@@ -19,6 +21,7 @@ pub struct MetalRenderer {
     quad_pipeline_state: metal::RenderPipelineState,
     unit_vertices: metal::Buffer,
     instances: metal::Buffer,
+    glyph_atlas: Arc<MetalAtlas<RasterizedGlyphId>>,
 }
 
 impl MetalRenderer {
@@ -88,6 +91,15 @@ impl MetalRenderer {
         );
 
         let command_queue = device.new_command_queue();
+        let glyph_atlas = Arc::new(MetalAtlas::new(
+            Size {
+                width: DevicePixels(1024),
+                height: DevicePixels(1024),
+            },
+            MTLPixelFormat::A8Unorm,
+            device.clone(),
+        ));
+
         Self {
             device,
             layer,
@@ -95,6 +107,7 @@ impl MetalRenderer {
             quad_pipeline_state,
             unit_vertices,
             instances,
+            glyph_atlas,
         }
     }
 
@@ -102,6 +115,10 @@ impl MetalRenderer {
         &*self.layer
     }
 
+    pub fn glyph_atlas(&self) -> &Arc<MetalAtlas<RasterizedGlyphId>> {
+        &self.glyph_atlas
+    }
+
     pub fn draw(&mut self, scene: &mut Scene) {
         let layer = self.layer.clone();
         let viewport_size = layer.drawable_size();

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

@@ -1,7 +1,7 @@
 use crate::{
-    point, px, size, Bounds, Font, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, Glyph,
-    GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RasterizationOptions, Result, Run,
-    SharedString, Size,
+    point, px, size, Bounds, Font, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight,
+    GlyphId, Pixels, PlatformTextSystem, Point, RasterizationOptions, Result, ShapedGlyph,
+    ShapedLine, ShapedRun, SharedString, Size,
 };
 use cocoa::appkit::{CGFloat, CGPoint};
 use collections::HashMap;
@@ -161,7 +161,7 @@ impl PlatformTextSystem for MacTextSystem {
         text: &str,
         font_size: Pixels,
         font_runs: &[(usize, FontId)],
-    ) -> LineLayout {
+    ) -> ShapedLine {
         self.0.write().layout_line(text, font_size, font_runs)
     }
 
@@ -348,7 +348,7 @@ impl MacTextSystemState {
         text: &str,
         font_size: Pixels,
         font_runs: &[(usize, FontId)],
-    ) -> LineLayout {
+    ) -> ShapedLine {
         // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
         let mut string = CFMutableAttributedString::new();
         {
@@ -409,7 +409,7 @@ impl MacTextSystemState {
             {
                 let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
                 ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
-                glyphs.push(Glyph {
+                glyphs.push(ShapedGlyph {
                     id: (*glyph_id).into(),
                     position: point(position.x as f32, position.y as f32).map(px),
                     index: ix_converter.utf8_ix,
@@ -417,11 +417,11 @@ impl MacTextSystemState {
                 });
             }
 
-            runs.push(Run { font_id, glyphs })
+            runs.push(ShapedRun { font_id, glyphs })
         }
 
         let typographic_bounds = line.get_typographic_bounds();
-        LineLayout {
+        ShapedLine {
             width: typographic_bounds.width.into(),
             ascent: typographic_bounds.ascent.into(),
             descent: typographic_bounds.descent.into(),

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

@@ -1,10 +1,10 @@
 use super::{ns_string, MetalRenderer, NSRange};
 use crate::{
-    point, px, size, AnyWindowHandle, Bounds, Event, KeyDownEvent, Keystroke, MacScreen, Modifiers,
-    ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent, MouseUpEvent, NSRectExt,
-    Pixels, Platform, PlatformDispatcher, PlatformInputHandler, PlatformScreen, PlatformWindow,
-    Point, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
-    WindowPromptLevel,
+    point, px, size, AnyWindowHandle, Bounds, Event, KeyDownEvent, Keystroke, MacScreen,
+    MetalAtlas, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent,
+    MouseUpEvent, NSRectExt, Pixels, Platform, PlatformAtlas, PlatformDispatcher,
+    PlatformInputHandler, PlatformScreen, PlatformWindow, Point, RasterizedGlyphId, Scene, Size,
+    Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
 };
 use block::ConcreteBlock;
 use cocoa::{
@@ -20,6 +20,7 @@ use core_graphics::display::CGRect;
 use ctor::ctor;
 use foreign_types::ForeignTypeRef;
 use futures::channel::oneshot;
+use metal::{MTLPixelFormat, TextureDescriptor};
 use objc::{
     class,
     declare::ClassDecl,
@@ -885,6 +886,10 @@ impl PlatformWindow for MacWindow {
             let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES];
         }
     }
+
+    fn glyph_atlas(&self) -> Arc<dyn PlatformAtlas<RasterizedGlyphId>> {
+        self.0.lock().renderer.glyph_atlas().clone()
+    }
 }
 
 fn get_scale_factor(native_window: id) -> f32 {

crates/gpui3/src/text_system.rs 🔗

@@ -346,28 +346,35 @@ impl From<u32> for GlyphId {
     }
 }
 
-#[derive(Clone, Debug)]
-pub struct Glyph {
-    pub id: GlyphId,
-    pub position: Point<Pixels>,
-    pub index: usize,
-    pub is_emoji: bool,
-}
-
 #[derive(Default, Debug)]
-pub struct LineLayout {
+pub struct ShapedLine {
     pub font_size: Pixels,
     pub width: Pixels,
     pub ascent: Pixels,
     pub descent: Pixels,
-    pub runs: Vec<Run>,
+    pub runs: Vec<ShapedRun>,
     pub len: usize,
 }
 
 #[derive(Debug)]
-pub struct Run {
+pub struct ShapedRun {
     pub font_id: FontId,
-    pub glyphs: Vec<Glyph>,
+    pub glyphs: Vec<ShapedGlyph>,
+}
+
+#[derive(Clone, Debug)]
+pub struct ShapedGlyph {
+    pub id: GlyphId,
+    pub position: Point<Pixels>,
+    pub index: usize,
+    pub is_emoji: bool,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+pub struct RasterizedGlyphId {
+    font_id: FontId,
+    glyph_id: GlyphId,
+    font_size: Pixels,
 }
 
 #[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, LineLayout, Pixels, Point, Run, RunStyle,
-    ShapedBoundary, UnderlineStyle, WindowContext,
+    black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RunStyle, ShapedBoundary,
+    ShapedLine, ShapedRun, UnderlineStyle, WindowContext,
 };
 use anyhow::Result;
 use smallvec::SmallVec;
@@ -8,7 +8,7 @@ use std::sync::Arc;
 
 #[derive(Default, Debug, Clone)]
 pub struct Line {
-    layout: Arc<LineLayout>,
+    layout: Arc<ShapedLine>,
     style_runs: SmallVec<[StyleRun; 32]>,
 }
 
@@ -20,7 +20,7 @@ struct StyleRun {
 }
 
 impl Line {
-    pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
+    pub fn new(layout: Arc<ShapedLine>, runs: &[(usize, RunStyle)]) -> Self {
         let mut style_runs = SmallVec::new();
         for (len, style) in runs {
             style_runs.push(StyleRun {
@@ -32,7 +32,7 @@ impl Line {
         Self { layout, style_runs }
     }
 
-    pub fn runs(&self) -> &[Run] {
+    pub fn runs(&self) -> &[ShapedRun] {
         &self.layout.runs
     }
 

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

@@ -1,4 +1,4 @@
-use crate::{FontId, Glyph, LineLayout, Pixels, PlatformTextSystem, Run};
+use crate::{FontId, Pixels, PlatformTextSystem, ShapedGlyph, ShapedLine, ShapedRun};
 use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
 use smallvec::SmallVec;
 use std::{
@@ -9,8 +9,8 @@ use std::{
 };
 
 pub(crate) struct TextLayoutCache {
-    prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
-    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
+    prev_frame: Mutex<HashMap<CacheKeyValue, Arc<ShapedLine>>>,
+    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<ShapedLine>>>,
     platform_text_system: Arc<dyn PlatformTextSystem>,
 }
 
@@ -35,7 +35,7 @@ impl TextLayoutCache {
         text: &'a str,
         font_size: Pixels,
         runs: &[(usize, FontId)],
-    ) -> Arc<LineLayout> {
+    ) -> Arc<ShapedLine> {
         let key = &CacheKeyRef {
             text,
             font_size,
@@ -146,8 +146,8 @@ pub struct ShapedBoundary {
     pub glyph_ix: usize,
 }
 
-impl Run {
-    pub fn glyphs(&self) -> &[Glyph] {
+impl ShapedRun {
+    pub fn glyphs(&self) -> &[ShapedGlyph] {
         &self.glyphs
     }
 }

crates/gpui3/src/window.rs 🔗

@@ -1,7 +1,8 @@
 use crate::{
-    px, AnyView, AppContext, AvailableSpace, Bounds, Context, Effect, Element, EntityId, Handle,
-    LayoutId, MainThread, MainThreadOnly, Pixels, PlatformWindow, Point, Reference, Scene, Size,
-    StackContext, StackingOrder, Style, TaffyLayoutEngine, WeakHandle, WindowOptions,
+    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,
 };
 use anyhow::Result;
 use futures::Future;
@@ -14,6 +15,7 @@ pub struct AnyWindow {}
 pub struct Window {
     handle: AnyWindowHandle,
     platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
+    glyph_atlas: Arc<dyn PlatformAtlas<RasterizedGlyphId>>,
     rem_size: Pixels,
     content_size: Size<Pixels>,
     layout_engine: TaffyLayoutEngine,
@@ -31,6 +33,7 @@ impl Window {
         cx: &mut MainThread<AppContext>,
     ) -> Self {
         let platform_window = cx.platform().open_window(handle, options);
+        let glyph_atlas = platform_window.glyph_atlas();
         let mouse_position = platform_window.mouse_position();
         let content_size = platform_window.content_size();
         let scale_factor = platform_window.scale_factor();
@@ -53,6 +56,7 @@ impl Window {
         Window {
             handle,
             platform_window,
+            glyph_atlas,
             rem_size: px(16.),
             content_size,
             layout_engine: TaffyLayoutEngine::new(),