blade: port underline shader

Dzmitry Malyshau created

Change summary

crates/gpui/src/platform/linux/blade_renderer.rs | 39 +++++++++
crates/gpui/src/platform/linux/shaders.wgsl      | 70 ++++++++++++++++-
crates/gpui/src/scene.rs                         |  2 
crates/gpui/src/window/element_cx.rs             |  2 
4 files changed, 106 insertions(+), 7 deletions(-)

Detailed changes

crates/gpui/src/platform/linux/blade_renderer.rs 🔗

@@ -4,7 +4,7 @@
 use super::{BladeBelt, BladeBeltDescriptor};
 use crate::{
     AtlasTextureKind, AtlasTile, BladeAtlas, Bounds, ContentMask, Hsla, Path, PathId, PathVertex,
-    PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, PATH_TEXTURE_FORMAT,
+    PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Underline, PATH_TEXTURE_FORMAT,
 };
 use bytemuck::{Pod, Zeroable};
 use collections::HashMap;
@@ -48,6 +48,12 @@ struct ShaderPathsData {
     b_path_sprites: gpu::BufferPiece,
 }
 
+#[derive(blade_macros::ShaderData)]
+struct ShaderUnderlinesData {
+    globals: GlobalParams,
+    b_underlines: gpu::BufferPiece,
+}
+
 #[derive(Clone, Debug, Eq, PartialEq)]
 #[repr(C)]
 struct PathSprite {
@@ -61,6 +67,7 @@ struct BladePipelines {
     shadows: gpu::RenderPipeline,
     path_rasterization: gpu::RenderPipeline,
     paths: gpu::RenderPipeline,
+    underlines: gpu::RenderPipeline,
 }
 
 impl BladePipelines {
@@ -75,11 +82,13 @@ impl BladePipelines {
             shader.get_struct_size("PathVertex") as usize,
         );
         shader.check_struct_size::<PathSprite>();
+        shader.check_struct_size::<Underline>();
 
         let quads_layout = <ShaderQuadsData as gpu::ShaderData>::layout();
         let shadows_layout = <ShaderShadowsData as gpu::ShaderData>::layout();
         let path_rasterization_layout = <ShaderPathRasterizationData as gpu::ShaderData>::layout();
         let paths_layout = <ShaderPathsData as gpu::ShaderData>::layout();
+        let underlines_layout = <ShaderUnderlinesData as gpu::ShaderData>::layout();
 
         Self {
             quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
@@ -146,6 +155,22 @@ impl BladePipelines {
                     write_mask: gpu::ColorWrites::default(),
                 }],
             }),
+            underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
+                name: "underlines",
+                data_layouts: &[&underlines_layout],
+                vertex: shader.at("vs_underline"),
+                primitive: gpu::PrimitiveState {
+                    topology: gpu::PrimitiveTopology::TriangleStrip,
+                    ..Default::default()
+                },
+                depth_stencil: None,
+                fragment: shader.at("fs_underline"),
+                color_targets: &[gpu::ColorTargetState {
+                    format: surface_format,
+                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
+                    write_mask: gpu::ColorWrites::default(),
+                }],
+            }),
         }
     }
 }
@@ -359,6 +384,18 @@ impl BladeRenderer {
                             encoder.draw(0, 4, 0, sprites.len() as u32);
                         }
                     }
+                    PrimitiveBatch::Underlines(underlines) => {
+                        let instance_buf = self.instance_belt.alloc_data(underlines, &self.gpu);
+                        let mut encoder = pass.with(&self.pipelines.underlines);
+                        encoder.bind(
+                            0,
+                            &ShaderUnderlinesData {
+                                globals,
+                                b_underlines: instance_buf,
+                            },
+                        );
+                        encoder.draw(0, 4, 0, underlines.len() as u32);
+                    }
                     _ => continue,
                 }
             }

crates/gpui/src/platform/linux/shaders.wgsl 🔗

@@ -373,7 +373,7 @@ struct PathVarying {
 
 @vertex
 fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> PathVarying {
-	let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
+    let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
     let sprite = b_path_sprites[instance_id];
     // Don't apply content mask because it was already accounted for when rasterizing the path.
 
@@ -386,7 +386,69 @@ fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) insta
 
 @fragment
 fn fs_path(input: PathVarying) -> @location(0) vec4<f32> {
-	let sample = textureSample(t_tile, s_tile, input.tile_position).r;
-	let mask = 1.0 - abs(1.0 - sample % 2.0);
-	return input.color * mask;
+    let sample = textureSample(t_tile, s_tile, input.tile_position).r;
+    let mask = 1.0 - abs(1.0 - sample % 2.0);
+    return input.color * mask;
+}
+
+// --- underlines --- //
+
+struct Underline {
+    view_id: ViewId,
+    layer_id: u32,
+    order: u32,
+    bounds: Bounds,
+    content_mask: Bounds,
+    color: Hsla,
+    thickness: f32,
+    wavy: u32,
+}
+var<storage, read> b_underlines: array<Underline>;
+
+struct UnderlineVarying {
+    @builtin(position) position: vec4<f32>,
+    @location(0) @interpolate(flat) color: vec4<f32>,
+    @location(1) @interpolate(flat) underline_id: u32,
+    //TODO: use `clip_distance` once Naga supports it
+    @location(3) clip_distances: vec4<f32>,
+}
+
+@vertex
+fn vs_underline(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> UnderlineVarying {
+    let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
+    let underline = b_underlines[instance_id];
+
+    var out = UnderlineVarying();
+    out.position = to_device_position(unit_vertex, underline.bounds);
+    out.color = hsla_to_rgba(underline.color);
+    out.underline_id = instance_id;
+    out.clip_distances = distance_from_clip_rect(unit_vertex, underline.bounds, underline.content_mask);
+    return out;
+}
+
+@fragment
+fn fs_underline(input: UnderlineVarying) -> @location(0) vec4<f32> {
+    // Alpha clip first, since we don't have `clip_distance`.
+    if (any(input.clip_distances < vec4<f32>(0.0))) {
+        return vec4<f32>(0.0);
+    }
+
+    let underline = b_underlines[input.underline_id];
+    if (underline.wavy == 0u)
+    {
+        return vec4<f32>(0.0);
+    }
+
+    let half_thickness = underline.thickness * 0.5;
+    let st = (input.position.xy - underline.bounds.origin) / underline.bounds.size.y - vec2<f32>(0.0, 0.5);
+    let frequency = M_PI_F * 3.0 * underline.thickness / 8.0;
+    let amplitude = 1.0 / (2.0 * underline.thickness);
+    let sine = sin(st.x * frequency) * amplitude;
+    let dSine = cos(st.x * frequency) * amplitude * frequency;
+    let distance = (st.y - sine) / sqrt(1.0 + dSine * dSine);
+    let distance_in_pixels = distance * underline.bounds.size.y;
+    let distance_from_top_border = distance_in_pixels - half_thickness;
+    let distance_from_bottom_border = distance_in_pixels + half_thickness;
+    let alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border));
+    return input.color * vec4<f32>(1.0, 1.0, 1.0, alpha);
 }

crates/gpui/src/scene.rs 🔗

@@ -543,8 +543,8 @@ pub(crate) struct Underline {
     pub order: DrawOrder,
     pub bounds: Bounds<ScaledPixels>,
     pub content_mask: ContentMask<ScaledPixels>,
-    pub thickness: ScaledPixels,
     pub color: Hsla,
+    pub thickness: ScaledPixels,
     pub wavy: bool,
 }
 

crates/gpui/src/window/element_cx.rs 🔗

@@ -753,8 +753,8 @@ impl<'a> ElementContext<'a> {
                 order: 0,
                 bounds: bounds.scale(scale_factor),
                 content_mask: content_mask.scale(scale_factor),
-                thickness: style.thickness.scale(scale_factor),
                 color: style.color.unwrap_or_default(),
+                thickness: style.thickness.scale(scale_factor),
                 wavy: style.wavy,
             },
         );