blade: path sprite rendering

Dzmitry Malyshau created

Change summary

crates/gpui/src/platform.rs                      |  1 
crates/gpui/src/platform/linux/blade_atlas.rs    |  1 
crates/gpui/src/platform/linux/blade_renderer.rs | 78 +++++++++++++++++
crates/gpui/src/platform/linux/shaders.wgsl      | 66 +++++++++++++-
crates/gpui/src/platform/test/window.rs          |  1 
5 files changed, 138 insertions(+), 9 deletions(-)

Detailed changes

crates/gpui/src/platform.rs 🔗

@@ -304,6 +304,7 @@ pub(crate) trait PlatformAtlas: Send + Sync {
 pub(crate) struct AtlasTile {
     pub(crate) texture_id: AtlasTextureId,
     pub(crate) tile_id: TileId,
+    pub(crate) padding: u32,
     pub(crate) bounds: Bounds<DevicePixels>,
 }
 

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

@@ -276,6 +276,7 @@ impl BladeAtlasTexture {
         let tile = AtlasTile {
             texture_id: self.id,
             tile_id: allocation.id.into(),
+            padding: 0,
             bounds: Bounds {
                 origin: allocation.rectangle.min.into(),
                 size,

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

@@ -3,8 +3,8 @@
 
 use super::{BladeBelt, BladeBeltDescriptor};
 use crate::{
-    AtlasTextureKind, AtlasTile, BladeAtlas, ContentMask, Path, PathId, PathVertex, PrimitiveBatch,
-    Quad, ScaledPixels, Scene, Shadow, PATH_TEXTURE_FORMAT,
+    AtlasTextureKind, AtlasTile, BladeAtlas, Bounds, ContentMask, Hsla, Path, PathId, PathVertex,
+    PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, PATH_TEXTURE_FORMAT,
 };
 use bytemuck::{Pod, Zeroable};
 use collections::HashMap;
@@ -40,10 +40,27 @@ struct ShaderPathRasterizationData {
     b_path_vertices: gpu::BufferPiece,
 }
 
+#[derive(blade_macros::ShaderData)]
+struct ShaderPathsData {
+    globals: GlobalParams,
+    t_tile: gpu::TextureView,
+    s_tile: gpu::Sampler,
+    b_path_sprites: gpu::BufferPiece,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[repr(C)]
+struct PathSprite {
+    bounds: Bounds<ScaledPixels>,
+    color: Hsla,
+    tile: AtlasTile,
+}
+
 struct BladePipelines {
     quads: gpu::RenderPipeline,
     shadows: gpu::RenderPipeline,
     path_rasterization: gpu::RenderPipeline,
+    paths: gpu::RenderPipeline,
 }
 
 impl BladePipelines {
@@ -57,9 +74,12 @@ impl BladePipelines {
             mem::size_of::<PathVertex<ScaledPixels>>(),
             shader.get_struct_size("PathVertex") as usize,
         );
+        shader.check_struct_size::<PathSprite>();
+
         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();
 
         Self {
             quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
@@ -110,6 +130,22 @@ impl BladePipelines {
                     write_mask: gpu::ColorWrites::default(),
                 }],
             }),
+            paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
+                name: "paths",
+                data_layouts: &[&paths_layout],
+                vertex: shader.at("vs_path"),
+                primitive: gpu::PrimitiveState {
+                    topology: gpu::PrimitiveTopology::TriangleStrip,
+                    ..Default::default()
+                },
+                depth_stencil: None,
+                fragment: shader.at("fs_path"),
+                color_targets: &[gpu::ColorTargetState {
+                    format: surface_format,
+                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
+                    write_mask: gpu::ColorWrites::default(),
+                }],
+            }),
         }
     }
 }
@@ -123,6 +159,7 @@ pub struct BladeRenderer {
     viewport_size: gpu::Extent,
     path_tiles: HashMap<PathId, AtlasTile>,
     atlas: Arc<BladeAtlas>,
+    atlas_sampler: gpu::Sampler,
 }
 
 impl BladeRenderer {
@@ -142,6 +179,12 @@ impl BladeRenderer {
             min_chunk_size: 0x1000,
         });
         let atlas = Arc::new(BladeAtlas::new(&gpu));
+        let atlas_sampler = gpu.create_sampler(gpu::SamplerDesc {
+            name: "atlas",
+            mag_filter: gpu::FilterMode::Linear,
+            min_filter: gpu::FilterMode::Linear,
+            ..Default::default()
+        });
 
         Self {
             gpu,
@@ -152,6 +195,7 @@ impl BladeRenderer {
             viewport_size: size,
             path_tiles: HashMap::default(),
             atlas,
+            atlas_sampler,
         }
     }
 
@@ -285,7 +329,35 @@ impl BladeRenderer {
                         );
                         encoder.draw(0, 4, 0, shadows.len() as u32);
                     }
-                    PrimitiveBatch::Paths(paths) => {}
+                    PrimitiveBatch::Paths(paths) => {
+                        let mut encoder = pass.with(&self.pipelines.paths);
+                        //TODO: group by texture ID
+                        for path in paths {
+                            let tile = &self.path_tiles[&path.id];
+                            let tex_info = self.atlas.get_texture_info(tile.texture_id);
+                            let origin = path.bounds.intersect(&path.content_mask.bounds).origin;
+                            let sprites = [PathSprite {
+                                bounds: Bounds {
+                                    origin: origin.map(|p| p.floor()),
+                                    size: tile.bounds.size.map(Into::into),
+                                },
+                                color: path.color,
+                                tile: (*tile).clone(),
+                            }];
+
+                            let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
+                            encoder.bind(
+                                0,
+                                &ShaderPathsData {
+                                    globals,
+                                    t_tile: tex_info.raw_view.unwrap(),
+                                    s_tile: self.atlas_sampler,
+                                    b_path_sprites: instance_buf,
+                                },
+                            );
+                            encoder.draw(0, 4, 0, sprites.len() as u32);
+                        }
+                    }
                     _ => continue,
                 }
             }

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

@@ -4,6 +4,8 @@ struct Globals {
 }
 
 var<uniform> globals: Globals;
+var t_tile: texture_2d<f32>;
+var s_tile: sampler;
 
 const M_PI_F: f32 = 3.1415926;
 
@@ -35,6 +37,18 @@ struct Hsla {
     a: f32,
 }
 
+struct AtlasTextureId {
+    index: u32,
+    kind: u32,
+}
+
+struct AtlasTile {
+    texture_id: AtlasTextureId,
+    tile_id: u32,
+    padding: u32,
+    bounds: Bounds,
+}
+
 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);
@@ -45,6 +59,11 @@ fn to_device_position(unit_vertex: vec2<f32>, bounds: Bounds) -> vec4<f32> {
     return to_device_position_impl(position);
 }
 
+fn to_tile_position(unit_vertex: vec2<f32>, tile: AtlasTile) -> vec2<f32> {
+  let atlas_size = vec2<f32>(textureDimensions(t_tile, 0));
+  return (tile.bounds.origin + unit_vertex * tile.bounds.size) / atlas_size;
+}
+
 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;
@@ -325,14 +344,49 @@ fn vs_path_rasterization(@builtin(vertex_index) vertex_id: u32) -> PathRasteriza
 
 @fragment
 fn fs_path_rasterization(input: PathRasterizationVarying) -> @location(0) f32 {
-	let dx = dpdx(input.st_position);
-	let dy = dpdy(input.st_position);
+    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);
+    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);
+}
+
+// --- paths --- //
+
+struct PathSprite {
+    bounds: Bounds,
+    color: Hsla,
+    tile: AtlasTile,
+}
+var<storage, read> b_path_sprites: array<PathSprite>;
+
+struct PathVarying {
+    @builtin(position) position: vec4<f32>,
+    @location(0) tile_position: vec2<f32>,
+    @location(1) color: vec4<f32>,
+}
+
+@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 sprite = b_path_sprites[instance_id];
+    // Don't apply content mask because it was already accounted for when rasterizing the path.
+
+    var out = PathVarying();
+    out.position = to_device_position(unit_vertex, sprite.bounds);
+    out.tile_position = to_tile_position(unit_vertex, sprite.tile);
+    out.color = hsla_to_rgba(sprite.color);
+    return out;
+}
+
+@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;
 }

crates/gpui/src/platform/test/window.rs 🔗

@@ -344,6 +344,7 @@ impl PlatformAtlas for TestAtlas {
                     kind: crate::AtlasTextureKind::Path,
                 },
                 tile_id: TileId(tile_id),
+                padding: 0,
                 bounds: crate::Bounds {
                     origin: Point::default(),
                     size,