Reduce GPU memory usage (#7319)

Antonio Scandurra and Nathan Sobo created

This pull request decreases the size of each instance buffer and shares
instance buffers across windows.

Release Notes:

- Improved GPU memory usage.

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>
# Conflicts:
#	crates/gpui/src/platform/mac/window.rs

Change summary

crates/gpui/src/platform/mac/metal_renderer.rs | 24 ++++++++++---------
crates/gpui/src/platform/mac/platform.rs       | 10 +++++++
crates/gpui/src/platform/mac/window.rs         |  3 +
3 files changed, 24 insertions(+), 13 deletions(-)

Detailed changes

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

@@ -24,7 +24,7 @@ const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shader
 #[cfg(feature = "runtime_shaders")]
 const SHADERS_SOURCE_FILE: &'static str =
     include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal"));
-const INSTANCE_BUFFER_SIZE: usize = 32 * 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value (maybe even we could adjust dynamically...)
+const INSTANCE_BUFFER_SIZE: usize = 2 * 1024 * 1024; // This is an arbitrary decision. There's probably a more optimal value (maybe even we could adjust dynamically...)
 
 pub(crate) struct MetalRenderer {
     device: metal::Device,
@@ -40,13 +40,13 @@ pub(crate) struct MetalRenderer {
     surfaces_pipeline_state: metal::RenderPipelineState,
     unit_vertices: metal::Buffer,
     #[allow(clippy::arc_with_non_send_sync)]
-    instance_buffers: Arc<Mutex<Vec<metal::Buffer>>>,
+    instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>,
     sprite_atlas: Arc<MetalAtlas>,
     core_video_texture_cache: CVMetalTextureCache,
 }
 
 impl MetalRenderer {
-    pub fn new(is_opaque: bool) -> Self {
+    pub fn new(instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>) -> Self {
         let device: metal::Device = if let Some(device) = metal::Device::system_default() {
             device
         } else {
@@ -58,7 +58,7 @@ impl MetalRenderer {
         layer.set_device(&device);
         layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
         layer.set_presents_with_transaction(true);
-        layer.set_opaque(is_opaque);
+        layer.set_opaque(true);
         unsafe {
             let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO];
             let _: () = msg_send![&*layer, setNeedsDisplayOnBoundsChange: YES];
@@ -181,7 +181,7 @@ impl MetalRenderer {
             polychrome_sprites_pipeline_state,
             surfaces_pipeline_state,
             unit_vertices,
-            instance_buffers: Arc::default(),
+            instance_buffer_pool,
             sprite_atlas,
             core_video_texture_cache,
         }
@@ -211,7 +211,7 @@ impl MetalRenderer {
             );
             return;
         };
-        let mut instance_buffer = self.instance_buffers.lock().pop().unwrap_or_else(|| {
+        let mut instance_buffer = self.instance_buffer_pool.lock().pop().unwrap_or_else(|| {
             self.device.new_buffer(
                 INSTANCE_BUFFER_SIZE as u64,
                 MTLResourceOptions::StorageModeManaged,
@@ -227,7 +227,8 @@ impl MetalRenderer {
             &mut instance_offset,
             command_buffer,
         ) else {
-            panic!("failed to rasterize {} paths", scene.paths().len());
+            log::error!("failed to rasterize {} paths", scene.paths().len());
+            return;
         };
 
         let render_pass_descriptor = metal::RenderPassDescriptor::new();
@@ -314,7 +315,7 @@ impl MetalRenderer {
             };
 
             if !ok {
-                panic!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",
+                log::error!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces",
                     scene.paths.len(),
                     scene.shadows.len(),
                     scene.quads.len(),
@@ -322,7 +323,8 @@ impl MetalRenderer {
                     scene.monochrome_sprites.len(),
                     scene.polychrome_sprites.len(),
                     scene.surfaces.len(),
-                )
+                );
+                break;
             }
         }
 
@@ -333,11 +335,11 @@ impl MetalRenderer {
             length: instance_offset as NSUInteger,
         });
 
-        let instance_buffers = self.instance_buffers.clone();
+        let instance_buffer_pool = self.instance_buffer_pool.clone();
         let instance_buffer = Cell::new(Some(instance_buffer));
         let block = ConcreteBlock::new(move |_| {
             if let Some(instance_buffer) = instance_buffer.take() {
-                instance_buffers.lock().push(instance_buffer);
+                instance_buffer_pool.lock().push(instance_buffer);
             }
         });
         let block = block.copy();

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

@@ -146,6 +146,7 @@ pub(crate) struct MacPlatformState {
     foreground_executor: ForegroundExecutor,
     text_system: Arc<MacTextSystem>,
     display_linker: MacDisplayLinker,
+    instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>,
     pasteboard: id,
     text_hash_pasteboard_type: id,
     metadata_pasteboard_type: id,
@@ -176,6 +177,7 @@ impl MacPlatform {
             foreground_executor: ForegroundExecutor::new(dispatcher),
             text_system: Arc::new(MacTextSystem::new()),
             display_linker: MacDisplayLinker::new(),
+            instance_buffer_pool: Arc::default(),
             pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
             text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
             metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
@@ -494,7 +496,13 @@ impl Platform for MacPlatform {
         handle: AnyWindowHandle,
         options: WindowOptions,
     ) -> Box<dyn PlatformWindow> {
-        Box::new(MacWindow::open(handle, options, self.foreground_executor()))
+        let instance_buffer_pool = self.0.lock().instance_buffer_pool.clone();
+        Box::new(MacWindow::open(
+            handle,
+            options,
+            self.foreground_executor(),
+            instance_buffer_pool,
+        ))
     }
 
     fn set_display_link_output_callback(

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

@@ -458,6 +458,7 @@ impl MacWindow {
         handle: AnyWindowHandle,
         options: WindowOptions,
         executor: ForegroundExecutor,
+        instance_buffer_pool: Arc<Mutex<Vec<metal::Buffer>>>,
     ) -> Self {
         unsafe {
             let pool = NSAutoreleasePool::new(nil);
@@ -529,7 +530,7 @@ impl MacWindow {
                 executor,
                 native_window,
                 native_view: NonNull::new_unchecked(native_view as *mut _),
-                renderer: MetalRenderer::new(true),
+                renderer: MetalRenderer::new(instance_buffer_pool),
                 kind: options.kind,
                 request_frame_callback: None,
                 event_callback: None,