From 551fd9ba7ee3d5a4c51da110a140965bb25f8569 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 12:40:09 -0700 Subject: [PATCH 1/3] Boop --- .../gpui/src/platform/mac/metal_renderer.rs | 76 +++++++++++-------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index a6cdd166d3..2a1f9ef92d 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -18,7 +18,7 @@ use smallvec::SmallVec; use std::{ffi::c_void, mem, ptr, sync::Arc}; const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); -const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. +const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. [] pub(crate) struct MetalRenderer { layer: metal::MetalLayer, @@ -429,6 +429,13 @@ impl MetalRenderer { let shadow_bytes_len = std::mem::size_of_val(shadows); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + + let next_offset = *offset + shadow_bytes_len; + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); + unsafe { ptr::copy_nonoverlapping( shadows.as_ptr() as *const u8, @@ -437,12 +444,6 @@ impl MetalRenderer { ); } - let next_offset = *offset + shadow_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); - command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, 0, @@ -489,15 +490,15 @@ impl MetalRenderer { let quad_bytes_len = std::mem::size_of_val(quads); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; - unsafe { - ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len); - } let next_offset = *offset + quad_bytes_len; assert!( next_offset <= INSTANCE_BUFFER_SIZE, "instance buffer exhausted" ); + unsafe { + ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len); + } command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, @@ -586,22 +587,32 @@ impl MetalRenderer { command_encoder .set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture)); + // hypothesis: sprites.as_ptr() does something bogus sometimes? + // let sprite_bytes_len = mem::size_of::() * sprites.len(); - let buffer_contents = - unsafe { (self.instances.contents() as *mut u8).add(*offset) }; - unsafe { - ptr::copy_nonoverlapping( - sprites.as_ptr() as *const u8, - buffer_contents, - sprite_bytes_len, - ); - } - let next_offset = *offset + sprite_bytes_len; assert!( next_offset <= INSTANCE_BUFFER_SIZE, "instance buffer exhausted" ); + let buffer_contents = + unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + + // buffer_contents.len() < spite_bytes_len must be out of range. + // PANIC HERE! + let next_offset = *offset + sprite_bytes_len; + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); + + unsafe { + ptr::copy_nonoverlapping( + sprites.as_ptr() as *const u8, //src + buffer_contents, //dest + sprite_bytes_len, // count + ); + } command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, @@ -723,6 +734,13 @@ impl MetalRenderer { let sprite_bytes_len = std::mem::size_of_val(sprites); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + + let next_offset = *offset + sprite_bytes_len; + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); + unsafe { ptr::copy_nonoverlapping( sprites.as_ptr() as *const u8, @@ -731,12 +749,6 @@ impl MetalRenderer { ); } - let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); - command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, 0, @@ -794,6 +806,12 @@ impl MetalRenderer { let sprite_bytes_len = std::mem::size_of_val(sprites); let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + + let next_offset = *offset + sprite_bytes_len; + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); unsafe { ptr::copy_nonoverlapping( sprites.as_ptr() as *const u8, @@ -802,12 +820,6 @@ impl MetalRenderer { ); } - let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); - command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, 0, From 324d1d119ba4066064bd13505053b308ffc9ae07 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 12 Jan 2024 12:40:37 -0800 Subject: [PATCH 2/3] Add some context to assert --- .../gpui/src/platform/mac/metal_renderer.rs | 70 +++++++------------ 1 file changed, 24 insertions(+), 46 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index 2a1f9ef92d..8365525ebd 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -18,7 +18,7 @@ use smallvec::SmallVec; use std::{ffi::c_void, mem, ptr, sync::Arc}; const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); -const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. [] +const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. pub(crate) struct MetalRenderer { layer: metal::MetalLayer, @@ -337,10 +337,7 @@ impl MetalRenderer { for (texture_id, vertices) in vertices_by_texture_id { align_offset(offset); let next_offset = *offset + vertices.len() * mem::size_of::>(); - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, vertices.len(), "Path Vertexes"); let render_pass_descriptor = metal::RenderPassDescriptor::new(); let color_attachment = render_pass_descriptor @@ -431,10 +428,7 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + shadow_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, shadows.len(), "Shadows"); unsafe { ptr::copy_nonoverlapping( @@ -492,10 +486,8 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + quad_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, quads.len(), "Quads"); + unsafe { ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len); } @@ -587,30 +579,18 @@ impl MetalRenderer { command_encoder .set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture)); - // hypothesis: sprites.as_ptr() does something bogus sometimes? - // let sprite_bytes_len = mem::size_of::() * sprites.len(); let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Path Sprites"); + let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; - // buffer_contents.len() < spite_bytes_len must be out of range. - // PANIC HERE! - let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); - unsafe { ptr::copy_nonoverlapping( - sprites.as_ptr() as *const u8, //src - buffer_contents, //dest - sprite_bytes_len, // count + sprites.as_ptr() as *const u8, + buffer_contents, + sprite_bytes_len, ); } @@ -672,10 +652,7 @@ impl MetalRenderer { } let next_offset = *offset + quad_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, underlines.len(), "Underlines"); command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, @@ -736,10 +713,7 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Monoschrome Sprites"); unsafe { ptr::copy_nonoverlapping( @@ -808,10 +782,8 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + sprite_bytes_len; - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Polychrome Sprites"); + unsafe { ptr::copy_nonoverlapping( sprites.as_ptr() as *const u8, @@ -886,10 +858,7 @@ impl MetalRenderer { align_offset(offset); let next_offset = *offset + mem::size_of::(); - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted" - ); + self.assert_instance_buffer_bounds(next_offset, 1, "Surface"); command_encoder.set_vertex_buffer( SurfaceInputIndex::Surfaces as u64, @@ -926,6 +895,15 @@ impl MetalRenderer { *offset = next_offset; } } + + fn assert_instance_buffer_bounds(&self, next_offset: usize, count: usize, item: &'static str) { + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted attempting to copy {} of {}", + count, + item + ); + } } fn build_pipeline_state( From aa5c6a8aa354e424a60f736559355045b091775c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 12 Jan 2024 14:35:50 -0700 Subject: [PATCH 3/3] Update graphics memory assert to be more helpful --- .../gpui/src/platform/mac/metal_renderer.rs | 192 ++++++++++-------- 1 file changed, 105 insertions(+), 87 deletions(-) diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index 8365525ebd..20e749a2f6 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -18,7 +18,7 @@ use smallvec::SmallVec; use std::{ffi::c_void, mem, ptr, sync::Arc}; const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); -const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value. +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...) pub(crate) struct MetalRenderer { layer: metal::MetalLayer, @@ -204,7 +204,11 @@ impl MetalRenderer { let command_buffer = command_queue.new_command_buffer(); let mut instance_offset = 0; - let path_tiles = self.rasterize_paths(scene.paths(), &mut instance_offset, command_buffer); + let Some(path_tiles) = + self.rasterize_paths(scene.paths(), &mut instance_offset, command_buffer) + else { + panic!("failed to rasterize {} paths", scene.paths().len()); + }; let render_pass_descriptor = metal::RenderPassDescriptor::new(); let color_attachment = render_pass_descriptor @@ -228,67 +232,67 @@ impl MetalRenderer { zfar: 1.0, }); for batch in scene.batches() { - match batch { - PrimitiveBatch::Shadows(shadows) => { - self.draw_shadows( - shadows, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } + let ok = match batch { + PrimitiveBatch::Shadows(shadows) => self.draw_shadows( + shadows, + &mut instance_offset, + viewport_size, + command_encoder, + ), PrimitiveBatch::Quads(quads) => { - self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder); - } - PrimitiveBatch::Paths(paths) => { - self.draw_paths( - paths, - &path_tiles, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } - PrimitiveBatch::Underlines(underlines) => { - self.draw_underlines( - underlines, - &mut instance_offset, - viewport_size, - command_encoder, - ); + self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder) } + PrimitiveBatch::Paths(paths) => self.draw_paths( + paths, + &path_tiles, + &mut instance_offset, + viewport_size, + command_encoder, + ), + PrimitiveBatch::Underlines(underlines) => self.draw_underlines( + underlines, + &mut instance_offset, + viewport_size, + command_encoder, + ), PrimitiveBatch::MonochromeSprites { texture_id, sprites, - } => { - self.draw_monochrome_sprites( - texture_id, - sprites, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } + } => self.draw_monochrome_sprites( + texture_id, + sprites, + &mut instance_offset, + viewport_size, + command_encoder, + ), PrimitiveBatch::PolychromeSprites { texture_id, sprites, - } => { - self.draw_polychrome_sprites( - texture_id, - sprites, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } - PrimitiveBatch::Surfaces(surfaces) => { - self.draw_surfaces( - surfaces, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } + } => self.draw_polychrome_sprites( + texture_id, + sprites, + &mut instance_offset, + viewport_size, + command_encoder, + ), + PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces( + surfaces, + &mut instance_offset, + viewport_size, + command_encoder, + ), + }; + + if !ok { + panic!("scene too large: {} paths, {} shadows, {} quads, {} underlines, {} mono, {} poly, {} surfaces", + scene.paths.len(), + scene.shadows.len(), + scene.quads.len(), + scene.underlines.len(), + scene.monochrome_sprites.len(), + scene.polychrome_sprites.len(), + scene.surfaces.len(), + ) } } @@ -311,7 +315,7 @@ impl MetalRenderer { paths: &[Path], offset: &mut usize, command_buffer: &metal::CommandBufferRef, - ) -> HashMap { + ) -> Option> { let mut tiles = HashMap::default(); let mut vertices_by_texture_id = HashMap::default(); for path in paths { @@ -337,7 +341,9 @@ impl MetalRenderer { for (texture_id, vertices) in vertices_by_texture_id { align_offset(offset); let next_offset = *offset + vertices.len() * mem::size_of::>(); - self.assert_instance_buffer_bounds(next_offset, vertices.len(), "Path Vertexes"); + if next_offset > INSTANCE_BUFFER_SIZE { + return None; + } let render_pass_descriptor = metal::RenderPassDescriptor::new(); let color_attachment = render_pass_descriptor @@ -386,7 +392,7 @@ impl MetalRenderer { *offset = next_offset; } - tiles + Some(tiles) } fn draw_shadows( @@ -395,9 +401,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if shadows.is_empty() { - return; + return true; } align_offset(offset); @@ -428,7 +434,9 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + shadow_bytes_len; - self.assert_instance_buffer_bounds(next_offset, shadows.len(), "Shadows"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } unsafe { ptr::copy_nonoverlapping( @@ -445,6 +453,7 @@ impl MetalRenderer { shadows.len() as u64, ); *offset = next_offset; + true } fn draw_quads( @@ -453,9 +462,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if quads.is_empty() { - return; + return true; } align_offset(offset); @@ -486,7 +495,9 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + quad_bytes_len; - self.assert_instance_buffer_bounds(next_offset, quads.len(), "Quads"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } unsafe { ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len); @@ -499,6 +510,7 @@ impl MetalRenderer { quads.len() as u64, ); *offset = next_offset; + true } fn draw_paths( @@ -508,9 +520,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if paths.is_empty() { - return; + return true; } command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state); @@ -581,7 +593,9 @@ impl MetalRenderer { let sprite_bytes_len = mem::size_of::() * sprites.len(); let next_offset = *offset + sprite_bytes_len; - self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Path Sprites"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; @@ -604,6 +618,7 @@ impl MetalRenderer { sprites.clear(); } } + true } fn draw_underlines( @@ -612,9 +627,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if underlines.is_empty() { - return; + return true; } align_offset(offset); @@ -652,7 +667,9 @@ impl MetalRenderer { } let next_offset = *offset + quad_bytes_len; - self.assert_instance_buffer_bounds(next_offset, underlines.len(), "Underlines"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } command_encoder.draw_primitives_instanced( metal::MTLPrimitiveType::Triangle, @@ -661,6 +678,7 @@ impl MetalRenderer { underlines.len() as u64, ); *offset = next_offset; + true } fn draw_monochrome_sprites( @@ -670,9 +688,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if sprites.is_empty() { - return; + return true; } align_offset(offset); @@ -713,7 +731,9 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + sprite_bytes_len; - self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Monoschrome Sprites"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } unsafe { ptr::copy_nonoverlapping( @@ -730,6 +750,7 @@ impl MetalRenderer { sprites.len() as u64, ); *offset = next_offset; + true } fn draw_polychrome_sprites( @@ -739,9 +760,9 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { if sprites.is_empty() { - return; + return true; } align_offset(offset); @@ -782,7 +803,9 @@ impl MetalRenderer { let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; let next_offset = *offset + sprite_bytes_len; - self.assert_instance_buffer_bounds(next_offset, sprites.len(), "Polychrome Sprites"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } unsafe { ptr::copy_nonoverlapping( @@ -799,6 +822,7 @@ impl MetalRenderer { sprites.len() as u64, ); *offset = next_offset; + true } fn draw_surfaces( @@ -807,7 +831,7 @@ impl MetalRenderer { offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, - ) { + ) -> bool { command_encoder.set_render_pipeline_state(&self.surfaces_pipeline_state); command_encoder.set_vertex_buffer( SurfaceInputIndex::Vertices as u64, @@ -858,7 +882,9 @@ impl MetalRenderer { align_offset(offset); let next_offset = *offset + mem::size_of::(); - self.assert_instance_buffer_bounds(next_offset, 1, "Surface"); + if next_offset > INSTANCE_BUFFER_SIZE { + return false; + } command_encoder.set_vertex_buffer( SurfaceInputIndex::Surfaces as u64, @@ -894,15 +920,7 @@ impl MetalRenderer { command_encoder.draw_primitives(metal::MTLPrimitiveType::Triangle, 0, 6); *offset = next_offset; } - } - - fn assert_instance_buffer_bounds(&self, next_offset: usize, count: usize, item: &'static str) { - assert!( - next_offset <= INSTANCE_BUFFER_SIZE, - "instance buffer exhausted attempting to copy {} of {}", - count, - item - ); + true } }