WIP

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

Cargo.lock                                  |  47 ++++++++
gpui/Cargo.toml                             |   1 
gpui/build.rs                               |   3 
gpui/src/app.rs                             |   1 
gpui/src/fonts.rs                           | 116 ++++++++++++++-------
gpui/src/platform/mac/app.rs                |   5 
gpui/src/platform/mac/renderer.rs           | 121 +++++++++++++---------
gpui/src/platform/mac/shaders/shaders.h     |  16 +-
gpui/src/platform/mac/shaders/shaders.metal |  42 +++++++
gpui/src/platform/mac/sprite_cache.rs       |  67 ++++++++++--
gpui/src/platform/mac/window.rs             |   6 
gpui/src/platform/mod.rs                    |   3 
gpui/src/scene.rs                           |   2 
gpui/src/text_layout.rs                     |   2 
14 files changed, 313 insertions(+), 119 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1,5 +1,11 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+[[package]]
+name = "adler32"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
+
 [[package]]
 name = "aho-corasick"
 version = "0.7.15"
@@ -424,6 +430,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "crc32fast"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
 [[package]]
 name = "crossbeam-channel"
 version = "0.4.4"
@@ -477,6 +492,16 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "deflate"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
+dependencies = [
+ "adler32",
+ "byteorder",
+]
+
 [[package]]
 name = "dirs"
 version = "3.0.1"
@@ -776,6 +801,7 @@ dependencies = [
  "pathfinder_color",
  "pathfinder_geometry",
  "pin-project",
+ "png",
  "rand 0.8.3",
  "replace_with",
  "smallvec",
@@ -923,6 +949,15 @@ dependencies = [
  "objc",
 ]
 
+[[package]]
+name = "miniz_oxide"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
+dependencies = [
+ "adler32",
+]
+
 [[package]]
 name = "nb-connect"
 version = "1.0.3"
@@ -1103,6 +1138,18 @@ version = "0.3.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
 
+[[package]]
+name = "png"
+version = "0.16.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
+dependencies = [
+ "bitflags",
+ "crc32fast",
+ "deflate",
+ "miniz_oxide",
+]
+
 [[package]]
 name = "polling"
 version = "2.0.2"

gpui/Cargo.toml 🔗

@@ -14,6 +14,7 @@ parking_lot = "0.11.1"
 pathfinder_color = "0.5"
 pathfinder_geometry = "0.5"
 pin-project = "1.0.5"
+png = "0.16"
 rand = "0.8.3"
 replace_with = "0.1.7"
 smallvec = "1.6.1"

gpui/build.rs 🔗

@@ -89,7 +89,8 @@ fn generate_shader_bindings() {
         .whitelist_type("GPUIQuad")
         .whitelist_type("GPUIShadowInputIndex")
         .whitelist_type("GPUIShadow")
-        .whitelist_type("GPUISpriteInputIndex")
+        .whitelist_type("GPUISpriteVertexInputIndex")
+        .whitelist_type("GPUISpriteFragmentInputIndex")
         .whitelist_type("GPUISprite")
         .parse_callbacks(Box::new(bindgen::CargoCallbacks))
         .generate()

gpui/src/app.rs 🔗

@@ -620,6 +620,7 @@ impl MutableAppContext {
                 title: "Zed".into(),
             },
             self.foreground.clone(),
+            self.fonts.clone(),
         ) {
             Err(e) => log::error!("error opening window: {}", e),
             Ok(mut window) => {

gpui/src/fonts.rs 🔗

@@ -1,5 +1,10 @@
-use crate::geometry::vector::{vec2f, Vector2F};
+use crate::geometry::{
+    rect::RectI,
+    vector::{vec2f, Vector2F, Vector2I},
+};
 use anyhow::{anyhow, Result};
+use cocoa::appkit::CGPoint;
+use core_graphics::{base::CGGlyph, color_space::CGColorSpace, context::CGContext};
 use parking_lot::{RwLock, RwLockUpgradableReadGuard};
 
 pub use font_kit::properties::{Properties, Weight};
@@ -9,6 +14,9 @@ use font_kit::{
 use ordered_float::OrderedFloat;
 use std::{collections::HashMap, sync::Arc};
 
+#[allow(non_upper_case_globals)]
+const kCGImageAlphaOnly: u32 = 7;
+
 pub type GlyphId = u32;
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
@@ -179,47 +187,43 @@ impl FontCache {
         self.scale_metric(self.metric(font_id, |m| m.descent), font_id, font_size)
     }
 
-    // pub fn render_emoji(&self, glyph_id: GlyphId, font_size: f32) -> Result<Pattern> {
-    //     let key = (glyph_id, OrderedFloat(font_size));
-
-    //     {
-    //         if let Some(image) = self.0.read().emoji_images.get(&key) {
-    //             return Ok(image.clone());
-    //         }
-    //     }
-
-    //     let font_id = self.emoji_font_id()?;
-    //     let bounding_box = self.bounding_box(font_id, font_size);
-    //     let width = (4.0 * bounding_box.x()) as usize;
-    //     let height = (4.0 * bounding_box.y()) as usize;
-    //     let mut ctx = CGContext::create_bitmap_context(
-    //         None,
-    //         width,
-    //         height,
-    //         8,
-    //         width * 4,
-    //         &CGColorSpace::create_device_rgb(),
-    //         kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault,
-    //     );
-    //     ctx.scale(4.0, 4.0);
-
-    //     let native_font = self.native_font(font_id, font_size);
-    //     let glyph = glyph_id.0 as CGGlyph;
-    //     let glyph_bounds = native_font.get_bounding_rects_for_glyphs(Default::default(), &[glyph]);
-    //     let position = CGPoint::new(glyph_bounds.origin.x, -glyph_bounds.origin.y);
-
-    //     native_font.draw_glyphs(&[glyph], &[position], ctx.clone());
-
-    //     ctx.flush();
-
-    //     let image = Pattern::from_image(Image::new(
-    //         vec2i(ctx.width() as i32, ctx.height() as i32),
-    //         Arc::new(u8_slice_to_color_slice(&ctx.data()).into()),
-    //     ));
-    //     self.0.write().emoji_images.insert(key, image.clone());
-
-    //     Ok(image)
-    // }
+    pub fn render_glyph(
+        &self,
+        font_id: FontId,
+        font_size: f32,
+        glyph_id: GlyphId,
+        scale_factor: f32,
+    ) -> Option<(Vector2I, Vec<u8>)> {
+        let native_font = self.native_font(font_id, font_size);
+        let glyph_id = glyph_id as CGGlyph;
+        let glyph_bounds =
+            native_font.get_bounding_rects_for_glyphs(Default::default(), &[glyph_id]);
+        let position = CGPoint::new(-glyph_bounds.origin.x, -glyph_bounds.origin.y);
+        let width = (glyph_bounds.size.width * scale_factor as f64).ceil() as usize;
+        let height = (glyph_bounds.size.height * scale_factor as f64).ceil() as usize;
+
+        if width == 0 || height == 0 {
+            None
+        } else {
+            let mut ctx = CGContext::create_bitmap_context(
+                None,
+                width,
+                height,
+                8,
+                width,
+                &CGColorSpace::create_device_gray(),
+                kCGImageAlphaOnly,
+            );
+            ctx.scale(scale_factor as f64, scale_factor as f64);
+            native_font.draw_glyphs(&[glyph_id], &[position], ctx.clone());
+            ctx.flush();
+
+            Some((
+                Vector2I::new(width as i32, height as i32),
+                Vec::from(ctx.data()),
+            ))
+        }
+    }
 
     fn emoji_font_id(&self) -> Result<FontId> {
         let state = self.0.upgradable_read();
@@ -285,3 +289,31 @@ fn push_font(state: &mut FontCacheState, font: Font) -> FontId {
     state.fonts_by_name.insert(name, font_id);
     font_id
 }
+
+#[cfg(test)]
+mod tests {
+    use std::{fs::File, io::BufWriter, path::Path};
+
+    use super::*;
+
+    #[test]
+    fn test_render_glyph() {
+        let cache = FontCache::new();
+        let family_id = cache.load_family(&["Fira Code"]).unwrap();
+        let font_id = cache.select_font(family_id, &Default::default()).unwrap();
+        let glyph_id = cache.font(font_id).glyph_for_char('m').unwrap();
+        let (size, bytes) = cache.render_glyph(font_id, 16.0, glyph_id, 1.).unwrap();
+
+        let path = Path::new(r"/Users/as-cii/Desktop/image.png");
+        let file = File::create(path).unwrap();
+        let ref mut w = BufWriter::new(file);
+
+        let mut encoder = png::Encoder::new(w, size.x() as u32, size.y() as u32);
+        encoder.set_color(png::ColorType::Grayscale);
+        encoder.set_depth(png::BitDepth::Eight);
+        let mut writer = encoder.write_header().unwrap();
+
+        writer.write_image_data(&bytes).unwrap(); // Save
+        dbg!(size, bytes);
+    }
+}

gpui/src/platform/mac/app.rs 🔗

@@ -1,5 +1,5 @@
 use super::{BoolExt as _, Dispatcher, Window};
-use crate::{executor, platform};
+use crate::{executor, platform, FontCache};
 use anyhow::Result;
 use cocoa::base::id;
 use objc::{class, msg_send, sel, sel_impl};
@@ -33,7 +33,8 @@ impl platform::App for App {
         &self,
         options: platform::WindowOptions,
         executor: Rc<executor::Foreground>,
+        font_cache: Arc<FontCache>,
     ) -> Result<Box<dyn platform::Window>> {
-        Ok(Box::new(Window::open(options, executor)?))
+        Ok(Box::new(Window::open(options, executor, font_cache)?))
     }
 }

gpui/src/platform/mac/renderer.rs 🔗

@@ -3,17 +3,16 @@ use crate::{
     color::ColorU,
     geometry::vector::{vec2i, Vector2I},
     scene::Layer,
-    Scene,
+    FontCache, Scene,
 };
 use anyhow::{anyhow, Result};
 use metal::{MTLResourceOptions, NSRange};
-use shaders::{ToFloat2 as _, ToUchar4 as _, ToUint2 as _};
-use std::{collections::HashMap, ffi::c_void, mem};
+use shaders::{ToFloat2 as _, ToUchar4 as _};
+use std::{collections::HashMap, ffi::c_void, mem, sync::Arc};
 
 const SHADERS_METALLIB: &'static [u8] =
     include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
 const INSTANCE_BUFFER_SIZE: usize = 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
-const ATLAS_SIZE: Vector2I = vec2i(1024, 768);
 
 pub struct Renderer {
     sprite_cache: SpriteCache,
@@ -25,7 +24,11 @@ pub struct Renderer {
 }
 
 impl Renderer {
-    pub fn new(device: metal::Device, pixel_format: metal::MTLPixelFormat) -> Result<Self> {
+    pub fn new(
+        device: metal::Device,
+        pixel_format: metal::MTLPixelFormat,
+        font_cache: Arc<FontCache>,
+    ) -> Result<Self> {
         let library = device
             .new_library_with_data(SHADERS_METALLIB)
             .map_err(|message| anyhow!("error building metal library: {}", message))?;
@@ -48,8 +51,9 @@ impl Renderer {
             MTLResourceOptions::StorageModeManaged,
         );
 
+        let atlas_size: Vector2I = vec2i(1024, 768);
         Ok(Self {
-            sprite_cache: SpriteCache::new(device, ATLAS_SIZE),
+            sprite_cache: SpriteCache::new(device.clone(), atlas_size, font_cache),
             quad_pipeline_state: build_pipeline_state(
                 &device,
                 &library,
@@ -261,43 +265,35 @@ impl Renderer {
             return;
         }
 
-        align_offset(offset);
-        let next_offset = *offset + layer.glyphs().len() * mem::size_of::<shaders::GPUISprite>();
-        assert!(
-            next_offset <= INSTANCE_BUFFER_SIZE,
-            "instance buffer exhausted"
-        );
-
-        let mut sprites = HashMap::new();
+        let mut sprites_by_atlas = HashMap::new();
         for glyph in layer.glyphs() {
-            let (atlas, bounds) =
-                self.sprite_cache
-                    .render_glyph(glyph.font_id, glyph.font_size, glyph.glyph_id);
-            sprites
-                .entry(atlas)
-                .or_insert_with(Vec::new)
-                .push(shaders::GPUISprite {
-                    origin: glyph.origin.to_float2(),
-                    size: bounds.size().to_uint2(),
-                    atlas_origin: bounds.origin().to_uint2(),
-                    color: glyph.color.to_uchar4(),
-                });
+            if let Some((atlas, bounds)) = self.sprite_cache.render_glyph(
+                glyph.font_id,
+                glyph.font_size,
+                glyph.id,
+                scene.scale_factor(),
+            ) {
+                sprites_by_atlas
+                    .entry(atlas)
+                    .or_insert_with(Vec::new)
+                    .push(shaders::GPUISprite {
+                        origin: (glyph.origin * scene.scale_factor()).to_float2(),
+                        size: (bounds.size().to_f32() * scene.scale_factor()).to_float2(),
+                        atlas_origin: bounds.origin().to_float2(),
+                        color: glyph.color.to_uchar4(),
+                    });
+            }
         }
 
         ctx.command_encoder
             .set_render_pipeline_state(&self.sprite_pipeline_state);
         ctx.command_encoder.set_vertex_buffer(
-            shaders::GPUISpriteInputIndex_GPUISpriteInputIndexVertices as u64,
+            shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexVertices as u64,
             Some(&self.unit_vertices),
             0,
         );
-        ctx.command_encoder.set_vertex_buffer(
-            shaders::GPUISpriteInputIndex_GPUISpriteInputIndexSprites as u64,
-            Some(&self.instances),
-            *offset as u64,
-        );
         ctx.command_encoder.set_vertex_bytes(
-            shaders::GPUISpriteInputIndex_GPUISpriteInputIndexUniforms as u64,
+            shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexUniforms as u64,
             mem::size_of::<shaders::GPUIUniforms>() as u64,
             [shaders::GPUIUniforms {
                 viewport_size: ctx.drawable_size.to_float2(),
@@ -310,8 +306,41 @@ impl Renderer {
                 as *mut shaders::GPUISprite
         };
 
-        for glyph in layer.glyphs() {
-            let sprite = self.sprite_cache.rasterize_glyph();
+        for (atlas_id, sprites) in sprites_by_atlas {
+            align_offset(offset);
+            let next_offset = *offset + sprites.len() * mem::size_of::<shaders::GPUISprite>();
+            assert!(
+                next_offset <= INSTANCE_BUFFER_SIZE,
+                "instance buffer exhausted"
+            );
+
+            ctx.command_encoder.set_vertex_buffer(
+                shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexSprites as u64,
+                Some(&self.instances),
+                *offset as u64,
+            );
+
+            let texture = self.sprite_cache.atlas_texture(atlas_id).unwrap();
+            ctx.command_encoder.set_fragment_texture(
+                shaders::GPUISpriteFragmentInputIndex_GPUISpriteFragmentInputIndexAtlas as u64,
+                Some(texture),
+            );
+
+            unsafe {
+                std::ptr::copy_nonoverlapping(sprites.as_ptr(), buffer_contents, sprites.len());
+            }
+            self.instances.did_modify_range(NSRange {
+                location: *offset as u64,
+                length: (next_offset - *offset) as u64,
+            });
+            *offset = next_offset;
+
+            ctx.command_encoder.draw_primitives_instanced(
+                metal::MTLPrimitiveType::Triangle,
+                0,
+                6,
+                sprites.len() as u64,
+            );
         }
     }
 }
@@ -377,10 +406,6 @@ mod shaders {
         fn to_uchar4(&self) -> vector_uchar4;
     }
 
-    pub trait ToUint2 {
-        fn to_uint2(&self) -> vector_uint2;
-    }
-
     impl ToFloat2 for (f32, f32) {
         fn to_float2(&self) -> vector_float2 {
             unsafe {
@@ -403,6 +428,15 @@ mod shaders {
         }
     }
 
+    impl ToFloat2 for Vector2I {
+        fn to_float2(&self) -> vector_float2 {
+            let mut output = self.y() as vector_float2;
+            output <<= 32;
+            output |= self.x() as vector_float2;
+            output
+        }
+    }
+
     impl ToUchar4 for ColorU {
         fn to_uchar4(&self) -> vector_uchar4 {
             let mut vec = self.a as vector_uchar4;
@@ -415,13 +449,4 @@ mod shaders {
             vec
         }
     }
-
-    impl ToUint2 for Vector2I {
-        fn to_uint2(&self) -> vector_uint2 {
-            let mut output = self.y() as vector_uint2;
-            output <<= 32;
-            output |= self.x() as vector_uint2;
-            output
-        }
-    }
 }

gpui/src/platform/mac/shaders/shaders.h 🔗

@@ -37,14 +37,18 @@ typedef struct {
 } GPUIShadow;
 
 typedef enum {
-    GPUISpriteInputIndexVertices = 0,
-    GPUISpriteInputIndexSprites = 1,
-    GPUISpriteInputIndexUniforms = 2,
-} GPUISpriteInputIndex;
+    GPUISpriteVertexInputIndexVertices = 0,
+    GPUISpriteVertexInputIndexSprites = 1,
+    GPUISpriteVertexInputIndexUniforms = 2,
+} GPUISpriteVertexInputIndex;
+
+typedef enum {
+    GPUISpriteFragmentInputIndexAtlas = 0,
+} GPUISpriteFragmentInputIndex;
 
 typedef struct {
     vector_float2 origin;
-    vector_uint2 size;
-    vector_uint2 atlas_origin;
+    vector_float2 size;
+    vector_float2 atlas_origin;
     vector_uchar4 color;
 } GPUISprite;

gpui/src/platform/mac/shaders/shaders.metal 🔗

@@ -56,8 +56,7 @@ vertex QuadFragmentInput quad_vertex(
 }
 
 fragment float4 quad_fragment(
-    QuadFragmentInput input [[stage_in]],
-    constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
+    QuadFragmentInput input [[stage_in]]
 ) {
     float2 half_size = input.quad.size / 2.;
     float2 center = input.quad.origin + half_size;
@@ -115,8 +114,7 @@ vertex ShadowFragmentInput shadow_vertex(
 }
 
 fragment float4 shadow_fragment(
-    ShadowFragmentInput input [[stage_in]],
-    constant GPUIUniforms *uniforms [[buffer(GPUIShadowInputIndexUniforms)]]
+    ShadowFragmentInput input [[stage_in]]
 ) {
     float sigma = input.shadow.sigma;
     float corner_radius = input.shadow.corner_radius;
@@ -141,3 +139,39 @@ fragment float4 shadow_fragment(
 
     return float4(1., 1., 1., alpha) * coloru_to_colorf(input.shadow.color);
 }
+
+struct SpriteFragmentInput {
+    float4 position [[position]];
+    float2 atlas_position;
+    float4 color [[flat]];
+};
+
+vertex SpriteFragmentInput sprite_vertex(
+    uint unit_vertex_id [[vertex_id]],
+    uint sprite_id [[instance_id]],
+    constant float2 *unit_vertices [[buffer(GPUISpriteVertexInputIndexVertices)]],
+    constant GPUISprite *sprites [[buffer(GPUISpriteVertexInputIndexSprites)]],
+    constant GPUIUniforms *uniforms [[buffer(GPUISpriteVertexInputIndexUniforms)]]
+) {
+    float2 unit_vertex = unit_vertices[unit_vertex_id];
+    GPUISprite sprite = sprites[sprite_id];
+    float2 position = unit_vertex * sprite.size + sprite.origin;
+    float2 atlas_position = unit_vertex * sprite.size + sprite.atlas_origin;
+    float4 device_position = to_device_position(position, uniforms->viewport_size);
+
+    return SpriteFragmentInput {
+        device_position,
+        atlas_position,
+        coloru_to_colorf(sprite.color),
+    };
+}
+
+fragment float4 sprite_fragment(
+    SpriteFragmentInput input [[stage_in]],
+    texture2d<float> atlas [[ texture(GPUISpriteFragmentInputIndexAtlas) ]]
+) {
+    constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
+    float4 color = input.color;
+    color.a *= atlas.sample(atlas_sampler, input.atlas_position).r;
+    return color;
+}

gpui/src/platform/mac/sprite_cache.rs 🔗

@@ -2,7 +2,10 @@ use std::{collections::HashMap, sync::Arc};
 
 use crate::{
     fonts::{FontId, GlyphId},
-    geometry::{rect::RectI, vector::Vector2I},
+    geometry::{
+        rect::RectI,
+        vector::{vec2i, Vector2I},
+    },
     FontCache,
 };
 use etagere::BucketedAtlasAllocator;
@@ -17,19 +20,21 @@ struct GlyphDescriptor {
 }
 
 pub struct SpriteCache {
-    font_cache: Arc<FontCache>,
     device: metal::Device,
-    size: Vector2I,
+    atlas_size: Vector2I,
+    font_cache: Arc<FontCache>,
     atlasses: Vec<Atlas>,
-    glyphs: HashMap<GlyphDescriptor, (usize, RectI)>,
+    glyphs: HashMap<GlyphDescriptor, Option<(usize, RectI)>>,
 }
 
 impl SpriteCache {
-    pub fn new(device: metal::Device, size: Vector2I) -> Self {
+    pub fn new(device: metal::Device, size: Vector2I, font_cache: Arc<FontCache>) -> Self {
+        let atlasses = vec![Atlas::new(&device, size)];
         Self {
             device,
-            size,
-            atlasses: vec![Atlas::new(&device, size)],
+            atlas_size: size,
+            font_cache,
+            atlasses,
             glyphs: Default::default(),
         }
     }
@@ -39,7 +44,12 @@ impl SpriteCache {
         font_id: FontId,
         font_size: f32,
         glyph_id: GlyphId,
-    ) -> (usize, RectI) {
+        scale_factor: f32,
+    ) -> Option<(usize, RectI)> {
+        let font_cache = &self.font_cache;
+        let atlasses = &mut self.atlasses;
+        let atlas_size = self.atlas_size;
+        let device = &self.device;
         self.glyphs
             .entry(GlyphDescriptor {
                 font_id,
@@ -47,12 +57,27 @@ impl SpriteCache {
                 glyph_id,
             })
             .or_insert_with(|| {
-                let rendered_glyph = self.font_cache.render_glyph(font_id, font_size, glyph_id);
-                // let atlas = self.atlasses.last_mut().unwrap();
-                todo!()
+                let (size, mask) =
+                    font_cache.render_glyph(font_id, font_size, glyph_id, scale_factor)?;
+                assert!(size.x() < atlas_size.x());
+                assert!(size.y() < atlas_size.y());
+
+                let atlas = atlasses.last_mut().unwrap();
+                if let Some(bounds) = atlas.try_insert(size, &mask) {
+                    Some((atlasses.len() - 1, bounds))
+                } else {
+                    let mut atlas = Atlas::new(device, atlas_size);
+                    let bounds = atlas.try_insert(size, &mask).unwrap();
+                    atlasses.push(atlas);
+                    Some((atlasses.len() - 1, bounds))
+                }
             })
             .clone()
     }
+
+    pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
+        self.atlasses.get(atlas_id).map(|a| a.texture.as_ref())
+    }
 }
 
 struct Atlas {
@@ -72,4 +97,24 @@ impl Atlas {
             texture: device.new_texture(&descriptor),
         }
     }
+
+    fn try_insert(&mut self, size: Vector2I, mask: &[u8]) -> Option<RectI> {
+        let allocation = self
+            .allocator
+            .allocate(etagere::size2(size.x(), size.y()))?;
+
+        let bounds = allocation.rectangle;
+        let region = metal::MTLRegion::new_2d(
+            bounds.min.x as u64,
+            bounds.min.y as u64,
+            bounds.width() as u64,
+            bounds.height() as u64,
+        );
+        self.texture
+            .replace_region(region, 0, mask.as_ptr() as *const _, size.x() as u64);
+        Some(RectI::from_points(
+            vec2i(bounds.min.x, bounds.min.y),
+            vec2i(bounds.max.x, bounds.max.y),
+        ))
+    }
 }

gpui/src/platform/mac/window.rs 🔗

@@ -2,7 +2,7 @@ use crate::{
     executor,
     geometry::vector::Vector2F,
     platform::{self, Event, WindowContext},
-    Scene,
+    FontCache, Scene,
 };
 use anyhow::{anyhow, Result};
 use cocoa::{
@@ -31,6 +31,7 @@ use std::{
     ffi::c_void,
     mem, ptr,
     rc::{Rc, Weak},
+    sync::Arc,
     time::Duration,
 };
 
@@ -140,6 +141,7 @@ impl Window {
     pub fn open(
         options: platform::WindowOptions,
         executor: Rc<executor::Foreground>,
+        font_cache: Arc<FontCache>,
     ) -> Result<Self> {
         const PIXEL_FORMAT: metal::MTLPixelFormat = metal::MTLPixelFormat::BGRA8Unorm;
 
@@ -192,7 +194,7 @@ impl Window {
                 synthetic_drag_counter: 0,
                 executor,
                 scene_to_render: Default::default(),
-                renderer: Renderer::new(device, PIXEL_FORMAT)?,
+                renderer: Renderer::new(device.clone(), PIXEL_FORMAT, font_cache)?,
                 command_queue: device.new_command_queue(),
                 device,
                 layer,

gpui/src/platform/mod.rs 🔗

@@ -9,7 +9,7 @@ pub mod current {
 use crate::{
     executor,
     geometry::{rect::RectF, vector::Vector2F},
-    Scene,
+    FontCache, Scene,
 };
 use anyhow::Result;
 use async_task::Runnable;
@@ -32,6 +32,7 @@ pub trait App {
         &self,
         options: WindowOptions,
         executor: Rc<executor::Foreground>,
+        font_cache: Arc<FontCache>,
     ) -> Result<Box<dyn Window>>;
 }
 

gpui/src/scene.rs 🔗

@@ -38,7 +38,7 @@ pub struct Shadow {
 pub struct Glyph {
     pub font_id: FontId,
     pub font_size: f32,
-    pub glyph_id: GlyphId,
+    pub id: GlyphId,
     pub origin: Vector2F,
     pub color: ColorU,
 }

gpui/src/text_layout.rs 🔗

@@ -228,7 +228,7 @@ impl Line {
                 ctx.scene.push_glyph(scene::Glyph {
                     font_id: run.font_id,
                     font_size: self.font_size,
-                    glyph_id: glyph.id,
+                    id: glyph.id,
                     origin: glyph_origin,
                     color,
                 });