Checkpoint

Antonio Scandurra created

Change summary

crates/gpui3/src/elements/text.rs            |  23 +-
crates/gpui3/src/platform.rs                 |  20 ++
crates/gpui3/src/platform/mac.rs             |   4 
crates/gpui3/src/platform/mac/metal_atlas.rs | 164 ++++++++++++++++++++++
crates/gpui3/src/platform/mac/sprite.rs      |   1 
crates/gpui3/src/scene.rs                    |   8 
6 files changed, 196 insertions(+), 24 deletions(-)

Detailed changes

crates/gpui3/src/elements/text.rs 🔗

@@ -32,7 +32,7 @@ pub struct Text<S> {
 
 impl<S: 'static> Element for Text<S> {
     type State = S;
-    type FrameState = Arc<Mutex<Option<TextLayout>>>;
+    type FrameState = Arc<Mutex<Option<TextFrameState>>>;
 
     fn layout(
         &mut self,
@@ -54,7 +54,6 @@ impl<S: 'static> Element for Text<S> {
         let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
             let frame_state = paint_state.clone();
             move |_, _| {
-                dbg!("starting measurement");
                 let Some(line_layout) = text_system
                     .layout_line(
                         text.as_ref(),
@@ -65,23 +64,21 @@ impl<S: 'static> Element for Text<S> {
                 else {
                     return Size::default();
                 };
-                dbg!("bbbb");
 
                 let size = Size {
                     width: line_layout.width(),
                     height: line_height,
                 };
 
-                frame_state.lock().replace(TextLayout {
+                frame_state.lock().replace(TextFrameState {
                     line: Arc::new(line_layout),
                     line_height,
                 });
 
-                dbg!(size)
+                size
             }
         });
 
-        dbg!("got to end of text layout");
         Ok((layout_id?, paint_state))
     }
 
@@ -89,22 +86,20 @@ impl<S: 'static> Element for Text<S> {
         &mut self,
         layout: Layout,
         _: &mut Self::State,
-        paint_state: &mut Self::FrameState,
+        frame_state: &mut Self::FrameState,
         cx: &mut ViewContext<S>,
     ) -> Result<()> {
         let line;
         let line_height;
         {
-            let paint_state = paint_state.lock();
-            let paint_state = paint_state
+            let frame_state = frame_state.lock();
+            let frame_state = frame_state
                 .as_ref()
                 .expect("measurement has not been performed");
-            line = paint_state.line.clone();
-            line_height = paint_state.line_height;
+            line = frame_state.line.clone();
+            line_height = frame_state.line_height;
         }
 
-        let _text_style = cx.text_style();
-
         // todo!("We haven't added visible bounds to the new element system yet, so this is a placeholder.");
         let visible_bounds = layout.bounds;
         line.paint(&layout, visible_bounds, line_height, cx)?;
@@ -113,7 +108,7 @@ impl<S: 'static> Element for Text<S> {
     }
 }
 
-pub struct TextLayout {
+pub struct TextFrameState {
     line: Arc<Line>,
     line_height: Pixels,
 }

crates/gpui3/src/platform.rs 🔗

@@ -180,14 +180,30 @@ pub trait PlatformTextSystem: Send + Sync {
     ) -> Vec<usize>;
 }
 
-pub trait PlatformSpriteSystem<Key> {
+pub trait PlatformAtlas<Key> {
     fn get_or_insert_with(
         &self,
         key: Key,
         build: impl FnOnce() -> (Size<DevicePixels>, Vec<u8>),
-    ) -> MonochromeSprite;
+    ) -> AtlasTile;
+
+    fn clear(&self);
+}
+
+#[derive(Clone, Debug)]
+#[repr(C)]
+pub struct AtlasTile {
+    pub(crate) texture_id: AtlasTextureId,
+    pub(crate) tile_id: TileId,
+    pub(crate) bounds_in_atlas: Bounds<DevicePixels>,
 }
 
+#[derive(Clone, Copy, Debug)]
+#[repr(C)]
+pub(crate) struct AtlasTextureId(pub(crate) usize);
+
+pub(crate) type TileId = etagere::AllocId;
+
 pub trait PlatformInputHandler {
     fn selected_text_range(&self) -> Option<Range<usize>>;
     fn marked_text_range(&self) -> Option<Range<usize>>;

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

@@ -2,11 +2,11 @@
 ///! an origin at the bottom left of the main display.
 mod dispatcher;
 mod events;
+mod metal_atlas;
 mod metal_renderer;
 mod open_type;
 mod platform;
 mod screen;
-mod sprite;
 mod text_system;
 mod window;
 mod window_appearence;
@@ -31,9 +31,9 @@ use std::{
 };
 
 pub use dispatcher::*;
+pub use metal_atlas::*;
 pub use platform::*;
 pub use screen::*;
-pub use sprite::*;
 pub use text_system::*;
 pub use window::*;
 

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

@@ -0,0 +1,164 @@
+use crate::{AtlasTextureId, AtlasTile, Bounds, DevicePixels, PlatformAtlas, Point, Size};
+use collections::HashMap;
+use etagere::BucketedAtlasAllocator;
+use foreign_types::ForeignType;
+use metal::{Device, TextureDescriptor, TextureDescriptorRef};
+use objc::{msg_send, sel, sel_impl};
+use parking_lot::{RwLock, RwLockUpgradableReadGuard};
+use std::hash::Hash;
+
+pub struct MetalAtlas<Key>(RwLock<MetalAtlasState<Key>>);
+
+struct MetalAtlasState<Key> {
+    device: Device,
+    texture_descriptor: TextureDescriptor,
+    textures: Vec<MetalAtlasTexture>,
+    tiles_by_key: HashMap<Key, AtlasTile>,
+}
+
+impl<Key> PlatformAtlas<Key> for MetalAtlas<Key>
+where
+    Key: Eq + Hash,
+{
+    fn get_or_insert_with(
+        &self,
+        key: Key,
+        build: impl FnOnce() -> (Size<DevicePixels>, Vec<u8>),
+    ) -> AtlasTile {
+        let lock = self.0.upgradable_read();
+        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()
+                .rev()
+                .find_map(|texture| texture.allocate(size, &bytes))
+                .unwrap_or_else(|| {
+                    let texture = lock.push_texture(size);
+                    texture
+                        .allocate(size, &bytes)
+                        .expect("could not allocate a tile in new texture")
+                })
+        }
+    }
+
+    fn clear(&self) {
+        self.0.write().tiles_by_key.clear();
+    }
+}
+
+impl<Key> MetalAtlasState<Key> {
+    fn push_texture(&mut self, min_size: Size<DevicePixels>) -> &mut MetalAtlasTexture {
+        let default_atlas_size = Size {
+            width: self.texture_descriptor.width().into(),
+            height: self.texture_descriptor.height().into(),
+        };
+        let size;
+        let metal_texture;
+
+        if min_size.width > default_atlas_size.width || min_size.height > default_atlas_size.height
+        {
+            let descriptor = unsafe {
+                let descriptor_ptr: *mut metal::MTLTextureDescriptor =
+                    msg_send![self.texture_descriptor, copy];
+                metal::TextureDescriptor::from_ptr(descriptor_ptr)
+            };
+            descriptor.set_width(min_size.width.into());
+            descriptor.set_height(min_size.height.into());
+
+            size = min_size;
+            metal_texture = self.device.new_texture(&descriptor);
+        } else {
+            size = default_atlas_size;
+            metal_texture = self.device.new_texture(&self.texture_descriptor);
+        }
+
+        let atlas_texture = MetalAtlasTexture {
+            id: AtlasTextureId(self.textures.len()),
+            allocator: etagere::BucketedAtlasAllocator::new(size.into()),
+            metal_texture,
+        };
+        self.textures.push(atlas_texture);
+        self.textures.last_mut().unwrap()
+    }
+}
+
+struct MetalAtlasTexture {
+    id: AtlasTextureId,
+    allocator: BucketedAtlasAllocator,
+    metal_texture: metal::Texture,
+}
+
+impl MetalAtlasTexture {
+    fn allocate(&mut self, size: Size<DevicePixels>, bytes: &[u8]) -> Option<AtlasTile> {
+        let size = size.into();
+        let allocation = self.allocator.allocate(size)?;
+        let tile = AtlasTile {
+            texture_id: self.id,
+            tile_id: allocation.id,
+            bounds_in_atlas: allocation.rectangle.into(),
+        };
+        let region = metal::MTLRegion::new_2d(
+            u32::from(tile.bounds_in_atlas.origin.x) as u64,
+            u32::from(tile.bounds_in_atlas.origin.y) as u64,
+            u32::from(tile.bounds_in_atlas.size.width) as u64,
+            u32::from(tile.bounds_in_atlas.size.height) as u64,
+        );
+        self.metal_texture.replace_region(
+            region,
+            0,
+            bytes.as_ptr() as *const _,
+            u32::from(
+                tile.bounds_in_atlas
+                    .size
+                    .width
+                    .to_bytes(self.bytes_per_pixel()),
+            ) as u64,
+        );
+        Some(tile)
+    }
+
+    fn bytes_per_pixel(&self) -> u8 {
+        use metal::MTLPixelFormat::*;
+        match self.metal_texture.pixel_format() {
+            A8Unorm | R8Unorm => 1,
+            RGBA8Unorm | BGRA8Unorm => 4,
+            _ => unimplemented!(),
+        }
+    }
+}
+
+impl From<Size<DevicePixels>> for etagere::Size {
+    fn from(size: Size<DevicePixels>) -> Self {
+        etagere::Size::new(u32::from(size.width) as i32, u32::from(size.width) as i32)
+    }
+}
+
+impl From<etagere::Point> for Point<DevicePixels> {
+    fn from(value: etagere::Point) -> Self {
+        Point {
+            x: DevicePixels::from(value.x as u32),
+            y: DevicePixels::from(value.y as u32),
+        }
+    }
+}
+
+impl From<etagere::Size> for Size<DevicePixels> {
+    fn from(size: etagere::Size) -> Self {
+        Size {
+            width: DevicePixels::from(size.width as u32),
+            height: DevicePixels::from(size.height as u32),
+        }
+    }
+}
+
+impl From<etagere::Rectangle> for Bounds<DevicePixels> {
+    fn from(rectangle: etagere::Rectangle) -> Self {
+        Bounds {
+            origin: rectangle.min.into(),
+            size: rectangle.size().into(),
+        }
+    }
+}

crates/gpui3/src/scene.rs 🔗

@@ -1,7 +1,7 @@
 use std::{iter::Peekable, mem};
 
 use super::{Bounds, Hsla, Pixels, Point};
-use crate::{Corners, DevicePixels, Edges};
+use crate::{AtlasTile, Corners, DevicePixels, Edges};
 use bytemuck::{Pod, Zeroable};
 
 // Exported to metal
@@ -243,10 +243,8 @@ impl From<Quad> for Primitive {
 pub struct MonochromeSprite {
     pub order: u32,
     pub bounds: Bounds<Pixels>,
-    pub atlas_id: AtlasId,
-    pub tile_id: TileId,
-    pub bounds_in_atlas: Bounds<DevicePixels>,
-    pub color: Option<Hsla>,
+    pub color: Hsla,
+    pub tile: AtlasTile,
 }
 
 impl MonochromeSprite {