blade: mono/poly chrome sprite rendering

Dzmitry Malyshau created

Change summary

crates/gpui/src/platform/linux/blade_renderer.rs | 114 +++++++++++++++--
crates/gpui/src/platform/linux/shaders.wgsl      | 113 +++++++++++++++++
crates/gpui/src/scene.rs                         |   1 
crates/gpui/src/window/element_cx.rs             |   2 
4 files changed, 208 insertions(+), 22 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, Underline, PATH_TEXTURE_FORMAT,
+    PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Underline, MonochromeSprite, PolychromeSprite, PATH_TEXTURE_FORMAT,
 };
 use bytemuck::{Pod, Zeroable};
 use collections::HashMap;
@@ -43,8 +43,8 @@ struct ShaderPathRasterizationData {
 #[derive(blade_macros::ShaderData)]
 struct ShaderPathsData {
     globals: GlobalParams,
-    t_tile: gpu::TextureView,
-    s_tile: gpu::Sampler,
+    t_sprite: gpu::TextureView,
+    s_sprite: gpu::Sampler,
     b_path_sprites: gpu::BufferPiece,
 }
 
@@ -54,6 +54,22 @@ struct ShaderUnderlinesData {
     b_underlines: gpu::BufferPiece,
 }
 
+#[derive(blade_macros::ShaderData)]
+struct ShaderMonoSpritesData {
+    globals: GlobalParams,
+    t_sprite: gpu::TextureView,
+    s_sprite: gpu::Sampler,
+    b_mono_sprites: gpu::BufferPiece,
+}
+
+#[derive(blade_macros::ShaderData)]
+struct ShaderPolySpritesData {
+    globals: GlobalParams,
+    t_sprite: gpu::TextureView,
+    s_sprite: gpu::Sampler,
+    b_poly_sprites: gpu::BufferPiece,
+}
+
 #[derive(Clone, Debug, Eq, PartialEq)]
 #[repr(C)]
 struct PathSprite {
@@ -68,10 +84,14 @@ struct BladePipelines {
     path_rasterization: gpu::RenderPipeline,
     paths: gpu::RenderPipeline,
     underlines: gpu::RenderPipeline,
+    mono_sprites: gpu::RenderPipeline,
+    poly_sprites: gpu::RenderPipeline,
 }
 
 impl BladePipelines {
     fn new(gpu: &gpu::Context, surface_format: gpu::TextureFormat) -> Self {
+        use gpu::ShaderData as _;
+
         let shader = gpu.create_shader(gpu::ShaderDesc {
             source: include_str!("shaders.wgsl"),
         });
@@ -83,17 +103,13 @@ impl BladePipelines {
         );
         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();
+        shader.check_struct_size::<MonochromeSprite>();
+        shader.check_struct_size::<PolychromeSprite>();
 
         Self {
             quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
                 name: "quads",
-                data_layouts: &[&quads_layout],
+                data_layouts: &[&ShaderQuadsData::layout()],
                 vertex: shader.at("vs_quad"),
                 primitive: gpu::PrimitiveState {
                     topology: gpu::PrimitiveTopology::TriangleStrip,
@@ -109,7 +125,7 @@ impl BladePipelines {
             }),
             shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
                 name: "shadows",
-                data_layouts: &[&shadows_layout],
+                data_layouts: &[&ShaderShadowsData::layout()],
                 vertex: shader.at("vs_shadow"),
                 primitive: gpu::PrimitiveState {
                     topology: gpu::PrimitiveTopology::TriangleStrip,
@@ -125,7 +141,7 @@ impl BladePipelines {
             }),
             path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
                 name: "path_rasterization",
-                data_layouts: &[&path_rasterization_layout],
+                data_layouts: &[&ShaderPathRasterizationData::layout()],
                 vertex: shader.at("vs_path_rasterization"),
                 primitive: gpu::PrimitiveState {
                     topology: gpu::PrimitiveTopology::TriangleStrip,
@@ -141,7 +157,7 @@ impl BladePipelines {
             }),
             paths: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
                 name: "paths",
-                data_layouts: &[&paths_layout],
+                data_layouts: &[&ShaderPathsData::layout()],
                 vertex: shader.at("vs_path"),
                 primitive: gpu::PrimitiveState {
                     topology: gpu::PrimitiveTopology::TriangleStrip,
@@ -157,7 +173,7 @@ impl BladePipelines {
             }),
             underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
                 name: "underlines",
-                data_layouts: &[&underlines_layout],
+                data_layouts: &[&ShaderUnderlinesData::layout()],
                 vertex: shader.at("vs_underline"),
                 primitive: gpu::PrimitiveState {
                     topology: gpu::PrimitiveTopology::TriangleStrip,
@@ -171,6 +187,38 @@ impl BladePipelines {
                     write_mask: gpu::ColorWrites::default(),
                 }],
             }),
+            mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
+                name: "mono-sprites",
+                data_layouts: &[&ShaderMonoSpritesData::layout()],
+                vertex: shader.at("vs_mono_sprite"),
+                primitive: gpu::PrimitiveState {
+                    topology: gpu::PrimitiveTopology::TriangleStrip,
+                    ..Default::default()
+                },
+                depth_stencil: None,
+                fragment: shader.at("fs_mono_sprite"),
+                color_targets: &[gpu::ColorTargetState {
+                    format: surface_format,
+                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
+                    write_mask: gpu::ColorWrites::default(),
+                }],
+            }),
+            poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
+                name: "poly-sprites",
+                data_layouts: &[&ShaderPolySpritesData::layout()],
+                vertex: shader.at("vs_poly_sprite"),
+                primitive: gpu::PrimitiveState {
+                    topology: gpu::PrimitiveTopology::TriangleStrip,
+                    ..Default::default()
+                },
+                depth_stencil: None,
+                fragment: shader.at("fs_poly_sprite"),
+                color_targets: &[gpu::ColorTargetState {
+                    format: surface_format,
+                    blend: Some(gpu::BlendState::ALPHA_BLENDING),
+                    write_mask: gpu::ColorWrites::default(),
+                }],
+            }),
         }
     }
 }
@@ -376,8 +424,8 @@ impl BladeRenderer {
                                 0,
                                 &ShaderPathsData {
                                     globals,
-                                    t_tile: tex_info.raw_view.unwrap(),
-                                    s_tile: self.atlas_sampler,
+                                    t_sprite: tex_info.raw_view.unwrap(),
+                                    s_sprite: self.atlas_sampler,
                                     b_path_sprites: instance_buf,
                                 },
                             );
@@ -396,7 +444,39 @@ impl BladeRenderer {
                         );
                         encoder.draw(0, 4, 0, underlines.len() as u32);
                     }
-                    _ => continue,
+                    PrimitiveBatch::MonochromeSprites { texture_id, sprites } => {
+                        let tex_info = self.atlas.get_texture_info(texture_id);
+                        let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
+                        let mut encoder = pass.with(&self.pipelines.mono_sprites);
+                        encoder.bind(
+                            0,
+                            &ShaderMonoSpritesData {
+                                globals,
+                                t_sprite: tex_info.raw_view.unwrap(),
+                                s_sprite: self.atlas_sampler,
+                                b_mono_sprites: instance_buf,
+                            },
+                        );
+                        encoder.draw(0, 4, 0, sprites.len() as u32);
+                    }
+                    PrimitiveBatch::PolychromeSprites { texture_id, sprites } => {
+                        let tex_info = self.atlas.get_texture_info(texture_id);
+                        let instance_buf = self.instance_belt.alloc_data(&sprites, &self.gpu);
+                        let mut encoder = pass.with(&self.pipelines.poly_sprites);
+                        encoder.bind(
+                            0,
+                            &ShaderPolySpritesData {
+                                globals,
+                                t_sprite: tex_info.raw_view.unwrap(),
+                                s_sprite: self.atlas_sampler,
+                                b_poly_sprites: instance_buf,
+                            },
+                        );
+                        encoder.draw(0, 4, 0, sprites.len() as u32);
+                    }
+                    PrimitiveBatch::Surfaces {..} => {
+                        unimplemented!()
+                    }
                 }
             }
         }

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

@@ -4,10 +4,11 @@ struct Globals {
 }
 
 var<uniform> globals: Globals;
-var t_tile: texture_2d<f32>;
-var s_tile: sampler;
+var t_sprite: texture_2d<f32>;
+var s_sprite: sampler;
 
 const M_PI_F: f32 = 3.1415926;
+const GRAYSCALE_FACTORS: vec3<f32> = vec3<f32>(0.2126, 0.7152, 0.0722);
 
 struct ViewId {
     lo: u32,
@@ -60,7 +61,7 @@ fn to_device_position(unit_vertex: vec2<f32>, bounds: Bounds) -> vec4<f32> {
 }
 
 fn to_tile_position(unit_vertex: vec2<f32>, tile: AtlasTile) -> vec2<f32> {
-  let atlas_size = vec2<f32>(textureDimensions(t_tile, 0));
+  let atlas_size = vec2<f32>(textureDimensions(t_sprite, 0));
   return (tile.bounds.origin + unit_vertex * tile.bounds.size) / atlas_size;
 }
 
@@ -153,6 +154,17 @@ fn pick_corner_radius(point: vec2<f32>, radii: Corners) -> f32 {
     }
 }
 
+fn quad_sdf(point: vec2<f32>, bounds: Bounds, corner_radii: Corners) -> f32 {
+    let half_size = bounds.size / 2.0;
+    let center = bounds.origin + half_size;
+    let center_to_point = point - center;
+    let corner_radius = pick_corner_radius(center_to_point, corner_radii);
+    let rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
+    return length(max(vec2<f32>(0.0), rounded_edge_to_point)) +
+        min(0.0, max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
+        corner_radius;
+}
+
 // --- quads --- //
 
 struct Quad {
@@ -386,7 +398,7 @@ 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 sample = textureSample(t_sprite, s_sprite, input.tile_position).r;
     let mask = 1.0 - abs(1.0 - sample % 2.0);
     return input.color * mask;
 }
@@ -434,7 +446,7 @@ fn fs_underline(input: UnderlineVarying) -> @location(0) vec4<f32> {
     }
 
     let underline = b_underlines[input.underline_id];
-    if (underline.wavy == 0u)
+    if ((underline.wavy & 0xFFu) == 0u)
     {
         return vec4<f32>(0.0);
     }
@@ -452,3 +464,94 @@ fn fs_underline(input: UnderlineVarying) -> @location(0) vec4<f32> {
     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);
 }
+
+// --- monochrome sprites --- //
+
+struct MonochromeSprite {
+    view_id: ViewId,
+    layer_id: u32,
+    order: u32,
+    bounds: Bounds,
+    content_mask: Bounds,
+    color: Hsla,
+    tile: AtlasTile,
+}
+var<storage, read> b_mono_sprites: array<MonochromeSprite>;
+
+struct MonoSpriteVarying {
+    @builtin(position) position: vec4<f32>,
+    @location(0) tile_position: vec2<f32>,
+    @location(1) @interpolate(flat) color: vec4<f32>,
+    @location(3) clip_distances: vec4<f32>,
+}
+
+@vertex
+fn vs_mono_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> MonoSpriteVarying {
+    let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
+    let sprite = b_mono_sprites[instance_id];
+
+    var out = MonoSpriteVarying();
+    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);
+    out.clip_distances = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask);
+    return out;
+}
+
+@fragment
+fn fs_mono_sprite(input: MonoSpriteVarying) -> @location(0) vec4<f32> {
+    let sample = textureSample(t_sprite, s_sprite, input.tile_position).r;
+    return input.color * vec4<f32>(1.0, 1.0, 1.0, sample);
+}
+
+// --- polychrome sprites --- //
+
+struct PolychromeSprite {
+    view_id: ViewId,
+    layer_id: u32,
+    order: u32,
+    bounds: Bounds,
+    content_mask: Bounds,
+    corner_radii: Corners,
+    tile: AtlasTile,
+    grayscale: u32,
+    pad: u32,
+}
+var<storage, read> b_poly_sprites: array<PolychromeSprite>;
+
+struct PolySpriteVarying {
+    @builtin(position) position: vec4<f32>,
+    @location(0) tile_position: vec2<f32>,
+    @location(1) @interpolate(flat) sprite_id: u32,
+    @location(3) clip_distances: vec4<f32>,
+}
+
+@vertex
+fn vs_poly_sprite(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> PolySpriteVarying {
+    let unit_vertex = vec2<f32>(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u));
+    let sprite = b_poly_sprites[instance_id];
+
+    var out = PolySpriteVarying();
+    out.position = to_device_position(unit_vertex, sprite.bounds);
+    out.tile_position = to_tile_position(unit_vertex, sprite.tile);
+    out.sprite_id = instance_id;
+    out.clip_distances = distance_from_clip_rect(unit_vertex, sprite.bounds, sprite.content_mask);
+    return out;
+}
+
+@fragment
+fn fs_poly_sprite(input: PolySpriteVarying) -> @location(0) vec4<f32> {
+    let sample = textureSample(t_sprite, s_sprite, input.tile_position);
+    let sprite = b_poly_sprites[input.sprite_id];
+    let distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
+
+    var color = sample;
+    if ((sprite.grayscale & 0xFFu) != 0u) {
+        let grayscale = dot(color.rgb, GRAYSCALE_FACTORS);
+        color = vec4<f32>(vec3<f32>(grayscale), sample.a);
+    }
+    color.a *= saturate(0.5 - distance);
+    return color;;
+}
+
+// --- surface sprites --- //

crates/gpui/src/scene.rs 🔗

@@ -642,6 +642,7 @@ pub(crate) struct PolychromeSprite {
     pub corner_radii: Corners<ScaledPixels>,
     pub tile: AtlasTile,
     pub grayscale: bool,
+    pub pad: u32, // align to 8 bytes
 }
 
 impl Ord for PolychromeSprite {

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

@@ -874,6 +874,7 @@ impl<'a> ElementContext<'a> {
                     content_mask,
                     tile,
                     grayscale: false,
+                    pad: 0,
                 },
             );
         }
@@ -958,6 +959,7 @@ impl<'a> ElementContext<'a> {
                 corner_radii,
                 tile,
                 grayscale,
+                pad: 0,
             },
         );
         Ok(())