WIP: Add anti-aliasing to paths and render them after spriting them

Antonio Scandurra created

Change summary

gpui/src/platform/mac/renderer.rs           | 212 +++++++++++++++++++---
gpui/src/platform/mac/shaders/shaders.h     |   1 
gpui/src/platform/mac/shaders/shaders.metal |  27 ++
zed/src/editor/buffer_element.rs            |  12 
4 files changed, 206 insertions(+), 46 deletions(-)

Detailed changes

gpui/src/platform/mac/renderer.rs 🔗

@@ -65,12 +65,12 @@ impl Renderer {
             MTLResourceOptions::StorageModeManaged,
         );
 
-        let path_stencil_pixel_format = metal::MTLPixelFormat::Stencil8;
         let path_stencil_descriptor = metal::TextureDescriptor::new();
-        path_stencil_descriptor.set_width(2048);
-        path_stencil_descriptor.set_height(2048);
-        path_stencil_descriptor.set_pixel_format(path_stencil_pixel_format);
-        path_stencil_descriptor.set_usage(metal::MTLTextureUsage::RenderTarget);
+        path_stencil_descriptor.set_width(1024);
+        path_stencil_descriptor.set_height(768);
+        path_stencil_descriptor.set_pixel_format(pixel_format);
+        path_stencil_descriptor
+            .set_usage(metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead);
         path_stencil_descriptor.set_storage_mode(metal::MTLStorageMode::Private);
 
         let sprite_cache = SpriteCache::new(device.clone(), vec2i(1024, 768), fonts);
@@ -105,7 +105,7 @@ impl Renderer {
             "path_winding",
             "path_winding_vertex",
             "path_winding_fragment",
-            path_stencil_pixel_format,
+            pixel_format,
         )?;
         Ok(Self {
             device,
@@ -128,15 +128,21 @@ impl Renderer {
         output: &metal::TextureRef,
     ) {
         let mut offset = 0;
-        self.render_path_stencils(scene, &mut offset, drawable_size, command_buffer);
-        self.render_layers(scene, &mut offset, drawable_size, command_buffer, output);
+        let stencils = self.render_path_stencils(scene, &mut offset, command_buffer);
+        self.render_layers(
+            scene,
+            stencils,
+            &mut offset,
+            drawable_size,
+            command_buffer,
+            output,
+        );
     }
 
     fn render_path_stencils(
         &mut self,
         scene: &Scene,
         offset: &mut usize,
-        drawable_size: Vector2F,
         command_buffer: &metal::CommandBufferRef,
     ) -> Vec<PathSprite> {
         let mut stencils = Vec::new();
@@ -145,16 +151,20 @@ impl Renderer {
         for (layer_id, layer) in scene.layers().iter().enumerate() {
             for path in layer.paths() {
                 // Push a PathStencil struct for use later when sampling from the atlas as we draw the content of the layers
-                let size = path.bounds.size().ceil().to_i32();
-                let (atlas_id, atlas_origin) = self.path_stencils.allocate(size).unwrap();
+                let origin = path.bounds.origin() * scene.scale_factor();
+                let size = (path.bounds.size() * scene.scale_factor()).ceil();
+                let (atlas_id, atlas_origin) =
+                    self.path_stencils.allocate(size.ceil().to_i32()).unwrap();
+                let atlas_origin = atlas_origin.to_f32();
                 stencils.push(PathSprite {
                     layer_id,
                     atlas_id,
                     sprite: shaders::GPUISprite {
-                        origin: path.bounds.origin().to_float2(),
+                        origin: origin.to_float2(),
                         size: size.to_float2(),
                         atlas_origin: atlas_origin.to_float2(),
                         color: path.color.to_uchar4(),
+                        compute_winding: 1,
                     },
                 });
 
@@ -172,11 +182,10 @@ impl Renderer {
 
                 // Populate the vertices by translating them to their appropriate location in the atlas.
                 for vertex in &path.vertices {
-                    let xy_position = (vertex.xy_position - path.bounds.origin())
-                        * scene.scale_factor()
-                        + atlas_origin.to_f32();
+                    let xy_position =
+                        (vertex.xy_position - path.bounds.origin()) * scene.scale_factor();
                     vertices.push(shaders::GPUIPathVertex {
-                        xy_position: xy_position.to_float2(),
+                        xy_position: (atlas_origin + xy_position).to_float2(),
                         st_position: vertex.st_position.to_float2(),
                     });
                 }
@@ -205,25 +214,32 @@ impl Renderer {
         );
 
         let render_pass_descriptor = metal::RenderPassDescriptor::new();
-
-        let stencil_attachment = render_pass_descriptor.stencil_attachment().unwrap();
+        let color_attachment = render_pass_descriptor
+            .color_attachments()
+            .object_at(0)
+            .unwrap();
         let stencil_texture = self.path_stencils.texture(atlas_id).unwrap();
-        stencil_attachment.set_texture(Some(stencil_texture));
-        stencil_attachment.set_load_action(metal::MTLLoadAction::Clear);
-        stencil_attachment.set_store_action(metal::MTLStoreAction::Store);
-
-        let stencil_descriptor = metal::DepthStencilDescriptor::new();
-        let front_face_stencil = stencil_descriptor.front_face_stencil().unwrap();
-        front_face_stencil.set_depth_stencil_pass_operation(metal::MTLStencilOperation::Invert);
-        front_face_stencil.set_depth_failure_operation(metal::MTLStencilOperation::Keep);
-        front_face_stencil.set_stencil_compare_function(metal::MTLCompareFunction::Always);
-        front_face_stencil.set_read_mask(0x1);
-        front_face_stencil.set_write_mask(0x1);
-        let depth_stencil_state = self.device.new_depth_stencil_state(&stencil_descriptor);
+        color_attachment.set_texture(Some(stencil_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 stencil_attachment = render_pass_descriptor.stencil_attachment().unwrap();
+        // let stencil_texture = self.path_stencils.texture(atlas_id).unwrap();
+        // stencil_attachment.set_texture(Some(stencil_texture));
+        // stencil_attachment.set_load_action(metal::MTLLoadAction::Clear);
+        // stencil_attachment.set_store_action(metal::MTLStoreAction::Store);
+
+        // let stencil_descriptor = metal::DepthStencilDescriptor::new();
+        // let front_face_stencil = stencil_descriptor.front_face_stencil().unwrap();
+        // front_face_stencil.set_depth_stencil_pass_operation(metal::MTLStencilOperation::Invert);
+        // front_face_stencil.set_depth_failure_operation(metal::MTLStencilOperation::Keep);
+        // front_face_stencil.set_stencil_compare_function(metal::MTLCompareFunction::Always);
+        // front_face_stencil.set_read_mask(0x1);
+        // front_face_stencil.set_write_mask(0x1);
+        // let depth_stencil_state = self.device.new_depth_stencil_state(&stencil_descriptor);
 
         let winding_command_encoder =
             command_buffer.new_render_command_encoder(render_pass_descriptor);
-        winding_command_encoder.set_depth_stencil_state(&depth_stencil_state);
         winding_command_encoder.set_render_pipeline_state(&self.path_stencil_pipeline_state);
         winding_command_encoder.set_vertex_buffer(
             shaders::GPUIPathWindingVertexInputIndex_GPUIPathWindingVertexInputIndexVertices as u64,
@@ -264,6 +280,7 @@ impl Renderer {
     fn render_layers(
         &mut self,
         scene: &Scene,
+        path_sprites: Vec<PathSprite>,
         offset: &mut usize,
         drawable_size: Vector2F,
         command_buffer: &metal::CommandBufferRef,
@@ -289,11 +306,20 @@ impl Renderer {
             zfar: 1.0,
         });
 
-        for layer in scene.layers() {
+        for (layer_id, layer) in scene.layers().iter().enumerate() {
             self.clip(scene, layer, drawable_size, command_encoder);
             self.render_shadows(scene, layer, offset, drawable_size, command_encoder);
             self.render_quads(scene, layer, offset, drawable_size, command_encoder);
-            self.render_sprites(scene, layer, offset, drawable_size, command_encoder);
+            // TODO: Pass sprites relevant to this layer in a more efficient manner.
+            self.render_path_sprites(
+                scene,
+                layer,
+                path_sprites.iter().filter(|s| s.layer_id == layer_id),
+                offset,
+                drawable_size,
+                command_encoder,
+            );
+            self.render_glyph_sprites(scene, layer, offset, drawable_size, command_encoder);
         }
 
         command_encoder.end_encoding();
@@ -471,7 +497,7 @@ impl Renderer {
         );
     }
 
-    fn render_sprites(
+    fn render_glyph_sprites(
         &mut self,
         scene: &Scene,
         layer: &Layer,
@@ -502,6 +528,7 @@ impl Renderer {
                         size: sprite.size.to_float2(),
                         atlas_origin: sprite.atlas_origin.to_float2(),
                         color: glyph.color.to_uchar4(),
+                        compute_winding: 0,
                     });
             }
         }
@@ -563,6 +590,87 @@ impl Renderer {
             );
         }
     }
+
+    fn render_path_sprites<'a>(
+        &mut self,
+        scene: &Scene,
+        layer: &Layer,
+        sprites: impl Iterator<Item = &'a PathSprite>,
+        offset: &mut usize,
+        drawable_size: Vector2F,
+        command_encoder: &metal::RenderCommandEncoderRef,
+    ) {
+        let mut sprites = sprites.peekable();
+        if sprites.peek().is_none() {
+            return;
+        }
+
+        let mut sprites_by_atlas = HashMap::new();
+        for sprite in sprites {
+            sprites_by_atlas
+                .entry(sprite.atlas_id)
+                .or_insert_with(Vec::new)
+                .push(sprite.sprite);
+        }
+
+        command_encoder.set_render_pipeline_state(&self.sprite_pipeline_state);
+        command_encoder.set_vertex_buffer(
+            shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexVertices as u64,
+            Some(&self.unit_vertices),
+            0,
+        );
+        command_encoder.set_vertex_bytes(
+            shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexViewportSize as u64,
+            mem::size_of::<shaders::vector_float2>() as u64,
+            [drawable_size.to_float2()].as_ptr() as *const c_void,
+        );
+
+        for (atlas_id, sprites) in sprites_by_atlas {
+            align_offset(offset);
+            let next_offset = *offset + sprites.len() * mem::size_of::<shaders::GPUISprite>();
+            assert!(
+                next_offset <= INSTANCE_BUFFER_SIZE,
+                "instance buffer exhausted"
+            );
+
+            command_encoder.set_vertex_buffer(
+                shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexSprites as u64,
+                Some(&self.instances),
+                *offset as u64,
+            );
+
+            let texture = self.path_stencils.texture(atlas_id).unwrap();
+            command_encoder.set_vertex_bytes(
+                shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexAtlasSize 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,
+            );
+            command_encoder.set_fragment_texture(
+                shaders::GPUISpriteFragmentInputIndex_GPUISpriteFragmentInputIndexAtlas as u64,
+                Some(texture),
+            );
+
+            unsafe {
+                let buffer_contents = (self.instances.contents() as *mut u8)
+                    .offset(*offset as isize)
+                    as *mut shaders::GPUISprite;
+                std::ptr::copy_nonoverlapping(sprites.as_ptr(), buffer_contents, sprites.len());
+            }
+            self.instances.did_modify_range(NSRange {
+                location: *offset as u64,
+                length: (next_offset - *offset) as u64,
+            });
+            *offset = next_offset;
+
+            command_encoder.draw_primitives_instanced(
+                metal::MTLPrimitiveType::Triangle,
+                0,
+                6,
+                sprites.len() as u64,
+            );
+        }
+    }
 }
 
 fn align_offset(offset: &mut usize) {
@@ -625,13 +733,47 @@ fn build_stencil_pipeline_state(
     descriptor.set_label(label);
     descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
     descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
-    descriptor.set_stencil_attachment_pixel_format(pixel_format);
+    let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
+    color_attachment.set_pixel_format(pixel_format);
+    color_attachment.set_blending_enabled(true);
+    color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
+    color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
+    color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
+    color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
+    color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::One);
+    color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
 
     device
         .new_render_pipeline_state(&descriptor)
         .map_err(|message| anyhow!("could not create render pipeline state: {}", message))
 }
 
+// fn build_stencil_pipeline_state(
+//     device: &metal::DeviceRef,
+//     library: &metal::LibraryRef,
+//     label: &str,
+//     vertex_fn_name: &str,
+//     fragment_fn_name: &str,
+//     pixel_format: metal::MTLPixelFormat,
+// ) -> Result<metal::RenderPipelineState> {
+//     let vertex_fn = library
+//         .get_function(vertex_fn_name, None)
+//         .map_err(|message| anyhow!("error locating vertex function: {}", message))?;
+//     let fragment_fn = library
+//         .get_function(fragment_fn_name, None)
+//         .map_err(|message| anyhow!("error locating fragment function: {}", message))?;
+
+//     let descriptor = metal::RenderPipelineDescriptor::new();
+//     descriptor.set_label(label);
+//     descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
+//     descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
+//     descriptor.set_stencil_attachment_pixel_format(pixel_format);
+
+//     device
+//         .new_render_pipeline_state(&descriptor)
+//         .map_err(|message| anyhow!("could not create render pipeline state: {}", message))
+// }
+
 mod shaders {
     #![allow(non_upper_case_globals)]
     #![allow(non_camel_case_types)]

gpui/src/platform/mac/shaders/shaders.metal 🔗

@@ -168,6 +168,7 @@ struct SpriteFragmentInput {
     float4 position [[position]];
     float2 atlas_position;
     float4 color [[flat]];
+    uchar compute_winding [[flat]];
 };
 
 vertex SpriteFragmentInput sprite_vertex(
@@ -188,6 +189,7 @@ vertex SpriteFragmentInput sprite_vertex(
         device_position,
         atlas_position,
         coloru_to_colorf(sprite.color),
+        sprite.compute_winding
     };
 }
 
@@ -197,8 +199,14 @@ fragment float4 sprite_fragment(
 ) {
     constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
     float4 color = input.color;
-    float4 mask = atlas.sample(atlas_sampler, input.atlas_position);
-    color.a *= mask.a;
+    float4 sample = atlas.sample(atlas_sampler, input.atlas_position);
+    float mask;
+    if (input.compute_winding) {
+        mask = fmod(sample.r * 255., 2.);
+    } else {
+        mask = sample.a;
+    }
+    color.a *= mask;
     return color;
 }
 
@@ -223,9 +231,14 @@ vertex PathWindingFragmentInput path_winding_vertex(
 fragment float4 path_winding_fragment(
     PathWindingFragmentInput input [[stage_in]]
 ) {
-    if (input.st_position.x * input.st_position.x - input.st_position.y > 0.) {
-        return float4(0.);
-    } else {
-        return float4(1.);
-    }
+    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) / 255.;
+    return float4(alpha, 0., 0., 1.);
 }

zed/src/editor/buffer_element.rs 🔗

@@ -230,25 +230,29 @@ impl BufferElement {
 
                 let selection = Selection {
                     line_height,
-                    start_y: row_range.start as f32 * line_height - scroll_top,
+                    start_y: bounds.origin_y() + row_range.start as f32 * line_height - scroll_top,
                     lines: row_range
                         .into_iter()
                         .map(|row| {
                             let line_layout = &layout.line_layouts[(row - start_row) as usize];
                             SelectionLine {
                                 start_x: if row == range_start.row() {
-                                    line_layout.x_for_index(range_start.column() as usize)
+                                    bounds.origin_x()
+                                        + line_layout.x_for_index(range_start.column() as usize)
                                         - scroll_left
                                         - descent
                                 } else {
                                     -scroll_left
                                 },
                                 end_x: if row == range_end.row() {
-                                    line_layout.x_for_index(range_end.column() as usize)
+                                    bounds.origin_x()
+                                        + line_layout.x_for_index(range_end.column() as usize)
                                         - scroll_left
                                         - descent
                                 } else {
-                                    line_layout.width + corner_radius * 2.0 - scroll_left - descent
+                                    bounds.origin_x() + line_layout.width + corner_radius * 2.0
+                                        - scroll_left
+                                        - descent
                                 },
                             }
                         })