diff --git a/crates/gpui3/build.rs b/crates/gpui3/build.rs index c1ad7491d815fa60c696f13ba84df8147a29fa36..d0d4a5c74b73fead8cbb56bd083f3b6b10cc04ad 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 a0c608abc6c738fa20362e38af195c78fa7d6ac6..9b93a8a5619ccf006f81fd34090206fa1f6cb705 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,6 +252,66 @@ impl MetalRenderer { drawable.present(); } + fn draw_shadows( + &mut self, + shadows: &[Shadow], + offset: &mut usize, + viewport_size: Size, + command_encoder: &metal::RenderCommandEncoderRef, + ) { + if shadows.is_empty() { + return; + } + align_offset(offset); + + command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state); + command_encoder.set_vertex_buffer( + ShadowInputIndex::Vertices as u64, + Some(&self.unit_vertices), + 0, + ); + command_encoder.set_vertex_buffer( + ShadowInputIndex::Shadows as u64, + Some(&self.instances), + *offset as u64, + ); + command_encoder.set_fragment_buffer( + ShadowInputIndex::Shadows as u64, + Some(&self.instances), + *offset as u64, + ); + + command_encoder.set_vertex_bytes( + ShadowInputIndex::ViewportSize as u64, + mem::size_of_val(&viewport_size) as u64, + &viewport_size as *const Size as *const _, + ); + + let shadow_bytes_len = mem::size_of::() * shadows.len(); + let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + unsafe { + ptr::copy_nonoverlapping( + shadows.as_ptr() as *const u8, + buffer_contents, + shadow_bytes_len, + ); + } + + 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, + 6, + shadows.len() as u64, + ); + *offset = next_offset; + } + fn draw_quads( &mut self, quads: &[Quad], @@ -290,52 +368,52 @@ impl MetalRenderer { *offset = next_offset; } - fn draw_shadows( + fn draw_underlines( &mut self, - shadows: &[Shadow], + underlines: &[Underline], offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, ) { - if shadows.is_empty() { + if underlines.is_empty() { return; } align_offset(offset); - command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state); + command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state); command_encoder.set_vertex_buffer( - ShadowInputIndex::Vertices as u64, + UnderlineInputIndex::Vertices as u64, Some(&self.unit_vertices), 0, ); command_encoder.set_vertex_buffer( - ShadowInputIndex::Shadows as u64, + UnderlineInputIndex::Underlines as u64, Some(&self.instances), *offset as u64, ); command_encoder.set_fragment_buffer( - ShadowInputIndex::Shadows as u64, + UnderlineInputIndex::Underlines as u64, Some(&self.instances), *offset as u64, ); command_encoder.set_vertex_bytes( - ShadowInputIndex::ViewportSize as u64, + UnderlineInputIndex::ViewportSize as u64, mem::size_of_val(&viewport_size) as u64, &viewport_size as *const Size as *const _, ); - let shadow_bytes_len = mem::size_of::() * shadows.len(); + 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( - shadows.as_ptr() as *const u8, + underlines.as_ptr() as *const u8, buffer_contents, - shadow_bytes_len, + quad_bytes_len, ); } - let next_offset = *offset + shadow_bytes_len; + let next_offset = *offset + quad_bytes_len; assert!( next_offset <= INSTANCE_BUFFER_SIZE, "instance buffer exhausted" @@ -345,7 +423,7 @@ impl MetalRenderer { metal::MTLPrimitiveType::Triangle, 0, 6, - shadows.len() as u64, + underlines.len() as u64, ); *offset = next_offset; } @@ -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 580e46d2ab98dace424bd15ead48994bc261530a..33670c02bb1a02dd6d64a1ab46b51de4483eebe9 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 459d3e55f0a5124d1203af50fa6f8a75b110fd03..8cabda9f9f0a8c2743085a27edacfd1d878a012a 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,6 +222,21 @@ impl<'a> Iterator for BatchIterator<'a> { }; match batch_kind { + PrimitiveKind::Shadow => { + let shadows_start = self.shadows_start; + let mut shadows_end = shadows_start; + while self + .shadows_iter + .next_if(|shadow| shadow.order <= max_order) + .is_some() + { + shadows_end += 1; + } + self.shadows_start = shadows_end; + Some(PrimitiveBatch::Shadows( + &self.shadows[shadows_start..shadows_end], + )) + } PrimitiveKind::Quad => { let quads_start = self.quads_start; let mut quads_end = quads_start; @@ -203,19 +250,19 @@ impl<'a> Iterator for BatchIterator<'a> { 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; + PrimitiveKind::Underline => { + let underlines_start = self.underlines_start; + let mut underlines_end = underlines_start; while self - .shadows_iter - .next_if(|shadow| shadow.order <= max_order) + .underlines_iter + .next_if(|underline| underline.order <= max_order) .is_some() { - shadows_end += 1; + underlines_end += 1; } - self.shadows_start = shadows_end; - Some(PrimitiveBatch::Shadows( - &self.shadows[shadows_start..shadows_end], + self.underlines_start = underlines_end; + Some(PrimitiveBatch::Underlines( + &self.underlines[underlines_start..underlines_end], )) } PrimitiveKind::MonochromeSprite => { @@ -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 2b310d146f9dc2adad69b11f39761fc3122ba6ea..af2b87d0bbe00340eb571455b37a7d4d1fa38840 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 148a12fc9c42f3fdac7f51c0d183cee3859cfe73..a0cdbffd91d388f6c1f57cef733edde02d6f2b3f 100644 --- a/crates/gpui3/src/style_helpers.rs +++ b/crates/gpui3/src/style_helpers.rs @@ -416,6 +416,96 @@ pub trait StyleHelpers: Styled