From 0b12779e62468dd515da824571a52fb3c1f06f5f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 29 Mar 2021 16:28:35 -0700 Subject: [PATCH] WIP: Render path winding numbers to stencil buffer Co-Authored-By: Nathan Sobo --- gpui/src/geometry.rs | 60 +++++----- gpui/src/platform/mac/renderer.rs | 109 ++++++++++++++++- gpui/src/platform/mac/shaders/shaders.h | 10 ++ gpui/src/platform/mac/shaders/shaders.metal | 28 +++++ gpui/src/scene.rs | 28 +++++ zed/src/editor/buffer_element.rs | 126 ++++++++++++-------- 6 files changed, 277 insertions(+), 84 deletions(-) diff --git a/gpui/src/geometry.rs b/gpui/src/geometry.rs index b4ff99106556d4c1144b360446c6d3936e2e032b..ac781c451d7b86113d7f63a9c3bc5d8f82c2f8f6 100644 --- a/gpui/src/geometry.rs +++ b/gpui/src/geometry.rs @@ -1,31 +1,27 @@ pub use pathfinder_geometry::*; +use super::scene::{Path, PathVertex}; use vector::{vec2f, Vector2F}; -pub(crate) struct Vertex { - xy_position: Vector2F, - st_position: Vector2F, -} - -pub struct Path { - vertices: Vec, +pub struct PathBuilder { + vertices: Vec, start: Vector2F, current: Vector2F, - countours_len: usize, + contour_count: usize, } -enum Kind { +enum PathVertexKind { Solid, Quadratic, } -impl Path { - fn new() -> Self { +impl PathBuilder { + pub fn new() -> Self { Self { vertices: Vec::new(), start: vec2f(0., 0.), current: vec2f(0., 0.), - countours_len: 0, + contour_count: 0, } } @@ -33,58 +29,60 @@ impl Path { self.vertices.clear(); self.start = point; self.current = point; - self.countours_len = 0; + self.contour_count = 0; } pub fn line_to(&mut self, point: Vector2F) { - self.countours_len += 1; - if self.countours_len > 1 { - self.push_triangle(self.start, self.current, point, Kind::Solid); + self.contour_count += 1; + if self.contour_count > 1 { + self.push_triangle(self.start, self.current, point, PathVertexKind::Solid); } self.current = point; } pub fn curve_to(&mut self, point: Vector2F, ctrl: Vector2F) { - self.countours_len += 1; - if self.countours_len > 1 { - self.push_triangle(self.start, self.current, point, Kind::Solid); + self.contour_count += 1; + if self.contour_count > 1 { + self.push_triangle(self.start, self.current, point, PathVertexKind::Solid); } - self.push_triangle(self.current, ctrl, point, Kind::Quadratic); + self.push_triangle(self.current, ctrl, point, PathVertexKind::Quadratic); self.current = point; } - pub(crate) fn close(self) -> Vec { - self.vertices + pub fn build(self) -> Path { + Path { + vertices: self.vertices, + } } - fn push_triangle(&mut self, a: Vector2F, b: Vector2F, c: Vector2F, kind: Kind) { + fn push_triangle(&mut self, a: Vector2F, b: Vector2F, c: Vector2F, kind: PathVertexKind) { match kind { - Kind::Solid => { - self.vertices.push(Vertex { + PathVertexKind::Solid => { + self.vertices.push(PathVertex { xy_position: a, st_position: vec2f(0., 1.), }); - self.vertices.push(Vertex { + self.vertices.push(PathVertex { xy_position: b, st_position: vec2f(0., 1.), }); - self.vertices.push(Vertex { + self.vertices.push(PathVertex { xy_position: c, st_position: vec2f(0., 1.), }); } - Kind::Quadratic => { - self.vertices.push(Vertex { + PathVertexKind::Quadratic => { + self.vertices.push(PathVertex { xy_position: a, st_position: vec2f(0., 0.), }); - self.vertices.push(Vertex { + self.vertices.push(PathVertex { xy_position: b, st_position: vec2f(0.5, 0.), }); - self.vertices.push(Vertex { + self.vertices.push(PathVertex { xy_position: c, st_position: vec2f(1., 1.), }); diff --git a/gpui/src/platform/mac/renderer.rs b/gpui/src/platform/mac/renderer.rs index e682d1cdcb63009ff1522c94cedae9ed5a2066c3..7b8b6b70ae1d4103e0b0dcc66b3731e4024878ef 100644 --- a/gpui/src/platform/mac/renderer.rs +++ b/gpui/src/platform/mac/renderer.rs @@ -22,6 +22,7 @@ const INSTANCE_BUFFER_SIZE: usize = 1024 * 1024; // This is an arbitrary decisio struct RenderContext<'a> { drawable_size: Vector2F, command_encoder: &'a metal::RenderCommandEncoderRef, + command_buffer: &'a metal::CommandBufferRef, } pub struct Renderer { @@ -29,9 +30,10 @@ pub struct Renderer { quad_pipeline_state: metal::RenderPipelineState, shadow_pipeline_state: metal::RenderPipelineState, sprite_pipeline_state: metal::RenderPipelineState, + path_winding_pipeline_state: metal::RenderPipelineState, unit_vertices: metal::Buffer, instances: metal::Buffer, - paths_texture: metal::Texture, + path_winding_texture: metal::Texture, } impl Renderer { @@ -64,10 +66,12 @@ impl Renderer { let paths_texture_size = vec2f(2048., 2048.); let descriptor = metal::TextureDescriptor::new(); - descriptor.set_pixel_format(metal::MTLPixelFormat::A8Unorm); + descriptor.set_pixel_format(metal::MTLPixelFormat::Stencil8); descriptor.set_width(paths_texture_size.x() as u64); descriptor.set_height(paths_texture_size.y() as u64); - let paths_texture = device.new_texture(&descriptor); + descriptor.set_usage(metal::MTLTextureUsage::RenderTarget); + descriptor.set_storage_mode(metal::MTLStorageMode::Private); + let path_winding_texture = device.new_texture(&descriptor); let atlas_size: Vector2I = vec2i(1024, 768); Ok(Self { @@ -96,9 +100,17 @@ impl Renderer { "sprite_fragment", pixel_format, )?, + path_winding_pipeline_state: build_stencil_pipeline_state( + &device, + &library, + "path_winding", + "path_winding_vertex", + "path_winding_fragment", + path_winding_texture.pixel_format(), + )?, unit_vertices, instances, - paths_texture, + path_winding_texture, }) } @@ -133,12 +145,15 @@ impl Renderer { let ctx = RenderContext { drawable_size, command_encoder, + command_buffer, }; + let mut offset = 0; for layer in scene.layers() { self.clip(scene, layer, &ctx); self.render_shadows(scene, layer, &mut offset, &ctx); self.render_quads(scene, layer, &mut offset, &ctx); + self.render_paths(scene, layer, &mut offset, &ctx); self.render_sprites(scene, layer, &mut offset, &ctx); } @@ -318,6 +333,66 @@ impl Renderer { offset: &mut usize, ctx: &RenderContext, ) { + for (color, paths) in layer.paths_by_color() { + let winding_render_pass_descriptor = metal::RenderPassDescriptor::new(); + let stencil_attachment = winding_render_pass_descriptor.stencil_attachment().unwrap(); + stencil_attachment.set_texture(Some(&self.path_winding_texture)); + stencil_attachment.set_load_action(metal::MTLLoadAction::Clear); + stencil_attachment.set_store_action(metal::MTLStoreAction::Store); + let winding_command_encoder = ctx + .command_buffer + .new_render_command_encoder(winding_render_pass_descriptor); + + align_offset(offset); + let vertex_count = paths.iter().map(|p| p.vertices.len()).sum::(); + let next_offset = *offset + vertex_count * mem::size_of::(); + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); + + winding_command_encoder.set_render_pipeline_state(&self.path_winding_pipeline_state); + winding_command_encoder.set_vertex_buffer( + shaders::GPUIPathWindingVertexInputIndex_GPUIPathWindingVertexInputIndexVertices + as u64, + Some(&self.instances), + *offset as u64, + ); + winding_command_encoder.set_vertex_bytes( + shaders::GPUIPathWindingVertexInputIndex_GPUIPathWindingVertexInputIndexViewportSize + as u64, + mem::size_of::() as u64, + [ctx.drawable_size.to_float2()].as_ptr() as *const c_void, + ); + + let buffer_contents = unsafe { + (self.instances.contents() as *mut u8).offset(*offset as isize) + as *mut shaders::GPUIPathVertex + }; + + for (ix, vertex) in paths.iter().flat_map(|p| &p.vertices).enumerate() { + let shader_vertex = shaders::GPUIPathVertex { + xy_position: vertex.xy_position.to_float2(), + st_position: vertex.st_position.to_float2(), + }; + unsafe { + *(buffer_contents.offset(ix as isize)) = shader_vertex; + } + } + + self.instances.did_modify_range(NSRange { + location: *offset as u64, + length: (next_offset - *offset) as u64, + }); + *offset = next_offset; + + winding_command_encoder.draw_primitives( + metal::MTLPrimitiveType::Triangle, + 0, + vertex_count as u64, + ); + winding_command_encoder.end_encoding(); + } } fn render_sprites( @@ -455,6 +530,32 @@ fn build_pipeline_state( .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 { + 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)] diff --git a/gpui/src/platform/mac/shaders/shaders.h b/gpui/src/platform/mac/shaders/shaders.h index 5b07a9e8393a439db7c032b11399347b4882fa8c..74883948bbadb9fa0f3eecc544fef4c5e44f9b55 100644 --- a/gpui/src/platform/mac/shaders/shaders.h +++ b/gpui/src/platform/mac/shaders/shaders.h @@ -53,3 +53,13 @@ typedef struct { vector_float2 atlas_origin; vector_uchar4 color; } GPUISprite; + +typedef enum { + GPUIPathWindingVertexInputIndexVertices = 0, + GPUIPathWindingVertexInputIndexViewportSize = 1, +} GPUIPathWindingVertexInputIndex; + +typedef struct { + vector_float2 xy_position; + vector_float2 st_position; +} GPUIPathVertex; diff --git a/gpui/src/platform/mac/shaders/shaders.metal b/gpui/src/platform/mac/shaders/shaders.metal index 18d6093654ff3835e4ac914e66b8ba34d2350c7a..fec7e0910673880abc5e97d0685ab5d01f759ef5 100644 --- a/gpui/src/platform/mac/shaders/shaders.metal +++ b/gpui/src/platform/mac/shaders/shaders.metal @@ -201,3 +201,31 @@ fragment float4 sprite_fragment( color.a *= mask.a; return color; } + +struct PathWindingFragmentInput { + float4 position [[position]]; + float2 st_position; +}; + +vertex PathWindingFragmentInput path_winding_vertex( + uint vertex_id [[vertex_id]], + constant GPUIPathVertex *vertices [[buffer(GPUIPathWindingVertexInputIndexVertices)]], + constant float2 *viewport_size [[buffer(GPUIPathWindingVertexInputIndexViewportSize)]] +) { + GPUIPathVertex v = vertices[vertex_id]; + float4 device_position = to_device_position(v.xy_position, *viewport_size); + return PathWindingFragmentInput { + device_position, + v.st_position, + }; +} + +fragment float4 path_winding_fragment( + PathWindingFragmentInput input [[stage_in]] +) { + if (input.st_position.x * input.st_position.x - input.st_position.y > 0.0) { + return float4(0.0); + } else { + return float4(float3(0.0), 1.0 / 255.0); + } +} \ No newline at end of file diff --git a/gpui/src/scene.rs b/gpui/src/scene.rs index ec396d78204b76aae3e1471678c7f3f60ac9fdec..daeb898a7bd429e144a1717aa3b41de2647e96f2 100644 --- a/gpui/src/scene.rs +++ b/gpui/src/scene.rs @@ -16,6 +16,7 @@ pub struct Layer { quads: Vec, shadows: Vec, glyphs: Vec, + paths: Vec<(ColorU, Vec)>, } #[derive(Default, Debug)] @@ -53,6 +54,17 @@ pub struct Border { pub left: bool, } +#[derive(Debug)] +pub struct Path { + pub vertices: Vec, +} + +#[derive(Debug)] +pub struct PathVertex { + pub xy_position: Vector2F, + pub st_position: Vector2F, +} + impl Scene { pub fn new(scale_factor: f32) -> Self { Scene { @@ -93,6 +105,10 @@ impl Scene { self.active_layer().push_glyph(glyph) } + pub fn push_path(&mut self, color: ColorU, path: Path) { + self.active_layer().push_path(color, path); + } + fn active_layer(&mut self) -> &mut Layer { &mut self.layers[*self.active_layer_stack.last().unwrap()] } @@ -105,6 +121,7 @@ impl Layer { quads: Vec::new(), shadows: Vec::new(), glyphs: Vec::new(), + paths: Vec::new(), } } @@ -135,6 +152,17 @@ impl Layer { pub fn glyphs(&self) -> &[Glyph] { self.glyphs.as_slice() } + + fn push_path(&mut self, color: ColorU, path: Path) { + match self.paths.binary_search_by_key(&color, |(c, path)| *c) { + Err(i) => self.paths.insert(i, (color, vec![path])), + Ok(i) => self.paths[i].1.push(path), + } + } + + pub fn paths_by_color(&self) -> &[(ColorU, Vec)] { + self.paths.as_slice() + } } impl Border { diff --git a/zed/src/editor/buffer_element.rs b/zed/src/editor/buffer_element.rs index fe1b5e71adf89f699ed49bd23c04b1a035848bf5..53deb39568843615866ede186f82b83f6016b384 100644 --- a/zed/src/editor/buffer_element.rs +++ b/zed/src/editor/buffer_element.rs @@ -4,6 +4,7 @@ use gpui::{ geometry::{ rect::RectF, vector::{vec2f, Vector2F}, + PathBuilder, }, text_layout::{self, TextLayoutCache}, AfterLayoutContext, AppContext, Border, Element, Event, EventContext, FontCache, LayoutContext, @@ -211,51 +212,51 @@ impl BufferElement { }); // Draw selections - // let corner_radius = 2.5; + let corner_radius = 2.5; let mut cursors = SmallVec::<[Cursor; 32]>::new(); for selection in view.selections_in_range( DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0), ctx.app, ) { - // if selection.start != selection.end { - // let range_start = cmp::min(selection.start, selection.end); - // let range_end = cmp::max(selection.start, selection.end); - // let row_range = if range_end.column() == 0 { - // cmp::max(range_start.row(), start_row)..cmp::min(range_end.row(), end_row) - // } else { - // cmp::max(range_start.row(), start_row)..cmp::min(range_end.row() + 1, end_row) - // }; - - // let selection = Selection { - // line_height, - // start_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) - // - scroll_left - // - descent - // } else { - // -scroll_left - // }, - // end_x: if row == range_end.row() { - // line_layout.x_for_index(range_end.column() as usize) - // - scroll_left - // - descent - // } else { - // line_layout.width + corner_radius * 2.0 - scroll_left - descent - // }, - // } - // }) - // .collect(), - // }; - - // selection.paint(scene); - // } + if selection.start != selection.end { + let range_start = cmp::min(selection.start, selection.end); + let range_end = cmp::max(selection.start, selection.end); + let row_range = if range_end.column() == 0 { + cmp::max(range_start.row(), start_row)..cmp::min(range_end.row(), end_row) + } else { + cmp::max(range_start.row(), start_row)..cmp::min(range_end.row() + 1, end_row) + }; + + let selection = Selection { + line_height, + start_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) + - scroll_left + - descent + } else { + -scroll_left + }, + end_x: if row == range_end.row() { + line_layout.x_for_index(range_end.column() as usize) + - scroll_left + - descent + } else { + line_layout.width + corner_radius * 2.0 - scroll_left - descent + }, + } + }) + .collect(), + }; + + selection.paint(ctx.scene); + } if view.cursors_visible() { let cursor_position = selection.end; @@ -597,20 +598,47 @@ impl Selection { } fn paint_lines(&self, start_y: f32, lines: &[SelectionLine], scene: &mut Scene) { - // use Direction::*; + if lines.is_empty() { + return; + } - // if lines.is_empty() { - // return; - // } + let mut path = PathBuilder::new(); + let corner_radius = 0.08 * self.line_height; - // let mut path = Path2D::new(); - // let corner_radius = 0.08 * self.line_height; + let first_line = lines.first().unwrap(); + path.reset(vec2f(first_line.end_x - corner_radius, start_y)); + path.curve_to( + vec2f(first_line.end_x, start_y + corner_radius), + vec2f(first_line.end_x, start_y), + ); + path.line_to(vec2f( + first_line.end_x, + start_y + self.line_height - corner_radius, + )); + path.curve_to( + vec2f(first_line.end_x - corner_radius, start_y + self.line_height), + vec2f(first_line.end_x, start_y + self.line_height), + ); + path.line_to(vec2f( + first_line.start_x + corner_radius, + start_y + self.line_height, + )); + path.curve_to( + vec2f( + first_line.start_x, + start_y + self.line_height - corner_radius, + ), + vec2f(first_line.start_x, start_y + self.line_height), + ); + path.line_to(vec2f(first_line.start_x, start_y + corner_radius)); + path.curve_to( + vec2f(first_line.start_x + corner_radius, start_y), + vec2f(first_line.start_x, start_y), + ); + path.line_to(vec2f(first_line.end_x - corner_radius, start_y)); - // let first_line = lines.first().unwrap(); - // let last_line = lines.last().unwrap(); + scene.push_path(ColorU::from_u32(0xff0000ff), path.build()); - // let corner = vec2f(first_line.end_x, start_y); - // path.move_to(corner - vec2f(corner_radius, 0.0)); // rounded_corner(&mut path, corner, corner_radius, Right, Down); // let mut iter = lines.iter().enumerate().peekable();