linux: path rasterization shader

Dzmitry Malyshau created

Change summary

crates/gpui/src/platform/linux/blade_atlas.rs    | 18 +++++
crates/gpui/src/platform/linux/blade_renderer.rs | 41 +++++++++++--
crates/gpui/src/platform/linux/shaders.wgsl      | 53 ++++++++++++++++-
crates/gpui/src/platform/linux/text_system.rs    |  2 
4 files changed, 100 insertions(+), 14 deletions(-)

Detailed changes

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

@@ -41,6 +41,11 @@ impl BladeAtlasState {
     }
 }
 
+pub struct BladeTextureInfo {
+    pub size: gpu::Extent,
+    pub raw_view: Option<gpu::TextureView>,
+}
+
 impl BladeAtlas {
     pub(crate) fn new(gpu: &Arc<gpu::Context>) -> Self {
         BladeAtlas(Mutex::new(BladeAtlasState {
@@ -94,14 +99,23 @@ impl BladeAtlas {
         sync_point
     }
 
-    pub fn get_texture_view(&self, id: AtlasTextureId) -> gpu::TextureView {
+    pub fn get_texture_info(&self, id: AtlasTextureId) -> BladeTextureInfo {
         let lock = self.0.lock();
         let textures = match id.kind {
             crate::AtlasTextureKind::Monochrome => &lock.monochrome_textures,
             crate::AtlasTextureKind::Polychrome => &lock.polychrome_textures,
             crate::AtlasTextureKind::Path => &lock.path_textures,
         };
-        textures[id.index as usize].raw_view.unwrap()
+        let texture = &textures[id.index as usize];
+        let size = texture.allocator.size();
+        BladeTextureInfo {
+            size: gpu::Extent {
+                width: size.width as u32,
+                height: size.height as u32,
+                depth: 1,
+            },
+            raw_view: texture.raw_view,
+        }
     }
 }
 

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

@@ -10,7 +10,7 @@ use bytemuck::{Pod, Zeroable};
 use collections::HashMap;
 
 use blade_graphics as gpu;
-use std::sync::Arc;
+use std::{mem, sync::Arc};
 
 const SURFACE_FRAME_COUNT: u32 = 3;
 const MAX_FRAME_TIME_MS: u32 = 1000;
@@ -34,6 +34,12 @@ struct ShaderShadowsData {
     b_shadows: gpu::BufferPiece,
 }
 
+#[derive(blade_macros::ShaderData)]
+struct ShaderPathRasterizationData {
+    globals: GlobalParams,
+    b_path_vertices: gpu::BufferPiece,
+}
+
 struct BladePipelines {
     quads: gpu::RenderPipeline,
     shadows: gpu::RenderPipeline,
@@ -47,8 +53,14 @@ impl BladePipelines {
         });
         shader.check_struct_size::<Quad>();
         shader.check_struct_size::<Shadow>();
+        assert_eq!(
+            mem::size_of::<PathVertex<ScaledPixels>>(),
+            shader.get_struct_size("PathVertex") as usize,
+        );
         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();
+
         Self {
             quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
                 name: "quads",
@@ -84,7 +96,7 @@ impl BladePipelines {
             }),
             path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
                 name: "path_rasterization",
-                data_layouts: &[&shadows_layout],
+                data_layouts: &[&path_rasterization_layout],
                 vertex: shader.at("vs_path_rasterization"),
                 primitive: gpu::PrimitiveState {
                     topology: gpu::PrimitiveTopology::TriangleStrip,
@@ -196,10 +208,16 @@ impl BladeRenderer {
         }
 
         for (texture_id, vertices) in vertices_by_texture_id {
-            let instances = self.instance_belt.alloc_data(&vertices, &self.gpu);
+            let tex_info = self.atlas.get_texture_info(texture_id);
+            let globals = GlobalParams {
+                viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32],
+                pad: [0; 2],
+            };
+
+            let vertex_buf = self.instance_belt.alloc_data(&vertices, &self.gpu);
             let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
                 colors: &[gpu::RenderTarget {
-                    view: self.atlas.get_texture_view(texture_id),
+                    view: tex_info.raw_view.unwrap(),
                     init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack),
                     finish_op: gpu::FinishOp::Store,
                 }],
@@ -207,6 +225,13 @@ impl BladeRenderer {
             });
 
             let mut encoder = pass.with(&self.pipelines.path_rasterization);
+            encoder.bind(
+                0,
+                &ShaderPathRasterizationData {
+                    globals,
+                    b_path_vertices: vertex_buf,
+                },
+            );
             encoder.draw(0, vertices.len() as u32, 0, 1);
         }
     }
@@ -237,25 +262,25 @@ impl BladeRenderer {
             for batch in scene.batches() {
                 match batch {
                     PrimitiveBatch::Quads(quads) => {
-                        let instances = self.instance_belt.alloc_data(quads, &self.gpu);
+                        let instance_buf = self.instance_belt.alloc_data(quads, &self.gpu);
                         let mut encoder = pass.with(&self.pipelines.quads);
                         encoder.bind(
                             0,
                             &ShaderQuadsData {
                                 globals,
-                                b_quads: instances,
+                                b_quads: instance_buf,
                             },
                         );
                         encoder.draw(0, 4, 0, quads.len() as u32);
                     }
                     PrimitiveBatch::Shadows(shadows) => {
-                        let instances = self.instance_belt.alloc_data(shadows, &self.gpu);
+                        let instance_buf = self.instance_belt.alloc_data(shadows, &self.gpu);
                         let mut encoder = pass.with(&self.pipelines.shadows);
                         encoder.bind(
                             0,
                             &ShaderShadowsData {
                                 globals,
-                                b_shadows: instances,
+                                b_shadows: instance_buf,
                             },
                         );
                         encoder.draw(0, 4, 0, shadows.len() as u32);

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

@@ -35,19 +35,27 @@ struct Hsla {
     a: f32,
 }
 
-fn to_device_position(unit_vertex: vec2<f32>, bounds: Bounds) -> vec4<f32> {
-    let position = unit_vertex * vec2<f32>(bounds.size) + bounds.origin;
+fn to_device_position_impl(position: vec2<f32>) -> vec4<f32> {
     let device_position = position / globals.viewport_size * vec2<f32>(2.0, -2.0) + vec2<f32>(-1.0, 1.0);
     return vec4<f32>(device_position, 0.0, 1.0);
 }
 
-fn distance_from_clip_rect(unit_vertex: vec2<f32>, bounds: Bounds, clip_bounds: Bounds) -> vec4<f32> {
+fn to_device_position(unit_vertex: vec2<f32>, bounds: Bounds) -> vec4<f32> {
     let position = unit_vertex * vec2<f32>(bounds.size) + bounds.origin;
+    return to_device_position_impl(position);
+}
+
+fn distance_from_clip_rect_impl(position: vec2<f32>, clip_bounds: Bounds) -> vec4<f32> {
     let tl = position - clip_bounds.origin;
     let br = clip_bounds.origin + clip_bounds.size - position;
     return vec4<f32>(tl.x, br.x, tl.y, br.y);
 }
 
+fn distance_from_clip_rect(unit_vertex: vec2<f32>, bounds: Bounds, clip_bounds: Bounds) -> vec4<f32> {
+    let position = unit_vertex * vec2<f32>(bounds.size) + bounds.origin;
+    return distance_from_clip_rect_impl(position, clip_bounds);
+}
+
 fn hsla_to_rgba(hsla: Hsla) -> vec4<f32> {
     let h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
     let s = hsla.s;
@@ -289,3 +297,42 @@ fn fs_shadow(input: ShadowVarying) -> @location(0) vec4<f32> {
 }
 
 // --- path rasterization --- //
+
+struct PathVertex {
+    xy_position: vec2<f32>,
+    st_position: vec2<f32>,
+    content_mask: Bounds,
+}
+var<storage, read> b_path_vertices: array<PathVertex>;
+
+struct PathRasterizationVarying {
+    @builtin(position) position: vec4<f32>,
+    @location(0) st_position: vec2<f32>,
+    //TODO: use `clip_distance` once Naga supports it
+    @location(3) clip_distances: vec4<f32>,
+}
+
+@vertex
+fn vs_path_rasterization(@builtin(vertex_index) vertex_id: u32) -> PathRasterizationVarying {
+    let v = b_path_vertices[vertex_id];
+
+    var out = PathRasterizationVarying();
+    out.position = to_device_position_impl(v.xy_position);
+    out.st_position = v.st_position;
+    out.clip_distances = distance_from_clip_rect_impl(v.xy_position, v.content_mask);
+    return out;
+}
+
+@fragment
+fn fs_path_rasterization(input: PathRasterizationVarying) -> @location(0) f32 {
+	let dx = dpdx(input.st_position);
+	let dy = dpdy(input.st_position);
+    if (any(input.clip_distances < vec4<f32>(0.0))) {
+        return 0.0;
+    }
+
+	let gradient = 2.0 * input.st_position * vec2<f32>(dx.x, dy.x) - vec2<f32>(dx.y, dy.y);
+	let f = input.st_position.x * input.st_position.x - input.st_position.y;
+	let distance = f / length(gradient);
+	return saturate(0.5 - distance);
+}

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

@@ -15,7 +15,7 @@ use font_kit::{
 };
 use parking_lot::RwLock;
 use smallvec::SmallVec;
-use std::{borrow::Cow};
+use std::borrow::Cow;
 
 pub(crate) struct LinuxTextSystem(RwLock<LinuxTextSystemState>);