From a4afb725351cfae9f596d2a080edc143992f7e48 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 10 Oct 2023 13:01:35 +0200 Subject: [PATCH] Checkpoint: beziers --- crates/gpui3/build.rs | 3 + crates/gpui3/src/platform/mac/metal_atlas.rs | 22 +- .../gpui3/src/platform/mac/metal_renderer.rs | 283 +++++++++++++----- crates/gpui3/src/platform/mac/shaders.metal | 88 ++++++ crates/gpui3/src/scene.rs | 91 ++++-- crates/gpui3/src/window.rs | 7 +- 6 files changed, 397 insertions(+), 97 deletions(-) diff --git a/crates/gpui3/build.rs b/crates/gpui3/build.rs index 0131d9f148..a49123cf86 100644 --- a/crates/gpui3/build.rs +++ b/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; diff --git a/crates/gpui3/src/platform/mac/metal_atlas.rs b/crates/gpui3/src/platform/mac/metal_atlas.rs index 6e7baf0763..4c9938e0ec 100644 --- a/crates/gpui3/src/platform/mac/metal_atlas.rs +++ b/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 { diff --git a/crates/gpui3/src/platform/mac/metal_renderer.rs b/crates/gpui3/src/platform/mac/metal_renderer.rs index 187f1b82ce..18d8410738 100644 --- a/crates/gpui3/src/platform/mac/metal_renderer.rs +++ b/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], - _offset: &mut usize, - _command_buffer: &metal::CommandBufferRef, + offset: &mut usize, + command_buffer: &metal::CommandBufferRef, ) -> HashMap { 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::>(); - // assert!( - // next_offset <= INSTANCE_BUFFER_SIZE, - // "instance buffer exhausted" - // ); + for (texture_id, vertices) in vertices_by_texture_id { + align_offset(offset); + let next_offset = *offset + vertices.len() * mem::size_of::>(); + 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 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::() as u64, - // [vec2i(texture.width() as i32, texture.height() as i32).to_float2()].as_ptr() - // as *const c_void, - // ); + 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 as *const _, + ); - // let buffer_contents = unsafe { - // (self.instances.contents() as *mut u8).add(*offset) as *mut shaders::GPUIPathVertex - // }; + let vertices_bytes_len = mem::size_of::>() * 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, + ); + } - // 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; + 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], + tiles_by_path_id: &HashMap, + offset: &mut usize, + viewport_size: Size, + 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 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 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::() * 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, + pub color: Hsla, + pub tile: AtlasTile, +} diff --git a/crates/gpui3/src/platform/mac/shaders.metal b/crates/gpui3/src/platform/mac/shaders.metal index 33670c02bb..444842d9b2 100644 --- a/crates/gpui3/src/platform/mac/shaders.metal +++ b/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 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; diff --git a/crates/gpui3/src/scene.rs b/crates/gpui3/src/scene.rs index 19948b9ffa..5406b024d6 100644 --- a/crates/gpui3/src/scene.rs +++ b/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; +pub(crate) type PointF = Point; +#[allow(non_camel_case_types, unused)] +pub(crate) type PathVertex_ScaledPixels = PathVertex; 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) { + 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 { + 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 { + 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 { pub(crate) id: PathId, order: u32, pub(crate) bounds: Bounds

, + pub(crate) content_mask: ContentMask

, pub(crate) vertices: Vec>, - start: Option>, + pub(crate) color: Hsla, + start: Point

, current: Point

, + contour_count: usize, } impl Path { - pub fn new() -> Self { + pub fn new(start: Point) -> 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 { 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 { .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) { - 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, ctrl: Point) { - 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 { xy: (Point, Point, Point), st: (Point, Point, Point), ) { - if self.vertices.is_empty() { - self.bounds = Bounds { - origin: xy.0, - size: Default::default(), - }; - } self.bounds = self .bounds .union(&Bounds { diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 8b27e17144..af4fe6cc1f 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -363,12 +363,11 @@ impl<'a, 'w> WindowContext<'a, 'w> { ); } - pub fn paint_path(&mut self, mut path: Path) { + pub fn paint_path(&mut self, mut path: Path, color: impl Into) { 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