Checkpoint: beziers

Antonio Scandurra created

Change summary

crates/gpui3/build.rs                           |   3 
crates/gpui3/src/platform/mac/metal_atlas.rs    |  22 +
crates/gpui3/src/platform/mac/metal_renderer.rs | 291 ++++++++++++++----
crates/gpui3/src/platform/mac/shaders.metal     |  88 +++++
crates/gpui3/src/scene.rs                       |  91 ++++-
crates/gpui3/src/window.rs                      |   7 
6 files changed, 401 insertions(+), 101 deletions(-)

Detailed changes

crates/gpui3/build.rs 🔗

@@ -50,6 +50,8 @@ fn generate_shader_bindings() -> PathBuf {
         "ContentMask".into(),
         "Uniforms".into(),
         "AtlasTile".into(),
+        "PathRasterizationInputIndex".into(),
+        "PathVertex_ScaledPixels".into(),
         "ShadowInputIndex".into(),
         "Shadow".into(),
         "QuadInputIndex".into(),
@@ -59,6 +61,7 @@ fn generate_shader_bindings() -> PathBuf {
         "SpriteInputIndex".into(),
         "MonochromeSprite".into(),
         "PolychromeSprite".into(),
+        "PathSprite".into(),
     ]);
     config.no_includes = true;
     config.enumeration.prefix_with_name = true;

crates/gpui3/src/platform/mac/metal_atlas.rs 🔗

@@ -99,12 +99,24 @@ impl MetalAtlasState {
         let texture_descriptor = metal::TextureDescriptor::new();
         texture_descriptor.set_width(size.width.into());
         texture_descriptor.set_height(size.height.into());
-        let pixel_format = match kind {
-            AtlasTextureKind::Monochrome => metal::MTLPixelFormat::A8Unorm,
-            AtlasTextureKind::Polychrome => metal::MTLPixelFormat::BGRA8Unorm,
-            AtlasTextureKind::Path => metal::MTLPixelFormat::R16Float,
-        };
+        let pixel_format;
+        let usage;
+        match kind {
+            AtlasTextureKind::Monochrome => {
+                pixel_format = metal::MTLPixelFormat::A8Unorm;
+                usage = metal::MTLTextureUsage::ShaderRead;
+            }
+            AtlasTextureKind::Polychrome => {
+                pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
+                usage = metal::MTLTextureUsage::ShaderRead;
+            }
+            AtlasTextureKind::Path => {
+                pixel_format = metal::MTLPixelFormat::R16Float;
+                usage = metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead;
+            }
+        }
         texture_descriptor.set_pixel_format(pixel_format);
+        texture_descriptor.set_usage(usage);
         let metal_texture = self.device.new_texture(&texture_descriptor);
 
         let textures = match kind {

crates/gpui3/src/platform/mac/metal_renderer.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
-    MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad,
-    ScaledPixels, Scene, Shadow, Size, Underline,
+    Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
+    Quad, ScaledPixels, Scene, Shadow, Size, Underline,
 };
 use cocoa::{
     base::{NO, YES},
@@ -11,6 +11,7 @@ use cocoa::{
 use collections::HashMap;
 use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
 use objc::{self, msg_send, sel, sel_impl};
+use smallvec::SmallVec;
 use std::{ffi::c_void, mem, ptr, sync::Arc};
 
 const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
@@ -19,6 +20,8 @@ const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decisio
 pub(crate) struct MetalRenderer {
     layer: metal::MetalLayer,
     command_queue: CommandQueue,
+    paths_rasterization_pipeline_state: metal::RenderPipelineState,
+    path_sprites_pipeline_state: metal::RenderPipelineState,
     shadows_pipeline_state: metal::RenderPipelineState,
     quads_pipeline_state: metal::RenderPipelineState,
     underlines_pipeline_state: metal::RenderPipelineState,
@@ -31,8 +34,6 @@ pub(crate) struct MetalRenderer {
 
 impl MetalRenderer {
     pub fn new(is_opaque: bool) -> Self {
-        const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
-
         let device: metal::Device = if let Some(device) = metal::Device::system_default() {
             device
         } else {
@@ -42,7 +43,7 @@ impl MetalRenderer {
 
         let layer = metal::MetalLayer::new();
         layer.set_device(&device);
-        layer.set_pixel_format(PIXEL_FORMAT);
+        layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
         layer.set_presents_with_transaction(true);
         layer.set_opaque(is_opaque);
         unsafe {
@@ -86,13 +87,29 @@ impl MetalRenderer {
             MTLResourceOptions::StorageModeManaged,
         );
 
+        let paths_rasterization_pipeline_state = build_pipeline_state(
+            &device,
+            &library,
+            "paths_rasterization",
+            "path_rasterization_vertex",
+            "path_rasterization_fragment",
+            MTLPixelFormat::R16Float,
+        );
+        let path_sprites_pipeline_state = build_pipeline_state(
+            &device,
+            &library,
+            "path_sprites",
+            "path_sprite_vertex",
+            "path_sprite_fragment",
+            MTLPixelFormat::BGRA8Unorm,
+        );
         let shadows_pipeline_state = build_pipeline_state(
             &device,
             &library,
             "shadows",
             "shadow_vertex",
             "shadow_fragment",
-            PIXEL_FORMAT,
+            MTLPixelFormat::BGRA8Unorm,
         );
         let quads_pipeline_state = build_pipeline_state(
             &device,
@@ -100,7 +117,7 @@ impl MetalRenderer {
             "quads",
             "quad_vertex",
             "quad_fragment",
-            PIXEL_FORMAT,
+            MTLPixelFormat::BGRA8Unorm,
         );
         let underlines_pipeline_state = build_pipeline_state(
             &device,
@@ -108,7 +125,7 @@ impl MetalRenderer {
             "underlines",
             "underline_vertex",
             "underline_fragment",
-            PIXEL_FORMAT,
+            MTLPixelFormat::BGRA8Unorm,
         );
         let monochrome_sprites_pipeline_state = build_pipeline_state(
             &device,
@@ -116,7 +133,7 @@ impl MetalRenderer {
             "monochrome_sprites",
             "monochrome_sprite_vertex",
             "monochrome_sprite_fragment",
-            PIXEL_FORMAT,
+            MTLPixelFormat::BGRA8Unorm,
         );
         let polychrome_sprites_pipeline_state = build_pipeline_state(
             &device,
@@ -124,7 +141,7 @@ impl MetalRenderer {
             "polychrome_sprites",
             "polychrome_sprite_vertex",
             "polychrome_sprite_fragment",
-            PIXEL_FORMAT,
+            MTLPixelFormat::BGRA8Unorm,
         );
 
         let command_queue = device.new_command_queue();
@@ -133,6 +150,8 @@ impl MetalRenderer {
         Self {
             layer,
             command_queue,
+            paths_rasterization_pipeline_state,
+            path_sprites_pipeline_state,
             shadows_pipeline_state,
             quads_pipeline_state,
             underlines_pipeline_state,
@@ -172,7 +191,7 @@ impl MetalRenderer {
         let command_buffer = command_queue.new_command_buffer();
         let mut instance_offset = 0;
 
-        // let path_tiles = self.rasterize_paths(scene.paths(), &mut instance_offset, &command_buffer);
+        let path_tiles = self.rasterize_paths(scene.paths(), &mut instance_offset, &command_buffer);
 
         let render_pass_descriptor = metal::RenderPassDescriptor::new();
         let color_attachment = render_pass_descriptor
@@ -208,8 +227,14 @@ impl MetalRenderer {
                 PrimitiveBatch::Quads(quads) => {
                     self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
                 }
-                PrimitiveBatch::Paths(_paths) => {
-                    // self.draw_paths(paths, &mut instance_offset, viewport_size, command_encoder);
+                PrimitiveBatch::Paths(paths) => {
+                    self.draw_paths(
+                        paths,
+                        &path_tiles,
+                        &mut instance_offset,
+                        viewport_size,
+                        command_encoder,
+                    );
                 }
                 PrimitiveBatch::Underlines(underlines) => {
                     self.draw_underlines(
@@ -258,19 +283,20 @@ impl MetalRenderer {
         drawable.present();
     }
 
-    #[allow(dead_code)]
     fn rasterize_paths(
         &mut self,
         paths: &[Path<ScaledPixels>],
-        _offset: &mut usize,
-        _command_buffer: &metal::CommandBufferRef,
+        offset: &mut usize,
+        command_buffer: &metal::CommandBufferRef,
     ) -> HashMap<PathId, AtlasTile> {
         let mut tiles = HashMap::default();
         let mut vertices_by_texture_id = HashMap::default();
         for path in paths {
+            let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
+
             let tile = self
                 .sprite_atlas
-                .allocate(path.bounds.size.map(Into::into), AtlasTextureKind::Path);
+                .allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
             vertices_by_texture_id
                 .entry(tile.texture_id)
                 .or_insert(Vec::new())
@@ -279,68 +305,65 @@ impl MetalRenderer {
                         + tile.bounds.origin.map(Into::into),
                     st_position: vertex.st_position,
                     content_mask: ContentMask {
-                        bounds: Bounds {
-                            origin: vertex.xy_position - path.bounds.origin
-                                + tile.bounds.origin.map(Into::into),
-                            size: vertex.content_mask.bounds.size,
-                        },
+                        bounds: tile.bounds.map(Into::into),
                     },
                 }));
             tiles.insert(path.id, tile);
         }
 
-        for (_texture_id, _vertices) in vertices_by_texture_id {
-            todo!();
-            // align_offset(offset);
-            // let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>();
-            // assert!(
-            //     next_offset <= INSTANCE_BUFFER_SIZE,
-            //     "instance buffer exhausted"
-            // );
-
-            // let render_pass_descriptor = metal::RenderPassDescriptor::new();
-            // let color_attachment = render_pass_descriptor
-            //     .color_attachments()
-            //     .object_at(0)
-            //     .unwrap();
-
-            // let texture = self.sprite_atlas.metal_texture(texture_id);
-            // color_attachment.set_texture(Some(&texture));
-            // color_attachment.set_load_action(metal::MTLLoadAction::Clear);
-            // color_attachment.set_store_action(metal::MTLStoreAction::Store);
-            // color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
-            // let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
-            // command_encoder.set_render_pipeline_state(&self.path_atlas_pipeline_state);
-            // command_encoder.set_vertex_buffer(
-            //     shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexVertices as u64,
-            //     Some(&self.instances),
-            //     *offset as u64,
-            // );
-            // command_encoder.set_vertex_bytes(
-            //     shaders::GPUIPathAtlasVertexInputIndex_GPUIPathAtlasVertexInputIndexAtlasSize
-            //         as u64,
-            //     mem::size_of::<shaders::vector_float2>() as u64,
-            //     [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr()
-            //         as *const c_void,
-            // );
-
-            // let buffer_contents = unsafe {
-            //     (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIPathVertex
-            // };
-
-            // for (ix, vertex) in vertices.iter().enumerate() {
-            //     unsafe {
-            //         *buffer_contents.add(ix) = *vertex;
-            //     }
-            // }
-
-            // command_encoder.draw_primitives(
-            //     metal::MTLPrimitiveType::Triangle,
-            //     0,
-            //     vertices.len() as u64,
-            // );
-            // command_encoder.end_encoding();
-            // *offset = next_offset;
+        for (texture_id, vertices) in vertices_by_texture_id {
+            align_offset(offset);
+            let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>();
+            assert!(
+                next_offset <= INSTANCE_BUFFER_SIZE,
+                "instance buffer exhausted"
+            );
+
+            let render_pass_descriptor = metal::RenderPassDescriptor::new();
+            let color_attachment = render_pass_descriptor
+                .color_attachments()
+                .object_at(0)
+                .unwrap();
+
+            let texture = self.sprite_atlas.metal_texture(texture_id);
+            color_attachment.set_texture(Some(&texture));
+            color_attachment.set_load_action(metal::MTLLoadAction::Clear);
+            color_attachment.set_store_action(metal::MTLStoreAction::Store);
+            color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
+            let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
+            command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
+            command_encoder.set_vertex_buffer(
+                PathRasterizationInputIndex::Vertices as u64,
+                Some(&self.instances),
+                *offset as u64,
+            );
+            let texture_size = Size {
+                width: DevicePixels::from(texture.width()),
+                height: DevicePixels::from(texture.height()),
+            };
+            command_encoder.set_vertex_bytes(
+                PathRasterizationInputIndex::AtlasTextureSize as u64,
+                mem::size_of_val(&texture_size) as u64,
+                &texture_size as *const Size<DevicePixels> as *const _,
+            );
+
+            let vertices_bytes_len = mem::size_of::<PathVertex<ScaledPixels>>() * vertices.len();
+            let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
+            unsafe {
+                ptr::copy_nonoverlapping(
+                    vertices.as_ptr() as *const u8,
+                    buffer_contents,
+                    vertices_bytes_len,
+                );
+            }
+
+            command_encoder.draw_primitives(
+                metal::MTLPrimitiveType::Triangle,
+                0,
+                vertices.len() as u64,
+            );
+            command_encoder.end_encoding();
+            *offset = next_offset;
         }
 
         tiles
@@ -462,6 +485,112 @@ impl MetalRenderer {
         *offset = next_offset;
     }
 
+    fn draw_paths(
+        &mut self,
+        paths: &[Path<ScaledPixels>],
+        tiles_by_path_id: &HashMap<PathId, AtlasTile>,
+        offset: &mut usize,
+        viewport_size: Size<DevicePixels>,
+        command_encoder: &metal::RenderCommandEncoderRef,
+    ) {
+        if paths.is_empty() {
+            return;
+        }
+
+        command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state);
+        command_encoder.set_vertex_buffer(
+            SpriteInputIndex::Vertices as u64,
+            Some(&self.unit_vertices),
+            0,
+        );
+        command_encoder.set_vertex_bytes(
+            SpriteInputIndex::ViewportSize as u64,
+            mem::size_of_val(&viewport_size) as u64,
+            &viewport_size as *const Size<DevicePixels> as *const _,
+        );
+
+        let mut prev_texture_id = None;
+        let mut sprites = SmallVec::<[_; 1]>::new();
+        let mut paths_and_tiles = paths
+            .into_iter()
+            .map(|path| (path, tiles_by_path_id.get(&path.id).unwrap()))
+            .peekable();
+
+        loop {
+            if let Some((path, tile)) = paths_and_tiles.peek() {
+                if prev_texture_id.map_or(true, |texture_id| texture_id == tile.texture_id) {
+                    prev_texture_id = Some(tile.texture_id);
+                    sprites.push(PathSprite {
+                        bounds: Bounds {
+                            origin: path.bounds.origin.map(|p| p.floor()),
+                            size: tile.bounds.size.map(Into::into),
+                        },
+                        color: path.color,
+                        tile: (*tile).clone(),
+                    });
+                    paths_and_tiles.next();
+                    continue;
+                }
+            }
+
+            if sprites.is_empty() {
+                break;
+            } else {
+                align_offset(offset);
+                let texture_id = prev_texture_id.take().unwrap();
+                let texture: metal::Texture = self.sprite_atlas.metal_texture(texture_id);
+                let texture_size = size(
+                    DevicePixels(texture.width() as i32),
+                    DevicePixels(texture.height() as i32),
+                );
+
+                command_encoder.set_vertex_buffer(
+                    SpriteInputIndex::Sprites as u64,
+                    Some(&self.instances),
+                    *offset as u64,
+                );
+                command_encoder.set_vertex_bytes(
+                    SpriteInputIndex::AtlasTextureSize as u64,
+                    mem::size_of_val(&texture_size) as u64,
+                    &texture_size as *const Size<DevicePixels> as *const _,
+                );
+                command_encoder.set_fragment_buffer(
+                    SpriteInputIndex::Sprites as u64,
+                    Some(&self.instances),
+                    *offset as u64,
+                );
+                command_encoder
+                    .set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
+
+                let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
+                let buffer_contents =
+                    unsafe { (self.instances.contents() as *mut u8).add(*offset) };
+                unsafe {
+                    ptr::copy_nonoverlapping(
+                        sprites.as_ptr() as *const u8,
+                        buffer_contents,
+                        sprite_bytes_len,
+                    );
+                }
+
+                let next_offset = *offset + sprite_bytes_len;
+                assert!(
+                    next_offset <= INSTANCE_BUFFER_SIZE,
+                    "instance buffer exhausted"
+                );
+
+                command_encoder.draw_primitives_instanced(
+                    metal::MTLPrimitiveType::Triangle,
+                    0,
+                    6,
+                    sprites.len() as u64,
+                );
+                *offset = next_offset;
+                sprites.clear();
+            }
+        }
+    }
+
     fn draw_underlines(
         &mut self,
         underlines: &[Underline],
@@ -734,3 +863,17 @@ enum SpriteInputIndex {
     AtlasTextureSize = 3,
     AtlasTexture = 4,
 }
+
+#[repr(C)]
+enum PathRasterizationInputIndex {
+    Vertices = 0,
+    AtlasTextureSize = 1,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[repr(C)]
+pub struct PathSprite {
+    pub bounds: Bounds<ScaledPixels>,
+    pub color: Hsla,
+    pub tile: AtlasTile,
+}

crates/gpui3/src/platform/mac/shaders.metal 🔗

@@ -336,6 +336,94 @@ fragment float4 polychrome_sprite_fragment(
   return color;
 }
 
+struct PathRasterizationVertexOutput {
+  float4 position [[position]];
+  float2 st_position;
+  float clip_rect_distance [[clip_distance]][4];
+};
+
+struct PathRasterizationFragmentInput {
+  float4 position [[position]];
+  float2 st_position;
+};
+
+vertex PathRasterizationVertexOutput path_rasterization_vertex(
+    uint vertex_id [[vertex_id]],
+    constant PathVertex_ScaledPixels *vertices
+    [[buffer(PathRasterizationInputIndex_Vertices)]],
+    constant Size_DevicePixels *atlas_size
+    [[buffer(PathRasterizationInputIndex_AtlasTextureSize)]]) {
+  PathVertex_ScaledPixels v = vertices[vertex_id];
+  float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
+  float2 viewport_size = float2(atlas_size->width, atlas_size->height);
+  return PathRasterizationVertexOutput{
+      float4(vertex_position / viewport_size * float2(2., -2.) +
+                 float2(-1., 1.),
+             0., 1.),
+      float2(v.st_position.x, v.st_position.y),
+      {v.xy_position.x - v.content_mask.bounds.origin.x,
+       v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
+           v.xy_position.x,
+       v.xy_position.y - v.content_mask.bounds.origin.y,
+       v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
+           v.xy_position.y}};
+}
+
+fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
+                                            [[stage_in]]) {
+  float2 dx = dfdx(input.st_position);
+  float2 dy = dfdy(input.st_position);
+  float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
+                           (2. * input.st_position.x) * dy.x - dy.y);
+  float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
+  float distance = f / length(gradient);
+  float alpha = saturate(0.5 - distance);
+  return float4(alpha, 0., 0., 1.);
+}
+
+struct PathSpriteVertexOutput {
+  float4 position [[position]];
+  float2 tile_position;
+  float4 color [[flat]];
+  uint sprite_id [[flat]];
+};
+
+vertex PathSpriteVertexOutput path_sprite_vertex(
+    uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
+    constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
+    constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
+    constant Size_DevicePixels *viewport_size
+    [[buffer(SpriteInputIndex_ViewportSize)]],
+    constant Size_DevicePixels *atlas_size
+    [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
+
+  float2 unit_vertex = unit_vertices[unit_vertex_id];
+  PathSprite sprite = sprites[sprite_id];
+  // Don't apply content mask because it was already accounted for when
+  // rasterizing the path.
+  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);
+  float4 color = hsla_to_rgba(sprite.color);
+  return PathSpriteVertexOutput{device_position, tile_position, color,
+                                sprite_id};
+}
+
+fragment float4 path_sprite_fragment(
+    PathSpriteVertexOutput input [[stage_in]],
+    constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
+    texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
+  PathSprite sprite = sprites[input.sprite_id];
+  constexpr sampler atlas_texture_sampler(mag_filter::linear,
+                                          min_filter::linear);
+  float4 sample =
+      atlas_texture.sample(atlas_texture_sampler, input.tile_position);
+  float mask = 1. - abs(1. - fmod(sample.r, 2.));
+  float4 color = input.color;
+  color.a *= mask;
+  return color;
+}
+
 float4 hsla_to_rgba(Hsla hsla) {
   float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
   float s = hsla.s;

crates/gpui3/src/scene.rs 🔗

@@ -8,7 +8,9 @@ use plane_split::{BspSplitter, Polygon as BspPolygon};
 use std::{fmt::Debug, iter::Peekable, mem, slice};
 
 // Exported to metal
-pub type PointF = Point<f32>;
+pub(crate) type PointF = Point<f32>;
+#[allow(non_camel_case_types, unused)]
+pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
 
 pub type LayerId = u32;
 
@@ -62,6 +64,12 @@ impl SceneBuilder {
                 .add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix)));
         }
 
+        for (ix, path) in self.paths.iter().enumerate() {
+            let z = layer_z_values[path.order as LayerId as usize];
+            self.splitter
+                .add(path.bounds.to_bsp_polygon(z, (PrimitiveKind::Path, ix)));
+        }
+
         for (ix, underline) in self.underlines.iter().enumerate() {
             let z = layer_z_values[underline.order as LayerId as usize];
             self.splitter.add(
@@ -131,6 +139,16 @@ impl SceneBuilder {
     }
 
     pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
+        let primitive = primitive.into();
+        let clipped_bounds = primitive
+            .bounds()
+            .intersect(&primitive.content_mask().bounds);
+        if clipped_bounds.size.width <= ScaledPixels(0.)
+            || clipped_bounds.size.height <= ScaledPixels(0.)
+        {
+            return;
+        }
+
         let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
             *layer_id
         } else {
@@ -139,7 +157,6 @@ impl SceneBuilder {
             next_id
         };
 
-        let primitive = primitive.into();
         match primitive {
             Primitive::Shadow(mut shadow) => {
                 shadow.order = layer_id;
@@ -240,6 +257,7 @@ impl<'a> Iterator for BatchIterator<'a> {
                 PrimitiveKind::Shadow,
             ),
             (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
+            (self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
             (
                 self.underlines_iter.peek().map(|u| u.order),
                 PrimitiveKind::Underline,
@@ -382,6 +400,30 @@ pub enum Primitive {
     PolychromeSprite(PolychromeSprite),
 }
 
+impl Primitive {
+    pub fn bounds(&self) -> &Bounds<ScaledPixels> {
+        match self {
+            Primitive::Shadow(shadow) => &shadow.bounds,
+            Primitive::Quad(quad) => &quad.bounds,
+            Primitive::Path(path) => &path.bounds,
+            Primitive::Underline(underline) => &underline.bounds,
+            Primitive::MonochromeSprite(sprite) => &sprite.bounds,
+            Primitive::PolychromeSprite(sprite) => &sprite.bounds,
+        }
+    }
+
+    pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
+        match self {
+            Primitive::Shadow(shadow) => &shadow.content_mask,
+            Primitive::Quad(quad) => &quad.content_mask,
+            Primitive::Path(path) => &path.content_mask,
+            Primitive::Underline(underline) => &underline.content_mask,
+            Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
+            Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
+        }
+    }
+}
+
 #[derive(Debug)]
 pub(crate) enum PrimitiveBatch<'a> {
     Shadows(&'a [Shadow]),
@@ -557,20 +599,29 @@ pub struct Path<P: Clone + Debug> {
     pub(crate) id: PathId,
     order: u32,
     pub(crate) bounds: Bounds<P>,
+    pub(crate) content_mask: ContentMask<P>,
     pub(crate) vertices: Vec<PathVertex<P>>,
-    start: Option<Point<P>>,
+    pub(crate) color: Hsla,
+    start: Point<P>,
     current: Point<P>,
+    contour_count: usize,
 }
 
 impl Path<Pixels> {
-    pub fn new() -> Self {
+    pub fn new(start: Point<Pixels>) -> Self {
         Self {
             id: PathId(0),
             order: 0,
             vertices: Vec::new(),
-            start: Default::default(),
-            current: Default::default(),
-            bounds: Default::default(),
+            start,
+            current: start,
+            bounds: Bounds {
+                origin: start,
+                size: Default::default(),
+            },
+            content_mask: Default::default(),
+            color: Default::default(),
+            contour_count: 0,
         }
     }
 
@@ -579,6 +630,7 @@ impl Path<Pixels> {
             id: self.id,
             order: self.order,
             bounds: self.bounds.scale(factor),
+            content_mask: self.content_mask.scale(factor),
             vertices: self
                 .vertices
                 .iter()
@@ -586,27 +638,36 @@ impl Path<Pixels> {
                 .collect(),
             start: self.start.map(|start| start.scale(factor)),
             current: self.current.scale(factor),
+            contour_count: self.contour_count,
+            color: self.color,
         }
     }
 
     pub fn line_to(&mut self, to: Point<Pixels>) {
-        if let Some(start) = self.start {
+        self.contour_count += 1;
+        if self.contour_count > 1 {
             self.push_triangle(
-                (start, self.current, to),
+                (self.start, self.current, to),
                 (point(0., 1.), point(0., 1.), point(0., 1.)),
             );
-        } else {
-            self.start = Some(to);
         }
         self.current = to;
     }
 
     pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
-        self.line_to(to);
+        self.contour_count += 1;
+        if self.contour_count > 1 {
+            self.push_triangle(
+                (self.start, self.current, to),
+                (point(0., 1.), point(0., 1.), point(0., 1.)),
+            );
+        }
+
         self.push_triangle(
             (self.current, ctrl, to),
             (point(0., 0.), point(0.5, 0.), point(1., 1.)),
         );
+        self.current = to;
     }
 
     fn push_triangle(
@@ -614,12 +675,6 @@ impl Path<Pixels> {
         xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
         st: (Point<f32>, Point<f32>, Point<f32>),
     ) {
-        if self.vertices.is_empty() {
-            self.bounds = Bounds {
-                origin: xy.0,
-                size: Default::default(),
-            };
-        }
         self.bounds = self
             .bounds
             .union(&Bounds {

crates/gpui3/src/window.rs 🔗

@@ -363,12 +363,11 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         );
     }
 
-    pub fn paint_path(&mut self, mut path: Path<Pixels>) {
+    pub fn paint_path(&mut self, mut path: Path<Pixels>, color: impl Into<Hsla>) {
         let scale_factor = self.scale_factor();
         let content_mask = self.content_mask();
-        for vertex in &mut path.vertices {
-            vertex.content_mask = content_mask.clone();
-        }
+        path.content_mask = content_mask;
+        path.color = color.into();
         let window = &mut *self.window;
         window
             .scene_builder