From ca6eb5511ce6e83659f3d1fd49de35a713ad0707 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 6 Oct 2023 15:34:37 +0200 Subject: [PATCH] Checkpoint: underlines --- crates/gpui3/build.rs | 6 +- .../gpui3/src/platform/mac/metal_renderer.rs | 227 ++++++++++++------ crates/gpui3/src/platform/mac/shaders.metal | 55 ++++- crates/gpui3/src/scene.rs | 139 ++++++++--- crates/gpui3/src/style.rs | 2 +- crates/gpui3/src/style_helpers.rs | 90 +++++++ crates/gpui3/src/text_system/line.rs | 78 +++--- crates/gpui3/src/window.rs | 45 +++- crates/storybook2/src/workspace.rs | 8 +- 9 files changed, 495 insertions(+), 155 deletions(-) diff --git a/crates/gpui3/build.rs b/crates/gpui3/build.rs index c1ad7491d8..d0d4a5c74b 100644 --- a/crates/gpui3/build.rs +++ b/crates/gpui3/build.rs @@ -50,10 +50,12 @@ fn generate_shader_bindings() -> PathBuf { "ScaledContentMask".into(), "Uniforms".into(), "AtlasTile".into(), - "QuadInputIndex".into(), - "Quad".into(), "ShadowInputIndex".into(), "Shadow".into(), + "QuadInputIndex".into(), + "Underline".into(), + "UnderlineInputIndex".into(), + "Quad".into(), "SpriteInputIndex".into(), "MonochromeSprite".into(), "PolychromeSprite".into(), diff --git a/crates/gpui3/src/platform/mac/metal_renderer.rs b/crates/gpui3/src/platform/mac/metal_renderer.rs index a0c608abc6..9b93a8a561 100644 --- a/crates/gpui3/src/platform/mac/metal_renderer.rs +++ b/crates/gpui3/src/platform/mac/metal_renderer.rs @@ -1,6 +1,6 @@ use crate::{ point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite, - Quad, Scene, Shadow, Size, + PrimitiveBatch, Quad, Scene, Shadow, Size, Underline, }; use cocoa::{ base::{NO, YES}, @@ -17,8 +17,9 @@ const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decisio pub struct MetalRenderer { layer: metal::MetalLayer, command_queue: CommandQueue, - quads_pipeline_state: metal::RenderPipelineState, shadows_pipeline_state: metal::RenderPipelineState, + quads_pipeline_state: metal::RenderPipelineState, + underlines_pipeline_state: metal::RenderPipelineState, monochrome_sprites_pipeline_state: metal::RenderPipelineState, polychrome_sprites_pipeline_state: metal::RenderPipelineState, unit_vertices: metal::Buffer, @@ -83,6 +84,14 @@ impl MetalRenderer { MTLResourceOptions::StorageModeManaged, ); + let shadows_pipeline_state = build_pipeline_state( + &device, + &library, + "shadows", + "shadow_vertex", + "shadow_fragment", + PIXEL_FORMAT, + ); let quads_pipeline_state = build_pipeline_state( &device, &library, @@ -91,12 +100,12 @@ impl MetalRenderer { "quad_fragment", PIXEL_FORMAT, ); - let shadows_pipeline_state = build_pipeline_state( + let underlines_pipeline_state = build_pipeline_state( &device, &library, - "shadows", - "shadow_vertex", - "shadow_fragment", + "underlines", + "underline_vertex", + "underline_fragment", PIXEL_FORMAT, ); let monochrome_sprites_pipeline_state = build_pipeline_state( @@ -122,8 +131,9 @@ impl MetalRenderer { Self { layer, command_queue, - quads_pipeline_state, shadows_pipeline_state, + quads_pipeline_state, + underlines_pipeline_state, monochrome_sprites_pipeline_state, polychrome_sprites_pipeline_state, unit_vertices, @@ -184,10 +194,7 @@ impl MetalRenderer { let mut instance_offset = 0; for batch in scene.batches() { match batch { - crate::PrimitiveBatch::Quads(quads) => { - self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder); - } - crate::PrimitiveBatch::Shadows(shadows) => { + PrimitiveBatch::Shadows(shadows) => { self.draw_shadows( shadows, &mut instance_offset, @@ -195,7 +202,18 @@ impl MetalRenderer { command_encoder, ); } - crate::PrimitiveBatch::MonochromeSprites { + PrimitiveBatch::Quads(quads) => { + self.draw_quads(quads, &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, } => { @@ -207,7 +225,7 @@ impl MetalRenderer { command_encoder, ); } - crate::PrimitiveBatch::PolychromeSprites { + PrimitiveBatch::PolychromeSprites { texture_id, sprites, } => { @@ -234,62 +252,6 @@ impl MetalRenderer { drawable.present(); } - fn draw_quads( - &mut self, - quads: &[Quad], - offset: &mut usize, - viewport_size: Size, - command_encoder: &metal::RenderCommandEncoderRef, - ) { - if quads.is_empty() { - return; - } - align_offset(offset); - - command_encoder.set_render_pipeline_state(&self.quads_pipeline_state); - command_encoder.set_vertex_buffer( - QuadInputIndex::Vertices as u64, - Some(&self.unit_vertices), - 0, - ); - command_encoder.set_vertex_buffer( - QuadInputIndex::Quads as u64, - Some(&self.instances), - *offset as u64, - ); - command_encoder.set_fragment_buffer( - QuadInputIndex::Quads as u64, - Some(&self.instances), - *offset as u64, - ); - - command_encoder.set_vertex_bytes( - QuadInputIndex::ViewportSize as u64, - mem::size_of_val(&viewport_size) as u64, - &viewport_size as *const Size as *const _, - ); - - let quad_bytes_len = mem::size_of::() * quads.len(); - 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" - ); - - command_encoder.draw_primitives_instanced( - metal::MTLPrimitiveType::Triangle, - 0, - 6, - quads.len() as u64, - ); - *offset = next_offset; - } - fn draw_shadows( &mut self, shadows: &[Shadow], @@ -350,6 +312,122 @@ impl MetalRenderer { *offset = next_offset; } + fn draw_quads( + &mut self, + quads: &[Quad], + offset: &mut usize, + viewport_size: Size, + command_encoder: &metal::RenderCommandEncoderRef, + ) { + if quads.is_empty() { + return; + } + align_offset(offset); + + command_encoder.set_render_pipeline_state(&self.quads_pipeline_state); + command_encoder.set_vertex_buffer( + QuadInputIndex::Vertices as u64, + Some(&self.unit_vertices), + 0, + ); + command_encoder.set_vertex_buffer( + QuadInputIndex::Quads as u64, + Some(&self.instances), + *offset as u64, + ); + command_encoder.set_fragment_buffer( + QuadInputIndex::Quads as u64, + Some(&self.instances), + *offset as u64, + ); + + command_encoder.set_vertex_bytes( + QuadInputIndex::ViewportSize as u64, + mem::size_of_val(&viewport_size) as u64, + &viewport_size as *const Size as *const _, + ); + + let quad_bytes_len = mem::size_of::() * quads.len(); + 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" + ); + + command_encoder.draw_primitives_instanced( + metal::MTLPrimitiveType::Triangle, + 0, + 6, + quads.len() as u64, + ); + *offset = next_offset; + } + + fn draw_underlines( + &mut self, + underlines: &[Underline], + offset: &mut usize, + viewport_size: Size, + command_encoder: &metal::RenderCommandEncoderRef, + ) { + if underlines.is_empty() { + return; + } + align_offset(offset); + + command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state); + command_encoder.set_vertex_buffer( + UnderlineInputIndex::Vertices as u64, + Some(&self.unit_vertices), + 0, + ); + command_encoder.set_vertex_buffer( + UnderlineInputIndex::Underlines as u64, + Some(&self.instances), + *offset as u64, + ); + command_encoder.set_fragment_buffer( + UnderlineInputIndex::Underlines as u64, + Some(&self.instances), + *offset as u64, + ); + + command_encoder.set_vertex_bytes( + UnderlineInputIndex::ViewportSize as u64, + mem::size_of_val(&viewport_size) as u64, + &viewport_size as *const Size as *const _, + ); + + let quad_bytes_len = mem::size_of::() * underlines.len(); + let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + unsafe { + ptr::copy_nonoverlapping( + underlines.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" + ); + + command_encoder.draw_primitives_instanced( + metal::MTLPrimitiveType::Triangle, + 0, + 6, + underlines.len() as u64, + ); + *offset = next_offset; + } + fn draw_monochrome_sprites( &mut self, texture_id: AtlasTextureId, @@ -533,6 +611,13 @@ fn align_offset(offset: &mut usize) { *offset = ((*offset + 255) / 256) * 256; } +#[repr(C)] +enum ShadowInputIndex { + Vertices = 0, + Shadows = 1, + ViewportSize = 2, +} + #[repr(C)] enum QuadInputIndex { Vertices = 0, @@ -541,9 +626,9 @@ enum QuadInputIndex { } #[repr(C)] -enum ShadowInputIndex { +enum UnderlineInputIndex { Vertices = 0, - Shadows = 1, + Underlines = 1, ViewportSize = 2, } diff --git a/crates/gpui3/src/platform/mac/shaders.metal b/crates/gpui3/src/platform/mac/shaders.metal index 580e46d2ab..33670c02bb 100644 --- a/crates/gpui3/src/platform/mac/shaders.metal +++ b/crates/gpui3/src/platform/mac/shaders.metal @@ -193,6 +193,53 @@ fragment float4 shadow_fragment(ShadowVertexOutput input [[stage_in]], return input.color * float4(1., 1., 1., alpha); } +struct UnderlineVertexOutput { + float4 position [[position]]; + float4 color [[flat]]; + uint underline_id [[flat]]; +}; + +vertex UnderlineVertexOutput underline_vertex( + uint unit_vertex_id [[vertex_id]], uint underline_id [[instance_id]], + constant float2 *unit_vertices [[buffer(UnderlineInputIndex_Vertices)]], + constant Underline *underlines [[buffer(UnderlineInputIndex_Underlines)]], + constant Size_DevicePixels *viewport_size + [[buffer(ShadowInputIndex_ViewportSize)]]) { + float2 unit_vertex = unit_vertices[unit_vertex_id]; + Underline underline = underlines[underline_id]; + float4 device_position = + to_device_position(unit_vertex, underline.bounds, + underline.content_mask.bounds, viewport_size); + float4 color = hsla_to_rgba(underline.color); + return UnderlineVertexOutput{device_position, color, underline_id}; +} + +fragment float4 underline_fragment(UnderlineVertexOutput input [[stage_in]], + constant Underline *underlines + [[buffer(UnderlineInputIndex_Underlines)]]) { + Underline underline = underlines[input.underline_id]; + if (underline.wavy) { + float half_thickness = underline.thickness * 0.5; + float2 origin = + float2(underline.bounds.origin.x, underline.bounds.origin.y); + float2 st = ((input.position.xy - origin) / underline.bounds.size.height) - + float2(0., 0.5); + float frequency = (M_PI_F * (3. * underline.thickness)) / 8.; + float amplitude = 1. / (2. * underline.thickness); + float sine = sin(st.x * frequency) * amplitude; + float dSine = cos(st.x * frequency) * amplitude * frequency; + float distance = (st.y - sine) / sqrt(1. + dSine * dSine); + float distance_in_pixels = distance * underline.bounds.size.height; + float distance_from_top_border = distance_in_pixels - half_thickness; + float distance_from_bottom_border = distance_in_pixels + half_thickness; + float alpha = saturate( + 0.5 - max(-distance_from_bottom_border, distance_from_top_border)); + return input.color * float4(1., 1., 1., alpha); + } else { + return input.color; + } +} + struct MonochromeSpriteVertexOutput { float4 position [[position]]; float2 tile_position; @@ -211,8 +258,8 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex( float2 unit_vertex = unit_vertices[unit_vertex_id]; MonochromeSprite sprite = sprites[sprite_id]; - // Don't apply content mask at the vertex level because we don't have time to - // make sampling from the texture match the mask. + // Don't apply content mask at the vertex level because we don't have time + // to make sampling from the texture match the mask. float4 device_position = to_device_position(unit_vertex, sprite.bounds, sprite.bounds, viewport_size); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); @@ -254,8 +301,8 @@ vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex( float2 unit_vertex = unit_vertices[unit_vertex_id]; PolychromeSprite sprite = sprites[sprite_id]; - // Don't apply content mask at the vertex level because we don't have time to - // make sampling from the texture match the mask. + // Don't apply content mask at the vertex level because we don't have time + // to make sampling from the texture match the mask. float4 device_position = to_device_position(unit_vertex, sprite.bounds, sprite.bounds, viewport_size); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); diff --git a/crates/gpui3/src/scene.rs b/crates/gpui3/src/scene.rs index 459d3e55f0..8cabda9f9f 100644 --- a/crates/gpui3/src/scene.rs +++ b/crates/gpui3/src/scene.rs @@ -17,8 +17,9 @@ pub type DrawOrder = u32; pub struct Scene { pub(crate) scale_factor: f32, pub(crate) layers: BTreeMap, - pub quads: Vec, pub shadows: Vec, + pub quads: Vec, + pub underlines: Vec, pub monochrome_sprites: Vec, pub polychrome_sprites: Vec, } @@ -28,8 +29,9 @@ impl Scene { Scene { scale_factor, layers: BTreeMap::new(), - quads: Vec::new(), shadows: Vec::new(), + quads: Vec::new(), + underlines: Vec::new(), monochrome_sprites: Vec::new(), polychrome_sprites: Vec::new(), } @@ -39,8 +41,9 @@ impl Scene { Scene { scale_factor: self.scale_factor, layers: mem::take(&mut self.layers), - quads: mem::take(&mut self.quads), shadows: mem::take(&mut self.shadows), + quads: mem::take(&mut self.quads), + underlines: mem::take(&mut self.underlines), monochrome_sprites: mem::take(&mut self.monochrome_sprites), polychrome_sprites: mem::take(&mut self.polychrome_sprites), } @@ -51,13 +54,17 @@ impl Scene { let layer_id = *self.layers.entry(layer_id).or_insert(next_id); let primitive = primitive.into(); match primitive { + Primitive::Shadow(mut shadow) => { + shadow.order = layer_id; + self.shadows.push(shadow); + } Primitive::Quad(mut quad) => { quad.order = layer_id; self.quads.push(quad); } - Primitive::Shadow(mut shadow) => { - shadow.order = layer_id; - self.shadows.push(shadow); + Primitive::Underline(mut underline) => { + underline.order = layer_id; + self.underlines.push(underline); } Primitive::MonochromeSprite(mut sprite) => { sprite.order = layer_id; @@ -78,15 +85,26 @@ impl Scene { } // Add all primitives to the BSP splitter to determine draw order + // todo!("reuse the same splitter") let mut splitter = BspSplitter::new(); + + for (ix, shadow) in self.shadows.iter().enumerate() { + let z = layer_z_values[shadow.order as LayerId as usize]; + splitter.add(shadow.bounds.to_bsp_polygon(z, (PrimitiveKind::Shadow, ix))); + } + for (ix, quad) in self.quads.iter().enumerate() { let z = layer_z_values[quad.order as LayerId as usize]; splitter.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix))); } - for (ix, shadow) in self.shadows.iter().enumerate() { - let z = layer_z_values[shadow.order as LayerId as usize]; - splitter.add(shadow.bounds.to_bsp_polygon(z, (PrimitiveKind::Shadow, ix))); + for (ix, underline) in self.underlines.iter().enumerate() { + let z = layer_z_values[underline.order as LayerId as usize]; + splitter.add( + underline + .bounds + .to_bsp_polygon(z, (PrimitiveKind::Underline, ix)), + ); } for (ix, monochrome_sprite) in self.monochrome_sprites.iter().enumerate() { @@ -111,8 +129,11 @@ impl Scene { // We need primitives to be repr(C), hence the weird reuse of the order field for two different types. for (draw_order, polygon) in splitter.sort(Vector3D::new(0., 0., 1.)).iter().enumerate() { match polygon.anchor { - (PrimitiveKind::Quad, ix) => self.quads[ix].order = draw_order as DrawOrder, (PrimitiveKind::Shadow, ix) => self.shadows[ix].order = draw_order as DrawOrder, + (PrimitiveKind::Quad, ix) => self.quads[ix].order = draw_order as DrawOrder, + (PrimitiveKind::Underline, ix) => { + self.underlines[ix].order = draw_order as DrawOrder + } (PrimitiveKind::MonochromeSprite, ix) => { self.monochrome_sprites[ix].order = draw_order as DrawOrder } @@ -123,18 +144,22 @@ impl Scene { } // Sort the primitives - self.quads.sort_unstable(); self.shadows.sort_unstable(); + self.quads.sort_unstable(); + self.underlines.sort_unstable(); self.monochrome_sprites.sort_unstable(); self.polychrome_sprites.sort_unstable(); BatchIterator { - quads: &self.quads, - quads_start: 0, - quads_iter: self.quads.iter().peekable(), shadows: &self.shadows, shadows_start: 0, shadows_iter: self.shadows.iter().peekable(), + quads: &self.quads, + quads_start: 0, + quads_iter: self.quads.iter().peekable(), + underlines: &self.underlines, + underlines_start: 0, + underlines_iter: self.underlines.iter().peekable(), monochrome_sprites: &self.monochrome_sprites, monochrome_sprites_start: 0, monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(), @@ -152,6 +177,9 @@ struct BatchIterator<'a> { shadows: &'a [Shadow], shadows_start: usize, shadows_iter: Peekable>, + underlines: &'a [Underline], + underlines_start: usize, + underlines_iter: Peekable>, monochrome_sprites: &'a [MonochromeSprite], monochrome_sprites_start: usize, monochrome_sprites_iter: Peekable>, @@ -165,11 +193,15 @@ impl<'a> Iterator for BatchIterator<'a> { fn next(&mut self) -> Option { let mut orders_and_kinds = [ - (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad), ( self.shadows_iter.peek().map(|s| s.order), PrimitiveKind::Shadow, ), + (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad), + ( + self.underlines_iter.peek().map(|u| u.order), + PrimitiveKind::Underline, + ), ( self.monochrome_sprites_iter.peek().map(|s| s.order), PrimitiveKind::MonochromeSprite, @@ -190,19 +222,6 @@ impl<'a> Iterator for BatchIterator<'a> { }; match batch_kind { - PrimitiveKind::Quad => { - let quads_start = self.quads_start; - let mut quads_end = quads_start; - while self - .quads_iter - .next_if(|quad| quad.order <= max_order) - .is_some() - { - quads_end += 1; - } - self.quads_start = quads_end; - Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end])) - } PrimitiveKind::Shadow => { let shadows_start = self.shadows_start; let mut shadows_end = shadows_start; @@ -218,6 +237,34 @@ impl<'a> Iterator for BatchIterator<'a> { &self.shadows[shadows_start..shadows_end], )) } + PrimitiveKind::Quad => { + let quads_start = self.quads_start; + let mut quads_end = quads_start; + while self + .quads_iter + .next_if(|quad| quad.order <= max_order) + .is_some() + { + quads_end += 1; + } + self.quads_start = quads_end; + Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end])) + } + PrimitiveKind::Underline => { + let underlines_start = self.underlines_start; + let mut underlines_end = underlines_start; + while self + .underlines_iter + .next_if(|underline| underline.order <= max_order) + .is_some() + { + underlines_end += 1; + } + self.underlines_start = underlines_end; + Some(PrimitiveBatch::Underlines( + &self.underlines[underlines_start..underlines_end], + )) + } PrimitiveKind::MonochromeSprite => { let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id; let sprites_start = self.monochrome_sprites_start; @@ -265,22 +312,25 @@ pub enum PrimitiveKind { Shadow, #[default] Quad, + Underline, MonochromeSprite, PolychromeSprite, } #[derive(Clone, Debug)] pub enum Primitive { - Quad(Quad), Shadow(Shadow), + Quad(Quad), + Underline(Underline), MonochromeSprite(MonochromeSprite), PolychromeSprite(PolychromeSprite), } #[derive(Debug)] pub(crate) enum PrimitiveBatch<'a> { - Quads(&'a [Quad]), Shadows(&'a [Shadow]), + Quads(&'a [Quad]), + Underlines(&'a [Underline]), MonochromeSprites { texture_id: AtlasTextureId, sprites: &'a [MonochromeSprite], @@ -321,6 +371,35 @@ impl From for Primitive { } } +#[derive(Debug, Clone, Eq, PartialEq)] +#[repr(C)] +pub struct Underline { + pub order: u32, + pub bounds: Bounds, + pub content_mask: ScaledContentMask, + pub thickness: ScaledPixels, + pub color: Hsla, + pub wavy: bool, +} + +impl Ord for Underline { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.order.cmp(&other.order) + } +} + +impl PartialOrd for Underline { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl From for Primitive { + fn from(underline: Underline) -> Self { + Primitive::Underline(underline) + } +} + #[derive(Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Shadow { diff --git a/crates/gpui3/src/style.rs b/crates/gpui3/src/style.rs index 2b310d146f..af2b87d0bb 100644 --- a/crates/gpui3/src/style.rs +++ b/crates/gpui3/src/style.rs @@ -344,7 +344,7 @@ impl Default for Style { pub struct UnderlineStyle { pub thickness: Pixels, pub color: Option, - pub squiggly: bool, + pub wavy: bool, } #[derive(Clone, Debug)] diff --git a/crates/gpui3/src/style_helpers.rs b/crates/gpui3/src/style_helpers.rs index 148a12fc9c..a0cdbffd91 100644 --- a/crates/gpui3/src/style_helpers.rs +++ b/crates/gpui3/src/style_helpers.rs @@ -416,6 +416,96 @@ pub trait StyleHelpers: Styled