Extract image rasterization into `ImageCache`

Antonio Scandurra and Nathan Sobo created

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

Change summary

gpui/src/platform/mac.rs             |  1 
gpui/src/platform/mac/image_cache.rs | 49 +++++++++++++++++++++++++++++
gpui/src/platform/mac/renderer.rs    | 50 ++++-------------------------
3 files changed, 58 insertions(+), 42 deletions(-)

Detailed changes

gpui/src/platform/mac.rs 🔗

@@ -3,6 +3,7 @@ mod dispatcher;
 mod event;
 mod fonts;
 mod geometry;
+mod image_cache;
 mod platform;
 mod renderer;
 mod sprite_cache;

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

@@ -0,0 +1,49 @@
+use metal::{MTLPixelFormat, TextureDescriptor, TextureRef};
+
+use super::atlas::{AllocId, AtlasAllocator};
+use crate::{
+    geometry::{rect::RectI, vector::Vector2I},
+    ImageData,
+};
+use std::{collections::HashMap, mem};
+
+pub struct ImageCache {
+    prev_frame: HashMap<usize, (AllocId, RectI)>,
+    curr_frame: HashMap<usize, (AllocId, RectI)>,
+    atlases: AtlasAllocator,
+}
+
+impl ImageCache {
+    pub fn new(device: metal::Device, size: Vector2I) -> Self {
+        let descriptor = TextureDescriptor::new();
+        descriptor.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
+        descriptor.set_width(size.x() as u64);
+        descriptor.set_height(size.y() as u64);
+        Self {
+            prev_frame: Default::default(),
+            curr_frame: Default::default(),
+            atlases: AtlasAllocator::new(device, descriptor),
+        }
+    }
+
+    pub fn render(&mut self, image: &ImageData) -> (AllocId, RectI) {
+        let (alloc_id, atlas_bounds) = self
+            .prev_frame
+            .remove(&image.id)
+            .or_else(|| self.curr_frame.get(&image.id).copied())
+            .unwrap_or_else(|| self.atlases.upload(image.size(), image.as_bytes()));
+        self.curr_frame.insert(image.id, (alloc_id, atlas_bounds));
+        (alloc_id, atlas_bounds)
+    }
+
+    pub fn finish_frame(&mut self) {
+        mem::swap(&mut self.prev_frame, &mut self.curr_frame);
+        for (_, (id, _)) in self.curr_frame.drain() {
+            self.atlases.deallocate(id);
+        }
+    }
+
+    pub fn atlas_texture(&self, atlas_id: usize) -> Option<&TextureRef> {
+        self.atlases.texture(atlas_id)
+    }
+}

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

@@ -1,11 +1,8 @@
-use super::{
-    atlas::{self, AtlasAllocator},
-    sprite_cache::SpriteCache,
-};
+use super::{atlas::AtlasAllocator, image_cache::ImageCache, sprite_cache::SpriteCache};
 use crate::{
     color::Color,
     geometry::{
-        rect::{RectF, RectI},
+        rect::RectF,
         vector::{vec2f, vec2i, Vector2F},
     },
     platform,
@@ -22,10 +19,8 @@ const INSTANCE_BUFFER_SIZE: usize = 1024 * 1024; // This is an arbitrary decisio
 
 pub struct Renderer {
     sprite_cache: SpriteCache,
+    image_cache: ImageCache,
     path_atlases: AtlasAllocator,
-    image_atlases: AtlasAllocator,
-    prev_rendered_images: HashMap<usize, (atlas::AllocId, RectI)>,
-    curr_rendered_images: HashMap<usize, (atlas::AllocId, RectI)>,
     quad_pipeline_state: metal::RenderPipelineState,
     shadow_pipeline_state: metal::RenderPipelineState,
     sprite_pipeline_state: metal::RenderPipelineState,
@@ -70,10 +65,9 @@ impl Renderer {
         );
 
         let sprite_cache = SpriteCache::new(device.clone(), vec2i(1024, 768), fonts);
+        let image_cache = ImageCache::new(device.clone(), vec2i(1024, 768));
         let path_atlases =
             AtlasAllocator::new(device.clone(), build_path_atlas_texture_descriptor());
-        let image_atlases =
-            AtlasAllocator::new(device.clone(), build_image_atlas_texture_descriptor());
         let quad_pipeline_state = build_pipeline_state(
             &device,
             &library,
@@ -116,10 +110,8 @@ impl Renderer {
         );
         Self {
             sprite_cache,
+            image_cache,
             path_atlases,
-            image_atlases,
-            prev_rendered_images: Default::default(),
-            curr_rendered_images: Default::default(),
             quad_pipeline_state,
             shadow_pipeline_state,
             sprite_pipeline_state,
@@ -139,11 +131,6 @@ impl Renderer {
     ) {
         let mut offset = 0;
 
-        mem::swap(
-            &mut self.curr_rendered_images,
-            &mut self.prev_rendered_images,
-        );
-
         let path_sprites = self.render_path_atlases(scene, &mut offset, command_buffer);
         self.render_layers(
             scene,
@@ -157,11 +144,7 @@ impl Renderer {
             location: 0,
             length: offset as NSUInteger,
         });
-
-        for (id, _) in self.prev_rendered_images.values() {
-            self.image_atlases.deallocate(*id);
-        }
-        self.prev_rendered_images.clear();
+        self.image_cache.finish_frame();
     }
 
     fn render_path_atlases(
@@ -660,16 +643,7 @@ impl Renderer {
             let target_size = image.bounds.size() * scale_factor;
             let corner_radius = image.corner_radius * scale_factor;
             let border_width = image.border.width * scale_factor;
-            let (alloc_id, atlas_bounds) = self
-                .prev_rendered_images
-                .remove(&image.data.id)
-                .or_else(|| self.curr_rendered_images.get(&image.data.id).copied())
-                .unwrap_or_else(|| {
-                    self.image_atlases
-                        .upload(image.data.size(), image.data.as_bytes())
-                });
-            self.curr_rendered_images
-                .insert(image.data.id, (alloc_id, atlas_bounds));
+            let (alloc_id, atlas_bounds) = self.image_cache.render(&image.data);
             images_by_atlas
                 .entry(alloc_id.atlas_id)
                 .or_insert_with(Vec::new)
@@ -707,7 +681,7 @@ impl Renderer {
                 "instance buffer exhausted"
             );
 
-            let texture = self.image_atlases.texture(atlas_id).unwrap();
+            let texture = self.image_cache.atlas_texture(atlas_id).unwrap();
             command_encoder.set_vertex_buffer(
                 shaders::GPUIImageVertexInputIndex_GPUIImageVertexInputIndexImages as u64,
                 Some(&self.instances),
@@ -858,14 +832,6 @@ fn build_path_atlas_texture_descriptor() -> metal::TextureDescriptor {
     texture_descriptor
 }
 
-fn build_image_atlas_texture_descriptor() -> metal::TextureDescriptor {
-    let texture_descriptor = metal::TextureDescriptor::new();
-    texture_descriptor.set_width(2048);
-    texture_descriptor.set_height(2048);
-    texture_descriptor.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
-    texture_descriptor
-}
-
 fn align_offset(offset: &mut usize) {
     let r = *offset % 256;
     if r > 0 {