Detailed changes
@@ -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<Vertex>,
+pub struct PathBuilder {
+ vertices: Vec<PathVertex>,
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<Vertex> {
- 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.),
});
@@ -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::<usize>();
+ let next_offset = *offset + vertex_count * mem::size_of::<shaders::GPUIPathVertex>();
+ 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::<shaders::vector_float2>() 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<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)]
@@ -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;
@@ -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);
+ }
+}
@@ -16,6 +16,7 @@ pub struct Layer {
quads: Vec<Quad>,
shadows: Vec<Shadow>,
glyphs: Vec<Glyph>,
+ paths: Vec<(ColorU, Vec<Path>)>,
}
#[derive(Default, Debug)]
@@ -53,6 +54,17 @@ pub struct Border {
pub left: bool,
}
+#[derive(Debug)]
+pub struct Path {
+ pub vertices: Vec<PathVertex>,
+}
+
+#[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<Path>)] {
+ self.paths.as_slice()
+ }
}
impl Border {
@@ -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();