Use `AtlasAllocator` in `SpriteCache`

Antonio Scandurra and Nathan Sobo created

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

Change summary

gpui/src/platform/mac/renderer.rs     |  13 ++-
gpui/src/platform/mac/sprite_cache.rs | 101 +++-------------------------
2 files changed, 20 insertions(+), 94 deletions(-)

Detailed changes

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

@@ -598,11 +598,6 @@ impl Renderer {
             mem::size_of::<shaders::vector_float2>() as u64,
             [drawable_size.to_float2()].as_ptr() as *const c_void,
         );
-        command_encoder.set_vertex_bytes(
-            shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexAtlasSize as u64,
-            mem::size_of::<shaders::vector_float2>() as u64,
-            [self.sprite_cache.atlas_size().to_float2()].as_ptr() as *const c_void,
-        );
 
         for (atlas_id, sprites) in sprites_by_atlas {
             align_offset(offset);
@@ -612,13 +607,19 @@ impl Renderer {
                 "instance buffer exhausted"
             );
 
+            let texture = self.sprite_cache.atlas_texture(atlas_id).unwrap();
             command_encoder.set_vertex_buffer(
                 shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexSprites as u64,
                 Some(&self.instances),
                 *offset as u64,
             );
+            command_encoder.set_vertex_bytes(
+                shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexAtlasSize as u64,
+                mem::size_of::<shaders::vector_float2>() as u64,
+                [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
+                    as *const c_void,
+            );
 
-            let texture = self.sprite_cache.atlas_texture(atlas_id).unwrap();
             command_encoder.set_fragment_texture(
                 shaders::GPUISpriteFragmentInputIndex_GPUISpriteFragmentInputIndexAtlas as u64,
                 Some(texture),

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

@@ -1,12 +1,9 @@
+use super::atlas::AtlasAllocator;
 use crate::{
     fonts::{FontId, GlyphId},
-    geometry::{
-        rect::RectI,
-        vector::{vec2f, vec2i, Vector2F, Vector2I},
-    },
+    geometry::vector::{vec2f, Vector2F, Vector2I},
     platform,
 };
-use etagere::BucketedAtlasAllocator;
 use metal::{MTLPixelFormat, TextureDescriptor};
 use ordered_float::OrderedFloat;
 use std::{borrow::Cow, collections::HashMap, sync::Arc};
@@ -42,10 +39,8 @@ pub struct IconSprite {
 }
 
 pub struct SpriteCache {
-    device: metal::Device,
-    atlas_size: Vector2I,
     fonts: Arc<dyn platform::FontSystem>,
-    atlases: Vec<Atlas>,
+    atlases: AtlasAllocator,
     glyphs: HashMap<GlyphDescriptor, Option<GlyphSprite>>,
     icons: HashMap<IconDescriptor, IconSprite>,
 }
@@ -56,21 +51,18 @@ impl SpriteCache {
         size: Vector2I,
         fonts: Arc<dyn platform::FontSystem>,
     ) -> Self {
-        let atlases = vec![Atlas::new(&device, size)];
+        let descriptor = TextureDescriptor::new();
+        descriptor.set_pixel_format(MTLPixelFormat::A8Unorm);
+        descriptor.set_width(size.x() as u64);
+        descriptor.set_height(size.y() as u64);
         Self {
-            device,
-            atlas_size: size,
             fonts,
-            atlases,
+            atlases: AtlasAllocator::new(device, descriptor),
             glyphs: Default::default(),
             icons: Default::default(),
         }
     }
 
-    pub fn atlas_size(&self) -> Vector2I {
-        self.atlas_size
-    }
-
     pub fn render_glyph(
         &mut self,
         font_id: FontId,
@@ -84,8 +76,6 @@ impl SpriteCache {
         let target_position = target_position * scale_factor;
         let fonts = &self.fonts;
         let atlases = &mut self.atlases;
-        let atlas_size = self.atlas_size;
-        let device = &self.device;
         let subpixel_variant = (
             (target_position.x().fract() * SUBPIXEL_VARIANTS as f32).round() as u8
                 % SUBPIXEL_VARIANTS,
@@ -111,22 +101,10 @@ impl SpriteCache {
                     subpixel_shift,
                     scale_factor,
                 )?;
-                assert!(glyph_bounds.width() < atlas_size.x());
-                assert!(glyph_bounds.height() < atlas_size.y());
-
-                let atlas_bounds = atlases
-                    .last_mut()
-                    .unwrap()
-                    .try_insert(glyph_bounds.size(), &mask)
-                    .unwrap_or_else(|| {
-                        let mut atlas = Atlas::new(device, atlas_size);
-                        let bounds = atlas.try_insert(glyph_bounds.size(), &mask).unwrap();
-                        atlases.push(atlas);
-                        bounds
-                    });
 
+                let (alloc_id, atlas_bounds) = atlases.upload(glyph_bounds.size(), &mask);
                 Some(GlyphSprite {
-                    atlas_id: atlases.len() - 1,
+                    atlas_id: alloc_id.atlas_id,
                     atlas_origin: atlas_bounds.origin(),
                     offset: glyph_bounds.origin(),
                     size: glyph_bounds.size(),
@@ -142,10 +120,6 @@ impl SpriteCache {
         svg: usvg::Tree,
     ) -> IconSprite {
         let atlases = &mut self.atlases;
-        let atlas_size = self.atlas_size;
-        let device = &self.device;
-        assert!(size.x() < atlas_size.x());
-        assert!(size.y() < atlas_size.y());
         self.icons
             .entry(IconDescriptor {
                 path,
@@ -161,19 +135,9 @@ impl SpriteCache {
                     .map(|a| a.alpha())
                     .collect::<Vec<_>>();
 
-                let atlas_bounds = atlases
-                    .last_mut()
-                    .unwrap()
-                    .try_insert(size, &mask)
-                    .unwrap_or_else(|| {
-                        let mut atlas = Atlas::new(device, atlas_size);
-                        let bounds = atlas.try_insert(size, &mask).unwrap();
-                        atlases.push(atlas);
-                        bounds
-                    });
-
+                let (alloc_id, atlas_bounds) = atlases.upload(size, &mask);
                 IconSprite {
-                    atlas_id: atlases.len() - 1,
+                    atlas_id: alloc_id.atlas_id,
                     atlas_origin: atlas_bounds.origin(),
                     size,
                 }
@@ -182,45 +146,6 @@ impl SpriteCache {
     }
 
     pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
-        self.atlases.get(atlas_id).map(|a| a.texture.as_ref())
-    }
-}
-
-struct Atlas {
-    allocator: BucketedAtlasAllocator,
-    texture: metal::Texture,
-}
-
-impl Atlas {
-    fn new(device: &metal::DeviceRef, size: Vector2I) -> Self {
-        let descriptor = TextureDescriptor::new();
-        descriptor.set_pixel_format(MTLPixelFormat::A8Unorm);
-        descriptor.set_width(size.x() as u64);
-        descriptor.set_height(size.y() as u64);
-
-        Self {
-            allocator: BucketedAtlasAllocator::new(etagere::Size::new(size.x(), size.y())),
-            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() + 1, size.y() + 1))?;
-
-        let bounds = allocation.rectangle;
-        let region = metal::MTLRegion::new_2d(
-            bounds.min.x as u64,
-            bounds.min.y as u64,
-            size.x() as u64,
-            size.y() 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),
-        ))
+        self.atlases.texture(atlas_id)
     }
 }