diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index aed4397440..7ab44a73f5 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -128,6 +128,7 @@ mod macos { "AtlasTile".into(), "PathRasterizationInputIndex".into(), "PathVertex_ScaledPixels".into(), + "PathRasterizationVertex".into(), "ShadowInputIndex".into(), "Shadow".into(), "QuadInputIndex".into(), diff --git a/crates/gpui/examples/painting.rs b/crates/gpui/examples/painting.rs index ff4b64cbda..668aed2377 100644 --- a/crates/gpui/examples/painting.rs +++ b/crates/gpui/examples/painting.rs @@ -1,11 +1,12 @@ use gpui::{ Application, Background, Bounds, ColorSpace, Context, MouseDownEvent, Path, PathBuilder, PathStyle, Pixels, Point, Render, SharedString, StrokeOptions, Window, WindowOptions, canvas, - div, linear_color_stop, linear_gradient, point, prelude::*, px, rgb, size, + div, linear_color_stop, linear_gradient, point, prelude::*, px, quad, rgb, size, }; struct PaintingViewer { default_lines: Vec<(Path, Background)>, + background_quads: Vec<(Bounds, Background)>, lines: Vec>>, start: Point, dashed: bool, @@ -16,12 +17,148 @@ impl PaintingViewer { fn new(_window: &mut Window, _cx: &mut Context) -> Self { let mut lines = vec![]; + // Black squares beneath transparent paths. + let background_quads = vec![ + ( + Bounds { + origin: point(px(70.), px(70.)), + size: size(px(40.), px(40.)), + }, + gpui::black().into(), + ), + ( + Bounds { + origin: point(px(170.), px(70.)), + size: size(px(40.), px(40.)), + }, + gpui::black().into(), + ), + ( + Bounds { + origin: point(px(270.), px(70.)), + size: size(px(40.), px(40.)), + }, + gpui::black().into(), + ), + ( + Bounds { + origin: point(px(370.), px(70.)), + size: size(px(40.), px(40.)), + }, + gpui::black().into(), + ), + ( + Bounds { + origin: point(px(450.), px(50.)), + size: size(px(80.), px(80.)), + }, + gpui::black().into(), + ), + ]; + + // 50% opaque red path that extends across black quad. + let mut builder = PathBuilder::fill(); + builder.move_to(point(px(50.), px(50.))); + builder.line_to(point(px(130.), px(50.))); + builder.line_to(point(px(130.), px(130.))); + builder.line_to(point(px(50.), px(130.))); + builder.close(); + let path = builder.build().unwrap(); + let mut red = rgb(0xFF0000); + red.a = 0.5; + lines.push((path, red.into())); + + // 50% opaque blue path that extends across black quad. + let mut builder = PathBuilder::fill(); + builder.move_to(point(px(150.), px(50.))); + builder.line_to(point(px(230.), px(50.))); + builder.line_to(point(px(230.), px(130.))); + builder.line_to(point(px(150.), px(130.))); + builder.close(); + let path = builder.build().unwrap(); + let mut blue = rgb(0x0000FF); + blue.a = 0.5; + lines.push((path, blue.into())); + + // 50% opaque green path that extends across black quad. + let mut builder = PathBuilder::fill(); + builder.move_to(point(px(250.), px(50.))); + builder.line_to(point(px(330.), px(50.))); + builder.line_to(point(px(330.), px(130.))); + builder.line_to(point(px(250.), px(130.))); + builder.close(); + let path = builder.build().unwrap(); + let mut green = rgb(0x00FF00); + green.a = 0.5; + lines.push((path, green.into())); + + // 50% opaque black path that extends across black quad. + let mut builder = PathBuilder::fill(); + builder.move_to(point(px(350.), px(50.))); + builder.line_to(point(px(430.), px(50.))); + builder.line_to(point(px(430.), px(130.))); + builder.line_to(point(px(350.), px(130.))); + builder.close(); + let path = builder.build().unwrap(); + let mut black = rgb(0x000000); + black.a = 0.5; + lines.push((path, black.into())); + + // Two 50% opaque red circles overlapping - center should be darker red + let mut builder = PathBuilder::fill(); + let center = point(px(530.), px(85.)); + let radius = px(30.); + builder.move_to(point(center.x + radius, center.y)); + builder.arc_to( + point(radius, radius), + px(0.), + false, + false, + point(center.x - radius, center.y), + ); + builder.arc_to( + point(radius, radius), + px(0.), + false, + false, + point(center.x + radius, center.y), + ); + builder.close(); + let path = builder.build().unwrap(); + let mut red1 = rgb(0xFF0000); + red1.a = 0.5; + lines.push((path, red1.into())); + + let mut builder = PathBuilder::fill(); + let center = point(px(570.), px(85.)); + let radius = px(30.); + builder.move_to(point(center.x + radius, center.y)); + builder.arc_to( + point(radius, radius), + px(0.), + false, + false, + point(center.x - radius, center.y), + ); + builder.arc_to( + point(radius, radius), + px(0.), + false, + false, + point(center.x + radius, center.y), + ); + builder.close(); + let path = builder.build().unwrap(); + let mut red2 = rgb(0xFF0000); + red2.a = 0.5; + lines.push((path, red2.into())); + // draw a Rust logo let mut builder = lyon::path::Path::svg_builder(); lyon::extra::rust_logo::build_logo_path(&mut builder); // move down the Path let mut builder: PathBuilder = builder.into(); - builder.translate(point(px(10.), px(100.))); + builder.translate(point(px(10.), px(200.))); builder.scale(0.9); let path = builder.build().unwrap(); lines.push((path, gpui::black().into())); @@ -30,10 +167,10 @@ impl PaintingViewer { let mut builder = PathBuilder::fill(); builder.add_polygon( &[ - point(px(150.), px(200.)), - point(px(200.), px(125.)), - point(px(200.), px(175.)), - point(px(250.), px(100.)), + point(px(150.), px(300.)), + point(px(200.), px(225.)), + point(px(200.), px(275.)), + point(px(250.), px(200.)), ], false, ); @@ -42,17 +179,17 @@ impl PaintingViewer { // draw a ⭐ let mut builder = PathBuilder::fill(); - builder.move_to(point(px(350.), px(100.))); - builder.line_to(point(px(370.), px(160.))); - builder.line_to(point(px(430.), px(160.))); - builder.line_to(point(px(380.), px(200.))); - builder.line_to(point(px(400.), px(260.))); - builder.line_to(point(px(350.), px(220.))); - builder.line_to(point(px(300.), px(260.))); - builder.line_to(point(px(320.), px(200.))); - builder.line_to(point(px(270.), px(160.))); - builder.line_to(point(px(330.), px(160.))); - builder.line_to(point(px(350.), px(100.))); + builder.move_to(point(px(350.), px(200.))); + builder.line_to(point(px(370.), px(260.))); + builder.line_to(point(px(430.), px(260.))); + builder.line_to(point(px(380.), px(300.))); + builder.line_to(point(px(400.), px(360.))); + builder.line_to(point(px(350.), px(320.))); + builder.line_to(point(px(300.), px(360.))); + builder.line_to(point(px(320.), px(300.))); + builder.line_to(point(px(270.), px(260.))); + builder.line_to(point(px(330.), px(260.))); + builder.line_to(point(px(350.), px(200.))); let path = builder.build().unwrap(); lines.push(( path, @@ -66,7 +203,7 @@ impl PaintingViewer { // draw linear gradient let square_bounds = Bounds { - origin: point(px(450.), px(100.)), + origin: point(px(450.), px(200.)), size: size(px(200.), px(80.)), }; let height = square_bounds.size.height; @@ -96,31 +233,31 @@ impl PaintingViewer { // draw a pie chart let center = point(px(96.), px(96.)); - let pie_center = point(px(775.), px(155.)); + let pie_center = point(px(775.), px(255.)); let segments = [ ( - point(px(871.), px(155.)), - point(px(747.), px(63.)), + point(px(871.), px(255.)), + point(px(747.), px(163.)), rgb(0x1374e9), ), ( - point(px(747.), px(63.)), - point(px(679.), px(163.)), + point(px(747.), px(163.)), + point(px(679.), px(263.)), rgb(0xe13527), ), ( - point(px(679.), px(163.)), - point(px(754.), px(249.)), + point(px(679.), px(263.)), + point(px(754.), px(349.)), rgb(0x0751ce), ), ( - point(px(754.), px(249.)), - point(px(854.), px(210.)), + point(px(754.), px(349.)), + point(px(854.), px(310.)), rgb(0x209742), ), ( - point(px(854.), px(210.)), - point(px(871.), px(155.)), + point(px(854.), px(310.)), + point(px(871.), px(255.)), rgb(0xfbc10a), ), ]; @@ -140,11 +277,11 @@ impl PaintingViewer { .with_line_width(1.) .with_line_join(lyon::path::LineJoin::Bevel); let mut builder = PathBuilder::stroke(px(1.)).with_style(PathStyle::Stroke(options)); - builder.move_to(point(px(40.), px(320.))); + builder.move_to(point(px(40.), px(420.))); for i in 1..50 { builder.line_to(point( px(40.0 + i as f32 * 10.0), - px(320.0 + (i as f32 * 10.0).sin() * 40.0), + px(420.0 + (i as f32 * 10.0).sin() * 40.0), )); } let path = builder.build().unwrap(); @@ -152,6 +289,7 @@ impl PaintingViewer { Self { default_lines: lines.clone(), + background_quads, lines: vec![], start: point(px(0.), px(0.)), dashed: false, @@ -185,6 +323,7 @@ fn button( impl Render for PaintingViewer { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { let default_lines = self.default_lines.clone(); + let background_quads = self.background_quads.clone(); let lines = self.lines.clone(); let dashed = self.dashed; @@ -221,6 +360,19 @@ impl Render for PaintingViewer { canvas( move |_, _, _| {}, move |_, _, window, _| { + // First draw background quads + for (bounds, color) in background_quads.iter() { + window.paint_quad(quad( + *bounds, + px(0.), + *color, + px(0.), + gpui::transparent_black(), + Default::default(), + )); + } + + // Then draw the default paths on top for (path, color) in default_lines { window.paint_path(path, color); } @@ -303,6 +455,10 @@ fn main() { |window, cx| cx.new(|cx| PaintingViewer::new(window, cx)), ) .unwrap(); + cx.on_window_closed(|cx| { + cx.quit(); + }) + .detach(); cx.activate(true); }); } diff --git a/crates/gpui/examples/paths_bench.rs b/crates/gpui/examples/paths_bench.rs new file mode 100644 index 0000000000..a801889ae8 --- /dev/null +++ b/crates/gpui/examples/paths_bench.rs @@ -0,0 +1,92 @@ +use gpui::{ + Application, Background, Bounds, ColorSpace, Context, Path, PathBuilder, Pixels, Render, + TitlebarOptions, Window, WindowBounds, WindowOptions, canvas, div, linear_color_stop, + linear_gradient, point, prelude::*, px, rgb, size, +}; + +const DEFAULT_WINDOW_WIDTH: Pixels = px(1024.0); +const DEFAULT_WINDOW_HEIGHT: Pixels = px(768.0); + +struct PaintingViewer { + default_lines: Vec<(Path, Background)>, + _painting: bool, +} + +impl PaintingViewer { + fn new(_window: &mut Window, _cx: &mut Context) -> Self { + let mut lines = vec![]; + + // draw a lightening bolt ⚡ + for _ in 0..2000 { + // draw a ⭐ + let mut builder = PathBuilder::fill(); + builder.move_to(point(px(350.), px(100.))); + builder.line_to(point(px(370.), px(160.))); + builder.line_to(point(px(430.), px(160.))); + builder.line_to(point(px(380.), px(200.))); + builder.line_to(point(px(400.), px(260.))); + builder.line_to(point(px(350.), px(220.))); + builder.line_to(point(px(300.), px(260.))); + builder.line_to(point(px(320.), px(200.))); + builder.line_to(point(px(270.), px(160.))); + builder.line_to(point(px(330.), px(160.))); + builder.line_to(point(px(350.), px(100.))); + let path = builder.build().unwrap(); + lines.push(( + path, + linear_gradient( + 180., + linear_color_stop(rgb(0xFACC15), 0.7), + linear_color_stop(rgb(0xD56D0C), 1.), + ) + .color_space(ColorSpace::Oklab), + )); + } + + Self { + default_lines: lines, + _painting: false, + } + } +} + +impl Render for PaintingViewer { + fn render(&mut self, window: &mut Window, _: &mut Context) -> impl IntoElement { + window.request_animation_frame(); + let lines = self.default_lines.clone(); + div().size_full().child( + canvas( + move |_, _, _| {}, + move |_, _, window, _| { + for (path, color) in lines { + window.paint_path(path, color); + } + }, + ) + .size_full(), + ) + } +} + +fn main() { + Application::new().run(|cx| { + cx.open_window( + WindowOptions { + titlebar: Some(TitlebarOptions { + title: Some("Vulkan".into()), + ..Default::default() + }), + focus: true, + window_bounds: Some(WindowBounds::Windowed(Bounds::centered( + None, + size(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT), + cx, + ))), + ..Default::default() + }, + |window, cx| cx.new(|cx| PaintingViewer::new(window, cx)), + ) + .unwrap(); + cx.activate(true); + }); +} diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 6f227f1d07..1e72d23868 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -809,7 +809,6 @@ pub(crate) struct AtlasTextureId { pub(crate) enum AtlasTextureKind { Monochrome = 0, Polychrome = 1, - Path = 2, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] diff --git a/crates/gpui/src/platform/blade/blade_atlas.rs b/crates/gpui/src/platform/blade/blade_atlas.rs index 78ba52056a..74500ebf83 100644 --- a/crates/gpui/src/platform/blade/blade_atlas.rs +++ b/crates/gpui/src/platform/blade/blade_atlas.rs @@ -10,8 +10,6 @@ use etagere::BucketedAtlasAllocator; use parking_lot::Mutex; use std::{borrow::Cow, ops, sync::Arc}; -pub(crate) const PATH_TEXTURE_FORMAT: gpu::TextureFormat = gpu::TextureFormat::R16Float; - pub(crate) struct BladeAtlas(Mutex); struct PendingUpload { @@ -27,7 +25,6 @@ struct BladeAtlasState { tiles_by_key: FxHashMap, initializations: Vec, uploads: Vec, - path_sample_count: u32, } #[cfg(gles)] @@ -41,13 +38,11 @@ impl BladeAtlasState { } pub struct BladeTextureInfo { - pub size: gpu::Extent, pub raw_view: gpu::TextureView, - pub msaa_view: Option, } impl BladeAtlas { - pub(crate) fn new(gpu: &Arc, path_sample_count: u32) -> Self { + pub(crate) fn new(gpu: &Arc) -> Self { BladeAtlas(Mutex::new(BladeAtlasState { gpu: Arc::clone(gpu), upload_belt: BufferBelt::new(BufferBeltDescriptor { @@ -59,7 +54,6 @@ impl BladeAtlas { tiles_by_key: Default::default(), initializations: Vec::new(), uploads: Vec::new(), - path_sample_count, })) } @@ -67,27 +61,6 @@ impl BladeAtlas { self.0.lock().destroy(); } - pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) { - let mut lock = self.0.lock(); - let textures = &mut lock.storage[texture_kind]; - for texture in textures.iter_mut() { - texture.clear(); - } - } - - /// Allocate a rectangle and make it available for rendering immediately (without waiting for `before_frame`) - pub fn allocate_for_rendering( - &self, - size: Size, - texture_kind: AtlasTextureKind, - gpu_encoder: &mut gpu::CommandEncoder, - ) -> AtlasTile { - let mut lock = self.0.lock(); - let tile = lock.allocate(size, texture_kind); - lock.flush_initializations(gpu_encoder); - tile - } - pub fn before_frame(&self, gpu_encoder: &mut gpu::CommandEncoder) { let mut lock = self.0.lock(); lock.flush(gpu_encoder); @@ -101,15 +74,8 @@ impl BladeAtlas { pub fn get_texture_info(&self, id: AtlasTextureId) -> BladeTextureInfo { let lock = self.0.lock(); let texture = &lock.storage[id]; - let size = texture.allocator.size(); BladeTextureInfo { - size: gpu::Extent { - width: size.width as u32, - height: size.height as u32, - depth: 1, - }, raw_view: texture.raw_view, - msaa_view: texture.msaa_view, } } } @@ -200,48 +166,8 @@ impl BladeAtlasState { format = gpu::TextureFormat::Bgra8UnormSrgb; usage = gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE; } - AtlasTextureKind::Path => { - format = PATH_TEXTURE_FORMAT; - usage = gpu::TextureUsage::COPY - | gpu::TextureUsage::RESOURCE - | gpu::TextureUsage::TARGET; - } } - // We currently only enable MSAA for path textures. - let (msaa, msaa_view) = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path { - let msaa = self.gpu.create_texture(gpu::TextureDesc { - name: "msaa path texture", - format, - size: gpu::Extent { - width: size.width.into(), - height: size.height.into(), - depth: 1, - }, - array_layer_count: 1, - mip_level_count: 1, - sample_count: self.path_sample_count, - dimension: gpu::TextureDimension::D2, - usage: gpu::TextureUsage::TARGET, - external: None, - }); - - ( - Some(msaa), - Some(self.gpu.create_texture_view( - msaa, - gpu::TextureViewDesc { - name: "msaa texture view", - format, - dimension: gpu::ViewDimension::D2, - subresources: &Default::default(), - }, - )), - ) - } else { - (None, None) - }; - let raw = self.gpu.create_texture(gpu::TextureDesc { name: "atlas", format, @@ -279,8 +205,6 @@ impl BladeAtlasState { format, raw, raw_view, - msaa, - msaa_view, live_atlas_keys: 0, }; @@ -340,7 +264,6 @@ impl BladeAtlasState { struct BladeAtlasStorage { monochrome_textures: AtlasTextureList, polychrome_textures: AtlasTextureList, - path_textures: AtlasTextureList, } impl ops::Index for BladeAtlasStorage { @@ -349,7 +272,6 @@ impl ops::Index for BladeAtlasStorage { match kind { crate::AtlasTextureKind::Monochrome => &self.monochrome_textures, crate::AtlasTextureKind::Polychrome => &self.polychrome_textures, - crate::AtlasTextureKind::Path => &self.path_textures, } } } @@ -359,7 +281,6 @@ impl ops::IndexMut for BladeAtlasStorage { match kind { crate::AtlasTextureKind::Monochrome => &mut self.monochrome_textures, crate::AtlasTextureKind::Polychrome => &mut self.polychrome_textures, - crate::AtlasTextureKind::Path => &mut self.path_textures, } } } @@ -370,7 +291,6 @@ impl ops::Index for BladeAtlasStorage { let textures = match id.kind { crate::AtlasTextureKind::Monochrome => &self.monochrome_textures, crate::AtlasTextureKind::Polychrome => &self.polychrome_textures, - crate::AtlasTextureKind::Path => &self.path_textures, }; textures[id.index as usize].as_ref().unwrap() } @@ -384,9 +304,6 @@ impl BladeAtlasStorage { for mut texture in self.polychrome_textures.drain().flatten() { texture.destroy(gpu); } - for mut texture in self.path_textures.drain().flatten() { - texture.destroy(gpu); - } } } @@ -395,17 +312,11 @@ struct BladeAtlasTexture { allocator: BucketedAtlasAllocator, raw: gpu::Texture, raw_view: gpu::TextureView, - msaa: Option, - msaa_view: Option, format: gpu::TextureFormat, live_atlas_keys: u32, } impl BladeAtlasTexture { - fn clear(&mut self) { - self.allocator.clear(); - } - fn allocate(&mut self, size: Size) -> Option { let allocation = self.allocator.allocate(size.into())?; let tile = AtlasTile { @@ -424,12 +335,6 @@ impl BladeAtlasTexture { fn destroy(&mut self, gpu: &gpu::Context) { gpu.destroy_texture(self.raw); gpu.destroy_texture_view(self.raw_view); - if let Some(msaa) = self.msaa { - gpu.destroy_texture(msaa); - } - if let Some(msaa_view) = self.msaa_view { - gpu.destroy_texture_view(msaa_view); - } } fn bytes_per_pixel(&self) -> u8 { diff --git a/crates/gpui/src/platform/blade/blade_renderer.rs b/crates/gpui/src/platform/blade/blade_renderer.rs index cac47434ae..2e18d2be22 100644 --- a/crates/gpui/src/platform/blade/blade_renderer.rs +++ b/crates/gpui/src/platform/blade/blade_renderer.rs @@ -1,24 +1,19 @@ // Doing `if let` gives you nice scoping with passes/encoders #![allow(irrefutable_let_patterns)] -use super::{BladeAtlas, BladeContext, PATH_TEXTURE_FORMAT}; +use super::{BladeAtlas, BladeContext}; use crate::{ - AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, GpuSpecs, - MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, Quad, - ScaledPixels, Scene, Shadow, Size, Underline, + Background, Bounds, DevicePixels, GpuSpecs, MonochromeSprite, Path, Point, PolychromeSprite, + PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Underline, }; use blade_graphics as gpu; use blade_util::{BufferBelt, BufferBeltDescriptor}; use bytemuck::{Pod, Zeroable}; -use collections::HashMap; #[cfg(target_os = "macos")] use media::core_video::CVMetalTextureCache; -use std::{mem, sync::Arc}; +use std::sync::Arc; const MAX_FRAME_TIME_MS: u32 = 10000; -// Use 4x MSAA, all devices support it. -// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount -const DEFAULT_PATH_SAMPLE_COUNT: u32 = 4; #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] @@ -114,8 +109,15 @@ struct ShaderSurfacesData { #[repr(C)] struct PathSprite { bounds: Bounds, +} + +#[derive(Clone, Debug)] +#[repr(C)] +struct PathRasterizationVertex { + xy_position: Point, + st_position: Point, color: Background, - tile: AtlasTile, + bounds: Bounds, } struct BladePipelines { @@ -144,10 +146,7 @@ impl BladePipelines { shader.check_struct_size::(); shader.check_struct_size::(); shader.check_struct_size::(); - assert_eq!( - mem::size_of::>(), - shader.get_struct_size("PathVertex") as usize, - ); + shader.check_struct_size::(); shader.check_struct_size::(); shader.check_struct_size::(); shader.check_struct_size::(); @@ -205,9 +204,16 @@ impl BladePipelines { }, depth_stencil: None, fragment: Some(shader.at("fs_path_rasterization")), + // The original implementation was using ADDITIVE blende mode, + // I don't know why + // color_targets: &[gpu::ColorTargetState { + // format: PATH_TEXTURE_FORMAT, + // blend: Some(gpu::BlendState::ADDITIVE), + // write_mask: gpu::ColorWrites::default(), + // }], color_targets: &[gpu::ColorTargetState { - format: PATH_TEXTURE_FORMAT, - blend: Some(gpu::BlendState::ADDITIVE), + format: surface_info.format, + blend: Some(gpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), write_mask: gpu::ColorWrites::default(), }], multisample_state: gpu::MultisampleState { @@ -226,7 +232,14 @@ impl BladePipelines { }, depth_stencil: None, fragment: Some(shader.at("fs_path")), - color_targets, + color_targets: &[gpu::ColorTargetState { + format: surface_info.format, + blend: Some(gpu::BlendState { + color: gpu::BlendComponent::OVER, + alpha: gpu::BlendComponent::ADDITIVE, + }), + write_mask: gpu::ColorWrites::default(), + }], multisample_state: gpu::MultisampleState::default(), }), underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc { @@ -317,12 +330,15 @@ pub struct BladeRenderer { last_sync_point: Option, pipelines: BladePipelines, instance_belt: BufferBelt, - path_tiles: HashMap, atlas: Arc, atlas_sampler: gpu::Sampler, #[cfg(target_os = "macos")] core_video_texture_cache: CVMetalTextureCache, path_sample_count: u32, + path_intermediate_texture: gpu::Texture, + path_intermediate_texture_view: gpu::TextureView, + path_intermediate_msaa_texture: Option, + path_intermediate_msaa_texture_view: Option, } impl BladeRenderer { @@ -352,21 +368,43 @@ impl BladeRenderer { let path_sample_count = std::env::var("ZED_PATH_SAMPLE_COUNT") .ok() .and_then(|v| v.parse().ok()) - .unwrap_or(DEFAULT_PATH_SAMPLE_COUNT); + .or_else(|| { + [4, 2, 1] + .into_iter() + .find(|count| context.gpu.supports_texture_sample_count(*count)) + }) + .unwrap_or(1); let pipelines = BladePipelines::new(&context.gpu, surface.info(), path_sample_count); let instance_belt = BufferBelt::new(BufferBeltDescriptor { memory: gpu::Memory::Shared, min_chunk_size: 0x1000, alignment: 0x40, // Vulkan `minStorageBufferOffsetAlignment` on Intel Xe }); - let atlas = Arc::new(BladeAtlas::new(&context.gpu, path_sample_count)); + let atlas = Arc::new(BladeAtlas::new(&context.gpu)); let atlas_sampler = context.gpu.create_sampler(gpu::SamplerDesc { - name: "atlas", + name: "path rasterization sampler", mag_filter: gpu::FilterMode::Linear, min_filter: gpu::FilterMode::Linear, ..Default::default() }); + let (path_intermediate_texture, path_intermediate_texture_view) = + create_path_intermediate_texture( + &context.gpu, + surface.info().format, + config.size.width, + config.size.height, + ); + let (path_intermediate_msaa_texture, path_intermediate_msaa_texture_view) = + create_msaa_texture_if_needed( + &context.gpu, + surface.info().format, + config.size.width, + config.size.height, + path_sample_count, + ) + .unzip(); + #[cfg(target_os = "macos")] let core_video_texture_cache = unsafe { CVMetalTextureCache::new( @@ -383,12 +421,15 @@ impl BladeRenderer { last_sync_point: None, pipelines, instance_belt, - path_tiles: HashMap::default(), atlas, atlas_sampler, #[cfg(target_os = "macos")] core_video_texture_cache, path_sample_count, + path_intermediate_texture, + path_intermediate_texture_view, + path_intermediate_msaa_texture, + path_intermediate_msaa_texture_view, }) } @@ -441,6 +482,35 @@ impl BladeRenderer { self.surface_config.size = gpu_size; self.gpu .reconfigure_surface(&mut self.surface, self.surface_config); + self.gpu.destroy_texture(self.path_intermediate_texture); + self.gpu + .destroy_texture_view(self.path_intermediate_texture_view); + if let Some(msaa_texture) = self.path_intermediate_msaa_texture { + self.gpu.destroy_texture(msaa_texture); + } + if let Some(msaa_view) = self.path_intermediate_msaa_texture_view { + self.gpu.destroy_texture_view(msaa_view); + } + let (path_intermediate_texture, path_intermediate_texture_view) = + create_path_intermediate_texture( + &self.gpu, + self.surface.info().format, + gpu_size.width, + gpu_size.height, + ); + self.path_intermediate_texture = path_intermediate_texture; + self.path_intermediate_texture_view = path_intermediate_texture_view; + let (path_intermediate_msaa_texture, path_intermediate_msaa_texture_view) = + create_msaa_texture_if_needed( + &self.gpu, + self.surface.info().format, + gpu_size.width, + gpu_size.height, + self.path_sample_count, + ) + .unzip(); + self.path_intermediate_msaa_texture = path_intermediate_msaa_texture; + self.path_intermediate_msaa_texture_view = path_intermediate_msaa_texture_view; } } @@ -491,76 +561,63 @@ impl BladeRenderer { } #[profiling::function] - fn rasterize_paths(&mut self, paths: &[Path]) { - self.path_tiles.clear(); - let mut vertices_by_texture_id = HashMap::default(); - - for path in paths { - let clipped_bounds = path - .bounds - .intersect(&path.content_mask.bounds) - .map_origin(|origin| origin.floor()) - .map_size(|size| size.ceil()); - let tile = self.atlas.allocate_for_rendering( - clipped_bounds.size.map(Into::into), - AtlasTextureKind::Path, - &mut self.command_encoder, - ); - vertices_by_texture_id - .entry(tile.texture_id) - .or_insert(Vec::new()) - .extend(path.vertices.iter().map(|vertex| PathVertex { - xy_position: vertex.xy_position - clipped_bounds.origin - + tile.bounds.origin.map(Into::into), - st_position: vertex.st_position, - content_mask: ContentMask { - bounds: tile.bounds.map(Into::into), - }, - })); - self.path_tiles.insert(path.id, tile); + fn draw_paths_to_intermediate( + &mut self, + paths: &[Path], + width: f32, + height: f32, + ) { + self.command_encoder + .init_texture(self.path_intermediate_texture); + if let Some(msaa_texture) = self.path_intermediate_msaa_texture { + self.command_encoder.init_texture(msaa_texture); } - for (texture_id, vertices) in vertices_by_texture_id { - let tex_info = self.atlas.get_texture_info(texture_id); + let target = if let Some(msaa_view) = self.path_intermediate_msaa_texture_view { + gpu::RenderTarget { + view: msaa_view, + init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack), + finish_op: gpu::FinishOp::ResolveTo(self.path_intermediate_texture_view), + } + } else { + gpu::RenderTarget { + view: self.path_intermediate_texture_view, + init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack), + finish_op: gpu::FinishOp::Store, + } + }; + if let mut pass = self.command_encoder.render( + "rasterize paths", + gpu::RenderTargetSet { + colors: &[target], + depth_stencil: None, + }, + ) { let globals = GlobalParams { - viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32], + viewport_size: [width, height], premultiplied_alpha: 0, pad: 0, }; + let mut encoder = pass.with(&self.pipelines.path_rasterization); - let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) }; - let frame_view = tex_info.raw_view; - let color_target = if let Some(msaa_view) = tex_info.msaa_view { - gpu::RenderTarget { - view: msaa_view, - init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack), - finish_op: gpu::FinishOp::ResolveTo(frame_view), - } - } else { - gpu::RenderTarget { - view: frame_view, - init_op: gpu::InitOp::Clear(gpu::TextureColor::OpaqueBlack), - finish_op: gpu::FinishOp::Store, - } - }; - - if let mut pass = self.command_encoder.render( - "paths", - gpu::RenderTargetSet { - colors: &[color_target], - depth_stencil: None, - }, - ) { - let mut encoder = pass.with(&self.pipelines.path_rasterization); - encoder.bind( - 0, - &ShaderPathRasterizationData { - globals, - b_path_vertices: vertex_buf, - }, - ); - encoder.draw(0, vertices.len() as u32, 0, 1); + let mut vertices = Vec::new(); + for path in paths { + vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex { + xy_position: v.xy_position, + st_position: v.st_position, + color: path.color, + bounds: path.bounds.intersect(&path.content_mask.bounds), + })); } + let vertex_buf = unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) }; + encoder.bind( + 0, + &ShaderPathRasterizationData { + globals, + b_path_vertices: vertex_buf, + }, + ); + encoder.draw(0, vertices.len() as u32, 0, 1); } } @@ -572,12 +629,20 @@ impl BladeRenderer { self.gpu.destroy_command_encoder(&mut self.command_encoder); self.pipelines.destroy(&self.gpu); self.gpu.destroy_surface(&mut self.surface); + self.gpu.destroy_texture(self.path_intermediate_texture); + self.gpu + .destroy_texture_view(self.path_intermediate_texture_view); + if let Some(msaa_texture) = self.path_intermediate_msaa_texture { + self.gpu.destroy_texture(msaa_texture); + } + if let Some(msaa_view) = self.path_intermediate_msaa_texture_view { + self.gpu.destroy_texture_view(msaa_view); + } } pub fn draw(&mut self, scene: &Scene) { self.command_encoder.start(); self.atlas.before_frame(&mut self.command_encoder); - self.rasterize_paths(scene.paths()); let frame = { profiling::scope!("acquire frame"); @@ -597,7 +662,7 @@ impl BladeRenderer { pad: 0, }; - if let mut pass = self.command_encoder.render( + let mut pass = self.command_encoder.render( "main", gpu::RenderTargetSet { colors: &[gpu::RenderTarget { @@ -607,209 +672,235 @@ impl BladeRenderer { }], depth_stencil: None, }, - ) { - profiling::scope!("render pass"); - for batch in scene.batches() { - match batch { - PrimitiveBatch::Quads(quads) => { - let instance_buf = - unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) }; - let mut encoder = pass.with(&self.pipelines.quads); - encoder.bind( - 0, - &ShaderQuadsData { - globals, - b_quads: instance_buf, - }, - ); - encoder.draw(0, 4, 0, quads.len() as u32); - } - PrimitiveBatch::Shadows(shadows) => { - let instance_buf = - unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) }; - let mut encoder = pass.with(&self.pipelines.shadows); - encoder.bind( - 0, - &ShaderShadowsData { - globals, - b_shadows: instance_buf, - }, - ); - encoder.draw(0, 4, 0, shadows.len() as u32); - } - PrimitiveBatch::Paths(paths) => { - let mut encoder = pass.with(&self.pipelines.paths); - // todo(linux): group by texture ID - for path in paths { - let tile = &self.path_tiles[&path.id]; - let tex_info = self.atlas.get_texture_info(tile.texture_id); - let origin = path.bounds.intersect(&path.content_mask.bounds).origin; - let sprites = [PathSprite { - bounds: Bounds { - origin: origin.map(|p| p.floor()), - size: tile.bounds.size.map(Into::into), - }, - color: path.color, - tile: (*tile).clone(), - }]; + ); - let instance_buf = - unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) }; - encoder.bind( - 0, - &ShaderPathsData { - globals, - t_sprite: tex_info.raw_view, - s_sprite: self.atlas_sampler, - b_path_sprites: instance_buf, - }, - ); - encoder.draw(0, 4, 0, sprites.len() as u32); + profiling::scope!("render pass"); + for batch in scene.batches() { + match batch { + PrimitiveBatch::Quads(quads) => { + let instance_buf = unsafe { self.instance_belt.alloc_typed(quads, &self.gpu) }; + let mut encoder = pass.with(&self.pipelines.quads); + encoder.bind( + 0, + &ShaderQuadsData { + globals, + b_quads: instance_buf, + }, + ); + encoder.draw(0, 4, 0, quads.len() as u32); + } + PrimitiveBatch::Shadows(shadows) => { + let instance_buf = + unsafe { self.instance_belt.alloc_typed(shadows, &self.gpu) }; + let mut encoder = pass.with(&self.pipelines.shadows); + encoder.bind( + 0, + &ShaderShadowsData { + globals, + b_shadows: instance_buf, + }, + ); + encoder.draw(0, 4, 0, shadows.len() as u32); + } + PrimitiveBatch::Paths(paths) => { + let Some(first_path) = paths.first() else { + continue; + }; + drop(pass); + self.draw_paths_to_intermediate( + paths, + self.surface_config.size.width as f32, + self.surface_config.size.height as f32, + ); + pass = self.command_encoder.render( + "main", + gpu::RenderTargetSet { + colors: &[gpu::RenderTarget { + view: frame.texture_view(), + init_op: gpu::InitOp::Load, + finish_op: gpu::FinishOp::Store, + }], + depth_stencil: None, + }, + ); + let mut encoder = pass.with(&self.pipelines.paths); + // When copying paths from the intermediate texture to the drawable, + // each pixel must only be copied once, in case of transparent paths. + // + // If all paths have the same draw order, then their bounds are all + // disjoint, so we can copy each path's bounds individually. If this + // batch combines different draw orders, we perform a single copy + // for a minimal spanning rect. + let sprites = if paths.last().unwrap().order == first_path.order { + paths + .iter() + .map(|path| PathSprite { + bounds: path.bounds, + }) + .collect() + } else { + let mut bounds = first_path.bounds; + for path in paths.iter().skip(1) { + bounds = bounds.union(&path.bounds); } - } - PrimitiveBatch::Underlines(underlines) => { - let instance_buf = - unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) }; - let mut encoder = pass.with(&self.pipelines.underlines); - encoder.bind( - 0, - &ShaderUnderlinesData { - globals, - b_underlines: instance_buf, - }, - ); - encoder.draw(0, 4, 0, underlines.len() as u32); - } - PrimitiveBatch::MonochromeSprites { - texture_id, - sprites, - } => { - let tex_info = self.atlas.get_texture_info(texture_id); - let instance_buf = - unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) }; - let mut encoder = pass.with(&self.pipelines.mono_sprites); - encoder.bind( - 0, - &ShaderMonoSpritesData { - globals, - t_sprite: tex_info.raw_view, - s_sprite: self.atlas_sampler, - b_mono_sprites: instance_buf, - }, - ); - encoder.draw(0, 4, 0, sprites.len() as u32); - } - PrimitiveBatch::PolychromeSprites { - texture_id, - sprites, - } => { - let tex_info = self.atlas.get_texture_info(texture_id); - let instance_buf = - unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) }; - let mut encoder = pass.with(&self.pipelines.poly_sprites); - encoder.bind( - 0, - &ShaderPolySpritesData { - globals, - t_sprite: tex_info.raw_view, - s_sprite: self.atlas_sampler, - b_poly_sprites: instance_buf, - }, - ); - encoder.draw(0, 4, 0, sprites.len() as u32); - } - PrimitiveBatch::Surfaces(surfaces) => { - let mut _encoder = pass.with(&self.pipelines.surfaces); + vec![PathSprite { bounds }] + }; + let instance_buf = + unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) }; + encoder.bind( + 0, + &ShaderPathsData { + globals, + t_sprite: self.path_intermediate_texture_view, + s_sprite: self.atlas_sampler, + b_path_sprites: instance_buf, + }, + ); + encoder.draw(0, 4, 0, sprites.len() as u32); + } + PrimitiveBatch::Underlines(underlines) => { + let instance_buf = + unsafe { self.instance_belt.alloc_typed(underlines, &self.gpu) }; + let mut encoder = pass.with(&self.pipelines.underlines); + encoder.bind( + 0, + &ShaderUnderlinesData { + globals, + b_underlines: instance_buf, + }, + ); + encoder.draw(0, 4, 0, underlines.len() as u32); + } + PrimitiveBatch::MonochromeSprites { + texture_id, + sprites, + } => { + let tex_info = self.atlas.get_texture_info(texture_id); + let instance_buf = + unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) }; + let mut encoder = pass.with(&self.pipelines.mono_sprites); + encoder.bind( + 0, + &ShaderMonoSpritesData { + globals, + t_sprite: tex_info.raw_view, + s_sprite: self.atlas_sampler, + b_mono_sprites: instance_buf, + }, + ); + encoder.draw(0, 4, 0, sprites.len() as u32); + } + PrimitiveBatch::PolychromeSprites { + texture_id, + sprites, + } => { + let tex_info = self.atlas.get_texture_info(texture_id); + let instance_buf = + unsafe { self.instance_belt.alloc_typed(sprites, &self.gpu) }; + let mut encoder = pass.with(&self.pipelines.poly_sprites); + encoder.bind( + 0, + &ShaderPolySpritesData { + globals, + t_sprite: tex_info.raw_view, + s_sprite: self.atlas_sampler, + b_poly_sprites: instance_buf, + }, + ); + encoder.draw(0, 4, 0, sprites.len() as u32); + } + PrimitiveBatch::Surfaces(surfaces) => { + let mut _encoder = pass.with(&self.pipelines.surfaces); - for surface in surfaces { - #[cfg(not(target_os = "macos"))] - { - let _ = surface; - continue; - }; + for surface in surfaces { + #[cfg(not(target_os = "macos"))] + { + let _ = surface; + continue; + }; - #[cfg(target_os = "macos")] - { - let (t_y, t_cb_cr) = unsafe { - use core_foundation::base::TCFType as _; - use std::ptr; + #[cfg(target_os = "macos")] + { + let (t_y, t_cb_cr) = unsafe { + use core_foundation::base::TCFType as _; + use std::ptr; - assert_eq!( + assert_eq!( surface.image_buffer.get_pixel_format(), core_video::pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ); - let y_texture = self - .core_video_texture_cache - .create_texture_from_image( - surface.image_buffer.as_concrete_TypeRef(), - ptr::null(), - metal::MTLPixelFormat::R8Unorm, - surface.image_buffer.get_width_of_plane(0), - surface.image_buffer.get_height_of_plane(0), - 0, - ) - .unwrap(); - let cb_cr_texture = self - .core_video_texture_cache - .create_texture_from_image( - surface.image_buffer.as_concrete_TypeRef(), - ptr::null(), - metal::MTLPixelFormat::RG8Unorm, - surface.image_buffer.get_width_of_plane(1), - surface.image_buffer.get_height_of_plane(1), - 1, - ) - .unwrap(); - ( - gpu::TextureView::from_metal_texture( - &objc2::rc::Retained::retain( - foreign_types::ForeignTypeRef::as_ptr( - y_texture.as_texture_ref(), - ) - as *mut objc2::runtime::ProtocolObject< - dyn objc2_metal::MTLTexture, - >, - ) - .unwrap(), - gpu::TexelAspects::COLOR, - ), - gpu::TextureView::from_metal_texture( - &objc2::rc::Retained::retain( - foreign_types::ForeignTypeRef::as_ptr( - cb_cr_texture.as_texture_ref(), - ) - as *mut objc2::runtime::ProtocolObject< - dyn objc2_metal::MTLTexture, - >, - ) - .unwrap(), - gpu::TexelAspects::COLOR, - ), + let y_texture = self + .core_video_texture_cache + .create_texture_from_image( + surface.image_buffer.as_concrete_TypeRef(), + ptr::null(), + metal::MTLPixelFormat::R8Unorm, + surface.image_buffer.get_width_of_plane(0), + surface.image_buffer.get_height_of_plane(0), + 0, ) - }; + .unwrap(); + let cb_cr_texture = self + .core_video_texture_cache + .create_texture_from_image( + surface.image_buffer.as_concrete_TypeRef(), + ptr::null(), + metal::MTLPixelFormat::RG8Unorm, + surface.image_buffer.get_width_of_plane(1), + surface.image_buffer.get_height_of_plane(1), + 1, + ) + .unwrap(); + ( + gpu::TextureView::from_metal_texture( + &objc2::rc::Retained::retain( + foreign_types::ForeignTypeRef::as_ptr( + y_texture.as_texture_ref(), + ) + as *mut objc2::runtime::ProtocolObject< + dyn objc2_metal::MTLTexture, + >, + ) + .unwrap(), + gpu::TexelAspects::COLOR, + ), + gpu::TextureView::from_metal_texture( + &objc2::rc::Retained::retain( + foreign_types::ForeignTypeRef::as_ptr( + cb_cr_texture.as_texture_ref(), + ) + as *mut objc2::runtime::ProtocolObject< + dyn objc2_metal::MTLTexture, + >, + ) + .unwrap(), + gpu::TexelAspects::COLOR, + ), + ) + }; - _encoder.bind( - 0, - &ShaderSurfacesData { - globals, - surface_locals: SurfaceParams { - bounds: surface.bounds.into(), - content_mask: surface.content_mask.bounds.into(), - }, - t_y, - t_cb_cr, - s_surface: self.atlas_sampler, + _encoder.bind( + 0, + &ShaderSurfacesData { + globals, + surface_locals: SurfaceParams { + bounds: surface.bounds.into(), + content_mask: surface.content_mask.bounds.into(), }, - ); + t_y, + t_cb_cr, + s_surface: self.atlas_sampler, + }, + ); - _encoder.draw(0, 4, 0, 1); - } + _encoder.draw(0, 4, 0, 1); } } } } } + drop(pass); self.command_encoder.present(frame); let sync_point = self.gpu.submit(&mut self.command_encoder); @@ -817,9 +908,79 @@ impl BladeRenderer { profiling::scope!("finish"); self.instance_belt.flush(&sync_point); self.atlas.after_frame(&sync_point); - self.atlas.clear_textures(AtlasTextureKind::Path); self.wait_for_gpu(); self.last_sync_point = Some(sync_point); } } + +fn create_path_intermediate_texture( + gpu: &gpu::Context, + format: gpu::TextureFormat, + width: u32, + height: u32, +) -> (gpu::Texture, gpu::TextureView) { + let texture = gpu.create_texture(gpu::TextureDesc { + name: "path intermediate", + format, + size: gpu::Extent { + width, + height, + depth: 1, + }, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: gpu::TextureDimension::D2, + usage: gpu::TextureUsage::COPY | gpu::TextureUsage::RESOURCE | gpu::TextureUsage::TARGET, + external: None, + }); + let texture_view = gpu.create_texture_view( + texture, + gpu::TextureViewDesc { + name: "path intermediate view", + format, + dimension: gpu::ViewDimension::D2, + subresources: &Default::default(), + }, + ); + (texture, texture_view) +} + +fn create_msaa_texture_if_needed( + gpu: &gpu::Context, + format: gpu::TextureFormat, + width: u32, + height: u32, + sample_count: u32, +) -> Option<(gpu::Texture, gpu::TextureView)> { + if sample_count <= 1 { + return None; + } + let texture_msaa = gpu.create_texture(gpu::TextureDesc { + name: "path intermediate msaa", + format, + size: gpu::Extent { + width, + height, + depth: 1, + }, + array_layer_count: 1, + mip_level_count: 1, + sample_count, + dimension: gpu::TextureDimension::D2, + usage: gpu::TextureUsage::TARGET, + external: None, + }); + let texture_view_msaa = gpu.create_texture_view( + texture_msaa, + gpu::TextureViewDesc { + name: "path intermediate msaa view", + format, + dimension: gpu::ViewDimension::D2, + subresources: &Default::default(), + }, + ); + + Some((texture_msaa, texture_view_msaa)) +} diff --git a/crates/gpui/src/platform/blade/shaders.wgsl b/crates/gpui/src/platform/blade/shaders.wgsl index 0b34a0eea3..b1ffb1812e 100644 --- a/crates/gpui/src/platform/blade/shaders.wgsl +++ b/crates/gpui/src/platform/blade/shaders.wgsl @@ -924,16 +924,19 @@ fn fs_shadow(input: ShadowVarying) -> @location(0) vec4 { // --- path rasterization --- // -struct PathVertex { +struct PathRasterizationVertex { xy_position: vec2, st_position: vec2, - content_mask: Bounds, + color: Background, + bounds: Bounds, } -var b_path_vertices: array; + +var b_path_vertices: array; struct PathRasterizationVarying { @builtin(position) position: vec4, @location(0) st_position: vec2, + @location(1) vertex_id: u32, //TODO: use `clip_distance` once Naga supports it @location(3) clip_distances: vec4, } @@ -945,40 +948,54 @@ fn vs_path_rasterization(@builtin(vertex_index) vertex_id: u32) -> PathRasteriza var out = PathRasterizationVarying(); out.position = to_device_position_impl(v.xy_position); out.st_position = v.st_position; - out.clip_distances = distance_from_clip_rect_impl(v.xy_position, v.content_mask); + out.vertex_id = vertex_id; + out.clip_distances = distance_from_clip_rect_impl(v.xy_position, v.bounds); return out; } @fragment -fn fs_path_rasterization(input: PathRasterizationVarying) -> @location(0) f32 { +fn fs_path_rasterization(input: PathRasterizationVarying) -> @location(0) vec4 { let dx = dpdx(input.st_position); let dy = dpdy(input.st_position); if (any(input.clip_distances < vec4(0.0))) { - return 0.0; + return vec4(0.0); } - let gradient = 2.0 * input.st_position.xx * vec2(dx.x, dy.x) - vec2(dx.y, dy.y); - let f = input.st_position.x * input.st_position.x - input.st_position.y; - let distance = f / length(gradient); - return saturate(0.5 - distance); + let v = b_path_vertices[input.vertex_id]; + let background = v.color; + let bounds = v.bounds; + + var alpha: f32; + if (length(vec2(dx.x, dy.x)) < 0.001) { + // If the gradient is too small, return a solid color. + alpha = 1.0; + } else { + let gradient = 2.0 * input.st_position.xx * vec2(dx.x, dy.x) - vec2(dx.y, dy.y); + let f = input.st_position.x * input.st_position.x - input.st_position.y; + let distance = f / length(gradient); + alpha = saturate(0.5 - distance); + } + let gradient_color = prepare_gradient_color( + background.tag, + background.color_space, + background.solid, + background.colors, + ); + let color = gradient_color(background, input.position.xy, bounds, + gradient_color.solid, gradient_color.color0, gradient_color.color1); + return vec4(color.rgb * color.a * alpha, color.a * alpha); } // --- paths --- // struct PathSprite { bounds: Bounds, - color: Background, - tile: AtlasTile, } var b_path_sprites: array; struct PathVarying { @builtin(position) position: vec4, - @location(0) tile_position: vec2, - @location(1) @interpolate(flat) instance_id: u32, - @location(2) @interpolate(flat) color_solid: vec4, - @location(3) @interpolate(flat) color0: vec4, - @location(4) @interpolate(flat) color1: vec4, + @location(0) texture_coords: vec2, } @vertex @@ -986,33 +1003,22 @@ fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) insta let unit_vertex = vec2(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u)); let sprite = b_path_sprites[instance_id]; // Don't apply content mask because it was already accounted for when rasterizing the path. + let device_position = to_device_position(unit_vertex, sprite.bounds); + // For screen-space intermediate texture, convert screen position to texture coordinates + let screen_position = sprite.bounds.origin + unit_vertex * sprite.bounds.size; + let texture_coords = screen_position / globals.viewport_size; var out = PathVarying(); - out.position = to_device_position(unit_vertex, sprite.bounds); - out.tile_position = to_tile_position(unit_vertex, sprite.tile); - out.instance_id = instance_id; + out.position = device_position; + out.texture_coords = texture_coords; - let gradient = prepare_gradient_color( - sprite.color.tag, - sprite.color.color_space, - sprite.color.solid, - sprite.color.colors - ); - out.color_solid = gradient.solid; - out.color0 = gradient.color0; - out.color1 = gradient.color1; return out; } @fragment fn fs_path(input: PathVarying) -> @location(0) vec4 { - let sample = textureSample(t_sprite, s_sprite, input.tile_position).r; - let mask = 1.0 - abs(1.0 - sample % 2.0); - let sprite = b_path_sprites[input.instance_id]; - let background = sprite.color; - let color = gradient_color(background, input.position.xy, sprite.bounds, - input.color_solid, input.color0, input.color1); - return blend_color(color, mask); + let sample = textureSample(t_sprite, s_sprite, input.texture_coords); + return sample; } // --- underlines --- // diff --git a/crates/gpui/src/platform/mac/metal_atlas.rs b/crates/gpui/src/platform/mac/metal_atlas.rs index 366f2dcc3c..5d2d8e63e0 100644 --- a/crates/gpui/src/platform/mac/metal_atlas.rs +++ b/crates/gpui/src/platform/mac/metal_atlas.rs @@ -13,53 +13,25 @@ use std::borrow::Cow; pub(crate) struct MetalAtlas(Mutex); impl MetalAtlas { - pub(crate) fn new(device: Device, path_sample_count: u32) -> Self { + pub(crate) fn new(device: Device) -> Self { MetalAtlas(Mutex::new(MetalAtlasState { device: AssertSend(device), monochrome_textures: Default::default(), polychrome_textures: Default::default(), - path_textures: Default::default(), tiles_by_key: Default::default(), - path_sample_count, })) } pub(crate) fn metal_texture(&self, id: AtlasTextureId) -> metal::Texture { self.0.lock().texture(id).metal_texture.clone() } - - pub(crate) fn msaa_texture(&self, id: AtlasTextureId) -> Option { - self.0.lock().texture(id).msaa_texture.clone() - } - - pub(crate) fn allocate( - &self, - size: Size, - texture_kind: AtlasTextureKind, - ) -> Option { - self.0.lock().allocate(size, texture_kind) - } - - pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) { - let mut lock = self.0.lock(); - let textures = match texture_kind { - AtlasTextureKind::Monochrome => &mut lock.monochrome_textures, - AtlasTextureKind::Polychrome => &mut lock.polychrome_textures, - AtlasTextureKind::Path => &mut lock.path_textures, - }; - for texture in textures.iter_mut() { - texture.clear(); - } - } } struct MetalAtlasState { device: AssertSend, monochrome_textures: AtlasTextureList, polychrome_textures: AtlasTextureList, - path_textures: AtlasTextureList, tiles_by_key: FxHashMap, - path_sample_count: u32, } impl PlatformAtlas for MetalAtlas { @@ -94,7 +66,6 @@ impl PlatformAtlas for MetalAtlas { let textures = match id.kind { AtlasTextureKind::Monochrome => &mut lock.monochrome_textures, AtlasTextureKind::Polychrome => &mut lock.polychrome_textures, - AtlasTextureKind::Path => &mut lock.polychrome_textures, }; let Some(texture_slot) = textures @@ -128,7 +99,6 @@ impl MetalAtlasState { let textures = match texture_kind { AtlasTextureKind::Monochrome => &mut self.monochrome_textures, AtlasTextureKind::Polychrome => &mut self.polychrome_textures, - AtlasTextureKind::Path => &mut self.path_textures, }; if let Some(tile) = textures @@ -173,31 +143,14 @@ impl MetalAtlasState { 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); - // We currently only enable MSAA for path textures. - let msaa_texture = if self.path_sample_count > 1 && kind == AtlasTextureKind::Path { - let mut descriptor = texture_descriptor.clone(); - descriptor.set_texture_type(metal::MTLTextureType::D2Multisample); - descriptor.set_storage_mode(metal::MTLStorageMode::Private); - descriptor.set_sample_count(self.path_sample_count as _); - let msaa_texture = self.device.new_texture(&descriptor); - Some(msaa_texture) - } else { - None - }; - let texture_list = match kind { AtlasTextureKind::Monochrome => &mut self.monochrome_textures, AtlasTextureKind::Polychrome => &mut self.polychrome_textures, - AtlasTextureKind::Path => &mut self.path_textures, }; let index = texture_list.free_list.pop(); @@ -209,7 +162,6 @@ impl MetalAtlasState { }, allocator: etagere::BucketedAtlasAllocator::new(size.into()), metal_texture: AssertSend(metal_texture), - msaa_texture: AssertSend(msaa_texture), live_atlas_keys: 0, }; @@ -226,7 +178,6 @@ impl MetalAtlasState { let textures = match id.kind { crate::AtlasTextureKind::Monochrome => &self.monochrome_textures, crate::AtlasTextureKind::Polychrome => &self.polychrome_textures, - crate::AtlasTextureKind::Path => &self.path_textures, }; textures[id.index as usize].as_ref().unwrap() } @@ -236,15 +187,10 @@ struct MetalAtlasTexture { id: AtlasTextureId, allocator: BucketedAtlasAllocator, metal_texture: AssertSend, - msaa_texture: AssertSend>, live_atlas_keys: u32, } impl MetalAtlasTexture { - fn clear(&mut self) { - self.allocator.clear(); - } - fn allocate(&mut self, size: Size) -> Option { let allocation = self.allocator.allocate(size.into())?; let tile = AtlasTile { diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index 3cdc2dd2cf..fb5cb852d6 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -1,27 +1,30 @@ use super::metal_atlas::MetalAtlas; use crate::{ - AtlasTextureId, AtlasTextureKind, AtlasTile, Background, Bounds, ContentMask, DevicePixels, - MonochromeSprite, PaintSurface, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch, - Quad, ScaledPixels, Scene, Shadow, Size, Surface, Underline, point, size, + AtlasTextureId, Background, Bounds, ContentMask, DevicePixels, MonochromeSprite, PaintSurface, + Path, Point, PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, + Surface, Underline, point, size, }; -use anyhow::{Context as _, Result}; +use anyhow::Result; use block::ConcreteBlock; use cocoa::{ base::{NO, YES}, foundation::{NSSize, NSUInteger}, quartzcore::AutoresizingMask, }; -use collections::HashMap; + use core_foundation::base::TCFType; use core_video::{ metal_texture::CVMetalTextureGetTexture, metal_texture_cache::CVMetalTextureCache, pixel_buffer::kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, }; use foreign_types::{ForeignType, ForeignTypeRef}; -use metal::{CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange}; +use metal::{ + CAMetalLayer, CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange, + RenderPassColorAttachmentDescriptorRef, +}; use objc::{self, msg_send, sel, sel_impl}; use parking_lot::Mutex; -use smallvec::SmallVec; + use std::{cell::Cell, ffi::c_void, mem, ptr, sync::Arc}; // Exported to metal @@ -111,6 +114,17 @@ pub(crate) struct MetalRenderer { instance_buffer_pool: Arc>, sprite_atlas: Arc, core_video_texture_cache: core_video::metal_texture_cache::CVMetalTextureCache, + path_intermediate_texture: Option, + path_intermediate_msaa_texture: Option, + path_sample_count: u32, +} + +#[repr(C)] +pub struct PathRasterizationVertex { + pub xy_position: Point, + pub st_position: Point, + pub color: Background, + pub bounds: Bounds, } impl MetalRenderer { @@ -175,10 +189,10 @@ impl MetalRenderer { "paths_rasterization", "path_rasterization_vertex", "path_rasterization_fragment", - MTLPixelFormat::R16Float, + MTLPixelFormat::BGRA8Unorm, PATH_SAMPLE_COUNT, ); - let path_sprites_pipeline_state = build_pipeline_state( + let path_sprites_pipeline_state = build_path_sprite_pipeline_state( &device, &library, "path_sprites", @@ -236,7 +250,7 @@ impl MetalRenderer { ); let command_queue = device.new_command_queue(); - let sprite_atlas = Arc::new(MetalAtlas::new(device.clone(), PATH_SAMPLE_COUNT)); + let sprite_atlas = Arc::new(MetalAtlas::new(device.clone())); let core_video_texture_cache = CVMetalTextureCache::new(None, device.clone(), None).unwrap(); @@ -257,6 +271,9 @@ impl MetalRenderer { instance_buffer_pool, sprite_atlas, core_video_texture_cache, + path_intermediate_texture: None, + path_intermediate_msaa_texture: None, + path_sample_count: PATH_SAMPLE_COUNT, } } @@ -289,6 +306,31 @@ impl MetalRenderer { setDrawableSize: size ]; } + let device_pixels_size = Size { + width: DevicePixels(size.width as i32), + height: DevicePixels(size.height as i32), + }; + self.update_path_intermediate_textures(device_pixels_size); + } + + fn update_path_intermediate_textures(&mut self, size: Size) { + let texture_descriptor = metal::TextureDescriptor::new(); + texture_descriptor.set_width(size.width.0 as u64); + texture_descriptor.set_height(size.height.0 as u64); + texture_descriptor.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm); + texture_descriptor + .set_usage(metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead); + self.path_intermediate_texture = Some(self.device.new_texture(&texture_descriptor)); + + if self.path_sample_count > 1 { + let mut msaa_descriptor = texture_descriptor.clone(); + msaa_descriptor.set_texture_type(metal::MTLTextureType::D2Multisample); + msaa_descriptor.set_storage_mode(metal::MTLStorageMode::Private); + msaa_descriptor.set_sample_count(self.path_sample_count as _); + self.path_intermediate_msaa_texture = Some(self.device.new_texture(&msaa_descriptor)); + } else { + self.path_intermediate_msaa_texture = None; + } } pub fn update_transparency(&self, _transparent: bool) { @@ -374,38 +416,18 @@ impl MetalRenderer { ) -> Result { let command_queue = self.command_queue.clone(); let command_buffer = command_queue.new_command_buffer(); + let alpha = if self.layer.is_opaque() { 1. } else { 0. }; let mut instance_offset = 0; - let path_tiles = self - .rasterize_paths( - scene.paths(), - instance_buffer, - &mut instance_offset, - command_buffer, - ) - .with_context(|| format!("rasterizing {} paths", scene.paths().len()))?; - - let render_pass_descriptor = metal::RenderPassDescriptor::new(); - let color_attachment = render_pass_descriptor - .color_attachments() - .object_at(0) - .unwrap(); - - color_attachment.set_texture(Some(drawable.texture())); - color_attachment.set_load_action(metal::MTLLoadAction::Clear); - color_attachment.set_store_action(metal::MTLStoreAction::Store); - let alpha = if self.layer.is_opaque() { 1. } else { 0. }; - color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha)); - let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor); - - command_encoder.set_viewport(metal::MTLViewport { - originX: 0.0, - originY: 0.0, - width: i32::from(viewport_size.width) as f64, - height: i32::from(viewport_size.height) as f64, - znear: 0.0, - zfar: 1.0, - }); + let mut command_encoder = new_command_encoder( + command_buffer, + drawable, + viewport_size, + |color_attachment| { + color_attachment.set_load_action(metal::MTLLoadAction::Clear); + color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., alpha)); + }, + ); for batch in scene.batches() { let ok = match batch { @@ -414,29 +436,53 @@ impl MetalRenderer { instance_buffer, &mut instance_offset, viewport_size, - command_encoder, + &command_encoder, ), PrimitiveBatch::Quads(quads) => self.draw_quads( quads, instance_buffer, &mut instance_offset, viewport_size, - command_encoder, - ), - PrimitiveBatch::Paths(paths) => self.draw_paths( - paths, - &path_tiles, - instance_buffer, - &mut instance_offset, - viewport_size, - command_encoder, + &command_encoder, ), + PrimitiveBatch::Paths(paths) => { + command_encoder.end_encoding(); + + let did_draw = self.draw_paths_to_intermediate( + paths, + instance_buffer, + &mut instance_offset, + viewport_size, + command_buffer, + ); + + command_encoder = new_command_encoder( + command_buffer, + drawable, + viewport_size, + |color_attachment| { + color_attachment.set_load_action(metal::MTLLoadAction::Load); + }, + ); + + if did_draw { + self.draw_paths_from_intermediate( + paths, + instance_buffer, + &mut instance_offset, + viewport_size, + &command_encoder, + ) + } else { + false + } + } PrimitiveBatch::Underlines(underlines) => self.draw_underlines( underlines, instance_buffer, &mut instance_offset, viewport_size, - command_encoder, + &command_encoder, ), PrimitiveBatch::MonochromeSprites { texture_id, @@ -447,7 +493,7 @@ impl MetalRenderer { instance_buffer, &mut instance_offset, viewport_size, - command_encoder, + &command_encoder, ), PrimitiveBatch::PolychromeSprites { texture_id, @@ -458,17 +504,16 @@ impl MetalRenderer { instance_buffer, &mut instance_offset, viewport_size, - command_encoder, + &command_encoder, ), PrimitiveBatch::Surfaces(surfaces) => self.draw_surfaces( surfaces, instance_buffer, &mut instance_offset, viewport_size, - command_encoder, + &command_encoder, ), }; - if !ok { command_encoder.end_encoding(); anyhow::bail!( @@ -493,104 +538,90 @@ impl MetalRenderer { Ok(command_buffer.to_owned()) } - fn rasterize_paths( + fn draw_paths_to_intermediate( &self, paths: &[Path], instance_buffer: &mut InstanceBuffer, instance_offset: &mut usize, + viewport_size: Size, command_buffer: &metal::CommandBufferRef, - ) -> Option> { - self.sprite_atlas.clear_textures(AtlasTextureKind::Path); + ) -> bool { + if paths.is_empty() { + return true; + } + let Some(intermediate_texture) = &self.path_intermediate_texture else { + return false; + }; - let mut tiles = HashMap::default(); - let mut vertices_by_texture_id = HashMap::default(); + let render_pass_descriptor = metal::RenderPassDescriptor::new(); + let color_attachment = render_pass_descriptor + .color_attachments() + .object_at(0) + .unwrap(); + color_attachment.set_load_action(metal::MTLLoadAction::Clear); + color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 0.)); + + if let Some(msaa_texture) = &self.path_intermediate_msaa_texture { + color_attachment.set_texture(Some(msaa_texture)); + color_attachment.set_resolve_texture(Some(intermediate_texture)); + color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve); + } else { + color_attachment.set_texture(Some(intermediate_texture)); + color_attachment.set_store_action(metal::MTLStoreAction::Store); + } + + let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor); + command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state); + + align_offset(instance_offset); + let mut vertices = Vec::new(); for path in paths { - let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds); - - let tile = self - .sprite_atlas - .allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path)?; - vertices_by_texture_id - .entry(tile.texture_id) - .or_insert(Vec::new()) - .extend(path.vertices.iter().map(|vertex| PathVertex { - xy_position: vertex.xy_position - clipped_bounds.origin - + tile.bounds.origin.map(Into::into), - st_position: vertex.st_position, - content_mask: ContentMask { - bounds: tile.bounds.map(Into::into), - }, - })); - tiles.insert(path.id, tile); + vertices.extend(path.vertices.iter().map(|v| PathRasterizationVertex { + xy_position: v.xy_position, + st_position: v.st_position, + color: path.color, + bounds: path.bounds.intersect(&path.content_mask.bounds), + })); } - - for (texture_id, vertices) in vertices_by_texture_id { - align_offset(instance_offset); - let vertices_bytes_len = mem::size_of_val(vertices.as_slice()); - let next_offset = *instance_offset + vertices_bytes_len; - if next_offset > instance_buffer.size { - return None; - } - - 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); - let msaa_texture = self.sprite_atlas.msaa_texture(texture_id); - - if let Some(msaa_texture) = msaa_texture { - color_attachment.set_texture(Some(&msaa_texture)); - color_attachment.set_resolve_texture(Some(&texture)); - color_attachment.set_load_action(metal::MTLLoadAction::Clear); - color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve); - } else { - 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(&instance_buffer.metal_buffer), - *instance_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 { - (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_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, - ); + let vertices_bytes_len = mem::size_of_val(vertices.as_slice()); + let next_offset = *instance_offset + vertices_bytes_len; + if next_offset > instance_buffer.size { command_encoder.end_encoding(); - *instance_offset = next_offset; + return false; } + command_encoder.set_vertex_buffer( + PathRasterizationInputIndex::Vertices as u64, + Some(&instance_buffer.metal_buffer), + *instance_offset as u64, + ); + command_encoder.set_vertex_bytes( + PathRasterizationInputIndex::ViewportSize as u64, + mem::size_of_val(&viewport_size) as u64, + &viewport_size as *const Size as *const _, + ); + command_encoder.set_fragment_buffer( + PathRasterizationInputIndex::Vertices as u64, + Some(&instance_buffer.metal_buffer), + *instance_offset as u64, + ); + let buffer_contents = + unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_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, + ); + *instance_offset = next_offset; - Some(tiles) + command_encoder.end_encoding(); + true } fn draw_shadows( @@ -715,18 +746,21 @@ impl MetalRenderer { true } - fn draw_paths( + fn draw_paths_from_intermediate( &self, paths: &[Path], - tiles_by_path_id: &HashMap, instance_buffer: &mut InstanceBuffer, instance_offset: &mut usize, viewport_size: Size, command_encoder: &metal::RenderCommandEncoderRef, ) -> bool { - if paths.is_empty() { + let Some(ref first_path) = paths.first() else { return true; - } + }; + + let Some(ref intermediate_texture) = self.path_intermediate_texture else { + return false; + }; command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state); command_encoder.set_vertex_buffer( @@ -740,88 +774,65 @@ impl MetalRenderer { &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 - .iter() - .map(|path| (path, tiles_by_path_id.get(&path.id).unwrap())) - .peekable(); + command_encoder.set_fragment_texture( + SpriteInputIndex::AtlasTexture as u64, + Some(intermediate_texture), + ); - 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); - let origin = path.bounds.intersect(&path.content_mask.bounds).origin; - sprites.push(PathSprite { - bounds: Bounds { - origin: 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(instance_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(&instance_buffer.metal_buffer), - *instance_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(&instance_buffer.metal_buffer), - *instance_offset as u64, - ); - command_encoder - .set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture)); - - let sprite_bytes_len = mem::size_of_val(sprites.as_slice()); - let next_offset = *instance_offset + sprite_bytes_len; - if next_offset > instance_buffer.size { - return false; - } - - let buffer_contents = unsafe { - (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) - }; - - unsafe { - ptr::copy_nonoverlapping( - sprites.as_ptr() as *const u8, - buffer_contents, - sprite_bytes_len, - ); - } - - command_encoder.draw_primitives_instanced( - metal::MTLPrimitiveType::Triangle, - 0, - 6, - sprites.len() as u64, - ); - *instance_offset = next_offset; - sprites.clear(); + // When copying paths from the intermediate texture to the drawable, + // each pixel must only be copied once, in case of transparent paths. + // + // If all paths have the same draw order, then their bounds are all + // disjoint, so we can copy each path's bounds individually. If this + // batch combines different draw orders, we perform a single copy + // for a minimal spanning rect. + let sprites; + if paths.last().unwrap().order == first_path.order { + sprites = paths + .iter() + .map(|path| PathSprite { + bounds: path.bounds, + }) + .collect(); + } else { + let mut bounds = first_path.bounds; + for path in paths.iter().skip(1) { + bounds = bounds.union(&path.bounds); } + sprites = vec![PathSprite { bounds }]; } + + align_offset(instance_offset); + let sprite_bytes_len = mem::size_of_val(sprites.as_slice()); + let next_offset = *instance_offset + sprite_bytes_len; + if next_offset > instance_buffer.size { + return false; + } + + command_encoder.set_vertex_buffer( + SpriteInputIndex::Sprites as u64, + Some(&instance_buffer.metal_buffer), + *instance_offset as u64, + ); + + let buffer_contents = + unsafe { (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset) }; + unsafe { + ptr::copy_nonoverlapping( + sprites.as_ptr() as *const u8, + buffer_contents, + sprite_bytes_len, + ); + } + + command_encoder.draw_primitives_instanced( + metal::MTLPrimitiveType::Triangle, + 0, + 6, + sprites.len() as u64, + ); + *instance_offset = next_offset; + true } @@ -1136,6 +1147,33 @@ impl MetalRenderer { } } +fn new_command_encoder<'a>( + command_buffer: &'a metal::CommandBufferRef, + drawable: &'a metal::MetalDrawableRef, + viewport_size: Size, + configure_color_attachment: impl Fn(&RenderPassColorAttachmentDescriptorRef), +) -> &'a metal::RenderCommandEncoderRef { + let render_pass_descriptor = metal::RenderPassDescriptor::new(); + let color_attachment = render_pass_descriptor + .color_attachments() + .object_at(0) + .unwrap(); + color_attachment.set_texture(Some(drawable.texture())); + color_attachment.set_store_action(metal::MTLStoreAction::Store); + configure_color_attachment(color_attachment); + + let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor); + command_encoder.set_viewport(metal::MTLViewport { + originX: 0.0, + originY: 0.0, + width: i32::from(viewport_size.width) as f64, + height: i32::from(viewport_size.height) as f64, + znear: 0.0, + zfar: 1.0, + }); + command_encoder +} + fn build_pipeline_state( device: &metal::DeviceRef, library: &metal::LibraryRef, @@ -1170,6 +1208,40 @@ fn build_pipeline_state( .expect("could not create render pipeline state") } +fn build_path_sprite_pipeline_state( + device: &metal::DeviceRef, + library: &metal::LibraryRef, + label: &str, + vertex_fn_name: &str, + fragment_fn_name: &str, + pixel_format: metal::MTLPixelFormat, +) -> metal::RenderPipelineState { + let vertex_fn = library + .get_function(vertex_fn_name, None) + .expect("error locating vertex function"); + let fragment_fn = library + .get_function(fragment_fn_name, None) + .expect("error locating fragment function"); + + 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())); + 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::OneMinusSourceAlpha); + color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One); + + device + .new_render_pipeline_state(&descriptor) + .expect("could not create render pipeline state") +} + fn build_path_rasterization_pipeline_state( device: &metal::DeviceRef, library: &metal::LibraryRef, @@ -1192,7 +1264,7 @@ fn build_path_rasterization_pipeline_state( descriptor.set_fragment_function(Some(fragment_fn.as_ref())); if path_sample_count > 1 { descriptor.set_raster_sample_count(path_sample_count as _); - descriptor.set_alpha_to_coverage_enabled(true); + descriptor.set_alpha_to_coverage_enabled(false); } let color_attachment = descriptor.color_attachments().object_at(0).unwrap(); color_attachment.set_pixel_format(pixel_format); @@ -1201,8 +1273,8 @@ fn build_path_rasterization_pipeline_state( 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); + color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha); + color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha); device .new_render_pipeline_state(&descriptor) @@ -1257,15 +1329,13 @@ enum SurfaceInputIndex { #[repr(C)] enum PathRasterizationInputIndex { Vertices = 0, - AtlasTextureSize = 1, + ViewportSize = 1, } #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] pub struct PathSprite { pub bounds: Bounds, - pub color: Background, - pub tile: AtlasTile, } #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/crates/gpui/src/platform/mac/shaders.metal b/crates/gpui/src/platform/mac/shaders.metal index 64ebb1e22b..f9d5bdbf4c 100644 --- a/crates/gpui/src/platform/mac/shaders.metal +++ b/crates/gpui/src/platform/mac/shaders.metal @@ -701,107 +701,117 @@ fragment float4 polychrome_sprite_fragment( struct PathRasterizationVertexOutput { float4 position [[position]]; float2 st_position; + uint vertex_id [[flat]]; float clip_rect_distance [[clip_distance]][4]; }; struct PathRasterizationFragmentInput { float4 position [[position]]; float2 st_position; + uint vertex_id [[flat]]; }; 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]; + uint vertex_id [[vertex_id]], + constant PathRasterizationVertex *vertices [[buffer(PathRasterizationInputIndex_Vertices)]], + constant Size_DevicePixels *atlas_size [[buffer(PathRasterizationInputIndex_ViewportSize)]] +) { + PathRasterizationVertex 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); + float4 position = float4( + vertex_position * float2(2. / atlas_size->width, -2. / atlas_size->height) + float2(-1., 1.), + 0., + 1. + ); return PathRasterizationVertexOutput{ - float4(vertex_position / viewport_size * float2(2., -2.) + - float2(-1., 1.), - 0., 1.), + position, 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}}; + vertex_id, + { + v.xy_position.x - v.bounds.origin.x, + v.bounds.origin.x + v.bounds.size.width - v.xy_position.x, + v.xy_position.y - v.bounds.origin.y, + v.bounds.origin.y + v.bounds.size.height - v.xy_position.y + } + }; } -fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input - [[stage_in]]) { +fragment float4 path_rasterization_fragment( + PathRasterizationFragmentInput input [[stage_in]], + constant PathRasterizationVertex *vertices [[buffer(PathRasterizationInputIndex_Vertices)]] +) { 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.); + + PathRasterizationVertex v = vertices[input.vertex_id]; + Background background = v.color; + Bounds_ScaledPixels path_bounds = v.bounds; + float alpha; + if (length(float2(dx.x, dy.x)) < 0.001) { + alpha = 1.0; + } else { + 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); + alpha = saturate(0.5 - distance); + } + + GradientColor gradient_color = prepare_fill_color( + background.tag, + background.color_space, + background.solid, + background.colors[0].color, + background.colors[1].color + ); + + float4 color = fill_color( + background, + input.position.xy, + path_bounds, + gradient_color.solid, + gradient_color.color0, + gradient_color.color1 + ); + return float4(color.rgb * color.a * alpha, alpha * color.a); } struct PathSpriteVertexOutput { float4 position [[position]]; - float2 tile_position; - uint sprite_id [[flat]]; - float4 solid_color [[flat]]; - float4 color0 [[flat]]; - float4 color1 [[flat]]; + float2 texture_coords; }; 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)]]) { - + 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)]] +) { 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, viewport_size); - float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); - GradientColor gradient = prepare_fill_color( - sprite.color.tag, - sprite.color.color_space, - sprite.color.solid, - sprite.color.colors[0].color, - sprite.color.colors[1].color - ); + float2 screen_position = float2(sprite.bounds.origin.x, sprite.bounds.origin.y) + unit_vertex * float2(sprite.bounds.size.width, sprite.bounds.size.height); + float2 texture_coords = screen_position / float2(viewport_size->width, viewport_size->height); return PathSpriteVertexOutput{ device_position, - tile_position, - sprite_id, - gradient.solid, - gradient.color0, - gradient.color1 + texture_coords }; } fragment float4 path_sprite_fragment( - PathSpriteVertexOutput input [[stage_in]], - constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]], - texture2d atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) { - 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.)); - PathSprite sprite = sprites[input.sprite_id]; - Background background = sprite.color; - float4 color = fill_color(background, input.position.xy, sprite.bounds, - input.solid_color, input.color0, input.color1); - color.a *= mask; - return color; + PathSpriteVertexOutput input [[stage_in]], + texture2d intermediate_texture [[texture(SpriteInputIndex_AtlasTexture)]] +) { + constexpr sampler intermediate_texture_sampler(mag_filter::linear, min_filter::linear); + return intermediate_texture.sample(intermediate_texture_sampler, input.texture_coords); } struct SurfaceVertexOutput { diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index 1b88415d3b..e15bd7aeec 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -341,7 +341,7 @@ impl PlatformAtlas for TestAtlas { crate::AtlasTile { texture_id: AtlasTextureId { index: texture_id, - kind: crate::AtlasTextureKind::Path, + kind: crate::AtlasTextureKind::Monochrome, }, tile_id: TileId(tile_id), padding: 0, diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 4eaef64afa..ec8d720cdf 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -43,17 +43,6 @@ impl Scene { self.surfaces.clear(); } - #[cfg_attr( - all( - any(target_os = "linux", target_os = "freebsd"), - not(any(feature = "x11", feature = "wayland")) - ), - allow(dead_code) - )] - pub fn paths(&self) -> &[Path] { - &self.paths - } - pub fn len(&self) -> usize { self.paint_operations.len() } @@ -681,7 +670,7 @@ pub(crate) struct PathId(pub(crate) usize); #[derive(Clone, Debug)] pub struct Path { pub(crate) id: PathId, - order: DrawOrder, + pub(crate) order: DrawOrder, pub(crate) bounds: Bounds

, pub(crate) content_mask: ContentMask

, pub(crate) vertices: Vec>,