diff --git a/Cargo.lock b/Cargo.lock index 27b6329a9f..39fd375d81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2076,7 +2076,7 @@ dependencies = [ [[package]] name = "blade-graphics" version = "0.6.0" -source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5" +source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad" dependencies = [ "ash", "ash-window", @@ -2109,7 +2109,7 @@ dependencies = [ [[package]] name = "blade-macros" version = "0.3.0" -source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5" +source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad" dependencies = [ "proc-macro2", "quote", @@ -2119,7 +2119,7 @@ dependencies = [ [[package]] name = "blade-util" version = "0.2.0" -source = "git+https://github.com/kvark/blade?rev=e0ec4e720957edd51b945b64dd85605ea54bcfe5#e0ec4e720957edd51b945b64dd85605ea54bcfe5" +source = "git+https://github.com/kvark/blade?rev=416375211bb0b5826b3584dccdb6a43369e499ad#416375211bb0b5826b3584dccdb6a43369e499ad" dependencies = [ "blade-graphics", "bytemuck", diff --git a/Cargo.toml b/Cargo.toml index 0155698bf9..1d9cf31c14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -426,9 +426,9 @@ aws-smithy-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] } aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] } base64 = "0.22" bitflags = "2.6.0" -blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" } -blade-macros = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" } -blade-util = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" } +blade-graphics = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" } +blade-macros = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" } +blade-util = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" } blake3 = "1.5.3" bytes = "1.0" cargo_metadata = "0.19" @@ -481,7 +481,7 @@ json_dotpath = "1.1" jsonschema = "0.30.0" jsonwebtoken = "9.3" jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" } -jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed" ,rev = "7130c804216b6914355d15d0b91ea91f6babd734" } +jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" } libc = "0.2" libsqlite3-sys = { version = "0.30.1", features = ["bundled"] } linkify = "0.10.0" @@ -492,7 +492,7 @@ metal = "0.29" moka = { version = "0.12.10", features = ["sync"] } naga = { version = "25.0", features = ["wgsl-in"] } nanoid = "0.4" -nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" } +nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" } nix = "0.29" num-format = "0.4.4" objc = "0.2" @@ -532,7 +532,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c77 "stream", ] } rsa = "0.9.6" -runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [ +runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [ "async-dispatcher-runtime", ] } rust-embed = { version = "8.4", features = ["include-exclude"] } diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index aed4397440..b9496cc014 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -126,7 +126,7 @@ mod macos { "ContentMask".into(), "Uniforms".into(), "AtlasTile".into(), - "PathRasterizationInputIndex".into(), + "PathInputIndex".into(), "PathVertex_ScaledPixels".into(), "ShadowInputIndex".into(), "Shadow".into(), diff --git a/crates/gpui/examples/painting.rs b/crates/gpui/examples/painting.rs index ff4b64cbda..9ab58cffc9 100644 --- a/crates/gpui/examples/painting.rs +++ b/crates/gpui/examples/painting.rs @@ -1,9 +1,13 @@ 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, + PathStyle, Pixels, Point, Render, SharedString, StrokeOptions, 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)>, lines: Vec>>, @@ -147,8 +151,6 @@ impl PaintingViewer { px(320.0 + (i as f32 * 10.0).sin() * 40.0), )); } - let path = builder.build().unwrap(); - lines.push((path, gpui::green().into())); Self { default_lines: lines.clone(), @@ -183,9 +185,13 @@ fn button( } impl Render for PaintingViewer { - fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + window.request_animation_frame(); + let default_lines = self.default_lines.clone(); let lines = self.lines.clone(); + let window_size = window.bounds().size; + let scale = window_size.width / DEFAULT_WINDOW_WIDTH; let dashed = self.dashed; div() @@ -222,7 +228,7 @@ impl Render for PaintingViewer { move |_, _, _| {}, move |_, _, window, _| { for (path, color) in default_lines { - window.paint_path(path, color); + window.paint_path(path.clone().scale(scale), color); } for points in lines { @@ -298,6 +304,11 @@ fn main() { cx.open_window( WindowOptions { 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)), diff --git a/crates/gpui/src/path_builder.rs b/crates/gpui/src/path_builder.rs index 6c8cfddd52..13c168b0bb 100644 --- a/crates/gpui/src/path_builder.rs +++ b/crates/gpui/src/path_builder.rs @@ -336,10 +336,7 @@ impl PathBuilder { let v1 = buf.vertices[i1]; let v2 = buf.vertices[i2]; - path.push_triangle( - (v0.into(), v1.into(), v2.into()), - (point(0., 1.), point(0., 1.), point(0., 1.)), - ); + path.push_triangle((v0.into(), v1.into(), v2.into())); } path diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 79ec5e5da6..1ad933dac1 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -789,7 +789,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..0b119c3910 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,13 @@ impl BladeAtlasState { } pub struct BladeTextureInfo { + #[allow(dead_code)] 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 +56,6 @@ impl BladeAtlas { tiles_by_key: Default::default(), initializations: Vec::new(), uploads: Vec::new(), - path_sample_count, })) } @@ -67,6 +63,7 @@ impl BladeAtlas { self.0.lock().destroy(); } + #[allow(dead_code)] pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) { let mut lock = self.0.lock(); let textures = &mut lock.storage[texture_kind]; @@ -75,19 +72,6 @@ impl BladeAtlas { } } - /// 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); @@ -109,7 +93,6 @@ impl BladeAtlas { depth: 1, }, raw_view: texture.raw_view, - msaa_view: texture.msaa_view, } } } @@ -200,48 +183,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 +222,6 @@ impl BladeAtlasState { format, raw, raw_view, - msaa, - msaa_view, live_atlas_keys: 0, }; @@ -340,7 +281,6 @@ impl BladeAtlasState { struct BladeAtlasStorage { monochrome_textures: AtlasTextureList, polychrome_textures: AtlasTextureList, - path_textures: AtlasTextureList, } impl ops::Index for BladeAtlasStorage { @@ -349,7 +289,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 +298,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 +308,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 +321,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,8 +329,6 @@ struct BladeAtlasTexture { allocator: BucketedAtlasAllocator, raw: gpu::Texture, raw_view: gpu::TextureView, - msaa: Option, - msaa_view: Option, format: gpu::TextureFormat, live_atlas_keys: u32, } @@ -424,12 +356,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..1b9f111b0d 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, ContentMask, DevicePixels, GpuSpecs, MonochromeSprite, PathVertex, + PolychromeSprite, PrimitiveBatch, Quad, ScaledPixels, Scene, Shadow, Size, Underline, }; -use blade_graphics as gpu; +use blade_graphics::{self 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}; 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)] @@ -65,17 +60,10 @@ struct ShaderShadowsData { b_shadows: gpu::BufferPiece, } -#[derive(blade_macros::ShaderData)] -struct ShaderPathRasterizationData { - globals: GlobalParams, - b_path_vertices: gpu::BufferPiece, -} - #[derive(blade_macros::ShaderData)] struct ShaderPathsData { globals: GlobalParams, - t_sprite: gpu::TextureView, - s_sprite: gpu::Sampler, + b_path_vertices: gpu::BufferPiece, b_path_sprites: gpu::BufferPiece, } @@ -115,13 +103,27 @@ struct ShaderSurfacesData { struct PathSprite { bounds: Bounds, color: Background, - tile: AtlasTile, +} + +/// Argument buffer layout for `draw_indirect` commands. +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)] +pub struct DrawIndirectArgs { + /// The number of vertices to draw. + pub vertex_count: u32, + /// The number of instances to draw. + pub instance_count: u32, + /// The Index of the first vertex to draw. + pub first_vertex: u32, + /// The instance ID of the first instance to draw. + /// + /// Has to be 0, unless [`Features::INDIRECT_FIRST_INSTANCE`](crate::Features::INDIRECT_FIRST_INSTANCE) is enabled. + pub first_instance: u32, } struct BladePipelines { quads: gpu::RenderPipeline, shadows: gpu::RenderPipeline, - path_rasterization: gpu::RenderPipeline, paths: gpu::RenderPipeline, underlines: gpu::RenderPipeline, mono_sprites: gpu::RenderPipeline, @@ -130,7 +132,7 @@ struct BladePipelines { } impl BladePipelines { - fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo, path_sample_count: u32) -> Self { + fn new(gpu: &gpu::Context, surface_info: gpu::SurfaceInfo, sample_count: u32) -> Self { use gpu::ShaderData as _; log::info!( @@ -178,7 +180,10 @@ impl BladePipelines { depth_stencil: None, fragment: Some(shader.at("fs_quad")), color_targets, - multisample_state: gpu::MultisampleState::default(), + multisample_state: gpu::MultisampleState { + sample_count, + ..Default::default() + }, }), shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc { name: "shadows", @@ -192,26 +197,8 @@ impl BladePipelines { depth_stencil: None, fragment: Some(shader.at("fs_shadow")), color_targets, - multisample_state: gpu::MultisampleState::default(), - }), - path_rasterization: gpu.create_render_pipeline(gpu::RenderPipelineDesc { - name: "path_rasterization", - data_layouts: &[&ShaderPathRasterizationData::layout()], - vertex: shader.at("vs_path_rasterization"), - vertex_fetches: &[], - primitive: gpu::PrimitiveState { - topology: gpu::PrimitiveTopology::TriangleList, - ..Default::default() - }, - depth_stencil: None, - fragment: Some(shader.at("fs_path_rasterization")), - color_targets: &[gpu::ColorTargetState { - format: PATH_TEXTURE_FORMAT, - blend: Some(gpu::BlendState::ADDITIVE), - write_mask: gpu::ColorWrites::default(), - }], multisample_state: gpu::MultisampleState { - sample_count: path_sample_count, + sample_count, ..Default::default() }, }), @@ -221,13 +208,16 @@ impl BladePipelines { vertex: shader.at("vs_path"), vertex_fetches: &[], primitive: gpu::PrimitiveState { - topology: gpu::PrimitiveTopology::TriangleStrip, + topology: gpu::PrimitiveTopology::TriangleList, ..Default::default() }, depth_stencil: None, fragment: Some(shader.at("fs_path")), color_targets, - multisample_state: gpu::MultisampleState::default(), + multisample_state: gpu::MultisampleState { + sample_count, + ..Default::default() + }, }), underlines: gpu.create_render_pipeline(gpu::RenderPipelineDesc { name: "underlines", @@ -241,7 +231,10 @@ impl BladePipelines { depth_stencil: None, fragment: Some(shader.at("fs_underline")), color_targets, - multisample_state: gpu::MultisampleState::default(), + multisample_state: gpu::MultisampleState { + sample_count, + ..Default::default() + }, }), mono_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc { name: "mono-sprites", @@ -255,7 +248,10 @@ impl BladePipelines { depth_stencil: None, fragment: Some(shader.at("fs_mono_sprite")), color_targets, - multisample_state: gpu::MultisampleState::default(), + multisample_state: gpu::MultisampleState { + sample_count, + ..Default::default() + }, }), poly_sprites: gpu.create_render_pipeline(gpu::RenderPipelineDesc { name: "poly-sprites", @@ -269,7 +265,10 @@ impl BladePipelines { depth_stencil: None, fragment: Some(shader.at("fs_poly_sprite")), color_targets, - multisample_state: gpu::MultisampleState::default(), + multisample_state: gpu::MultisampleState { + sample_count, + ..Default::default() + }, }), surfaces: gpu.create_render_pipeline(gpu::RenderPipelineDesc { name: "surfaces", @@ -283,7 +282,10 @@ impl BladePipelines { depth_stencil: None, fragment: Some(shader.at("fs_surface")), color_targets, - multisample_state: gpu::MultisampleState::default(), + multisample_state: gpu::MultisampleState { + sample_count, + ..Default::default() + }, }), } } @@ -291,7 +293,6 @@ impl BladePipelines { fn destroy(&mut self, gpu: &gpu::Context) { gpu.destroy_render_pipeline(&mut self.quads); gpu.destroy_render_pipeline(&mut self.shadows); - gpu.destroy_render_pipeline(&mut self.path_rasterization); gpu.destroy_render_pipeline(&mut self.paths); gpu.destroy_render_pipeline(&mut self.underlines); gpu.destroy_render_pipeline(&mut self.mono_sprites); @@ -317,12 +318,13 @@ 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, + sample_count: u32, + texture_msaa: Option, + texture_view_msaa: Option, } impl BladeRenderer { @@ -331,6 +333,18 @@ impl BladeRenderer { window: &I, config: BladeSurfaceConfig, ) -> anyhow::Result { + // workaround for https://github.com/zed-industries/zed/issues/26143 + let sample_count = std::env::var("ZED_SAMPLE_COUNT") + .ok() + .or_else(|| std::env::var("ZED_PATH_SAMPLE_COUNT").ok()) + .and_then(|v| v.parse().ok()) + .or_else(|| { + [4, 2, 1] + .into_iter() + .find(|count| context.gpu.supports_texture_sample_count(*count)) + }) + .unwrap_or(1); + let surface_config = gpu::SurfaceConfig { size: config.size, usage: gpu::TextureUsage::TARGET, @@ -344,22 +358,27 @@ impl BladeRenderer { .create_surface_configured(window, surface_config) .map_err(|err| anyhow::anyhow!("Failed to create surface: {err:?}"))?; + let (texture_msaa, texture_view_msaa) = create_msaa_texture_if_needed( + &context.gpu, + surface.info().format, + config.size.width, + config.size.height, + sample_count, + ) + .unzip(); + let command_encoder = context.gpu.create_command_encoder(gpu::CommandEncoderDesc { name: "main", buffer_count: 2, }); - // workaround for https://github.com/zed-industries/zed/issues/26143 - let path_sample_count = std::env::var("ZED_PATH_SAMPLE_COUNT") - .ok() - .and_then(|v| v.parse().ok()) - .unwrap_or(DEFAULT_PATH_SAMPLE_COUNT); - let pipelines = BladePipelines::new(&context.gpu, surface.info(), path_sample_count); + + let pipelines = BladePipelines::new(&context.gpu, surface.info(), 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", mag_filter: gpu::FilterMode::Linear, @@ -383,12 +402,13 @@ 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, + sample_count, + texture_msaa, + texture_view_msaa, }) } @@ -441,6 +461,24 @@ impl BladeRenderer { self.surface_config.size = gpu_size; self.gpu .reconfigure_surface(&mut self.surface, self.surface_config); + + if let Some(texture_msaa) = self.texture_msaa { + self.gpu.destroy_texture(texture_msaa); + } + if let Some(texture_view_msaa) = self.texture_view_msaa { + self.gpu.destroy_texture_view(texture_view_msaa); + } + + let (texture_msaa, texture_view_msaa) = create_msaa_texture_if_needed( + &self.gpu, + self.surface.info().format, + gpu_size.width, + gpu_size.height, + self.sample_count, + ) + .unzip(); + self.texture_msaa = texture_msaa; + self.texture_view_msaa = texture_view_msaa; } } @@ -451,8 +489,7 @@ impl BladeRenderer { self.gpu .reconfigure_surface(&mut self.surface, self.surface_config); self.pipelines.destroy(&self.gpu); - self.pipelines = - BladePipelines::new(&self.gpu, self.surface.info(), self.path_sample_count); + self.pipelines = BladePipelines::new(&self.gpu, self.surface.info(), self.sample_count); } } @@ -490,80 +527,6 @@ impl BladeRenderer { objc2::rc::Retained::as_ptr(&self.surface.metal_layer()) as *mut _ } - #[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); - } - - for (texture_id, vertices) in vertices_by_texture_id { - let tex_info = self.atlas.get_texture_info(texture_id); - let globals = GlobalParams { - viewport_size: [tex_info.size.width as f32, tex_info.size.height as f32], - premultiplied_alpha: 0, - pad: 0, - }; - - 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); - } - } - } - pub fn destroy(&mut self) { self.wait_for_gpu(); self.atlas.destroy(); @@ -572,17 +535,26 @@ impl BladeRenderer { self.gpu.destroy_command_encoder(&mut self.command_encoder); self.pipelines.destroy(&self.gpu); self.gpu.destroy_surface(&mut self.surface); + if let Some(texture_msaa) = self.texture_msaa { + self.gpu.destroy_texture(texture_msaa); + } + if let Some(texture_view_msaa) = self.texture_view_msaa { + self.gpu.destroy_texture_view(texture_view_msaa); + } } 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"); self.surface.acquire_frame() }; + let frame_view = frame.texture_view(); + if let Some(texture_msaa) = self.texture_msaa { + self.command_encoder.init_texture(texture_msaa); + } self.command_encoder.init_texture(frame.texture()); let globals = GlobalParams { @@ -597,14 +569,25 @@ impl BladeRenderer { pad: 0, }; + let target = if let Some(texture_view_msaa) = self.texture_view_msaa { + gpu::RenderTarget { + view: texture_view_msaa, + init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack), + finish_op: gpu::FinishOp::ResolveTo(frame_view), + } + } else { + gpu::RenderTarget { + view: frame_view, + init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack), + finish_op: gpu::FinishOp::Store, + } + }; + + // draw to the target texture if let mut pass = self.command_encoder.render( "main", gpu::RenderTargetSet { - colors: &[gpu::RenderTarget { - view: frame.texture_view(), - init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack), - finish_op: gpu::FinishOp::Store, - }], + colors: &[target], depth_stencil: None, }, ) { @@ -639,32 +622,55 @@ impl BladeRenderer { } 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, + let mut vertices = Vec::new(); + let mut sprites = Vec::with_capacity(paths.len()); + let mut draw_indirect_commands = Vec::with_capacity(paths.len()); + let mut first_vertex = 0; + + for (i, path) in paths.iter().enumerate() { + draw_indirect_commands.push(DrawIndirectArgs { + vertex_count: path.vertices.len() as u32, + instance_count: 1, + first_vertex, + first_instance: i as u32, + }); + first_vertex += path.vertices.len() as u32; + + vertices.extend(path.vertices.iter().map(|v| PathVertex { + xy_position: v.xy_position, + content_mask: ContentMask { + bounds: path.content_mask.bounds, }, - ); - encoder.draw(0, 4, 0, sprites.len() as u32); + })); + + sprites.push(PathSprite { + bounds: path.bounds, + color: path.color, + }); + } + + let b_path_vertices = + unsafe { self.instance_belt.alloc_typed(&vertices, &self.gpu) }; + let instance_buf = + unsafe { self.instance_belt.alloc_typed(&sprites, &self.gpu) }; + let indirect_buf = unsafe { + self.instance_belt + .alloc_typed(&draw_indirect_commands, &self.gpu) + }; + + encoder.bind( + 0, + &ShaderPathsData { + globals, + b_path_vertices, + b_path_sprites: instance_buf, + }, + ); + + for i in 0..paths.len() { + encoder.draw_indirect(indirect_buf.buffer.at(indirect_buf.offset + + (i * mem::size_of::()) as u64)); } } PrimitiveBatch::Underlines(underlines) => { @@ -817,9 +823,47 @@ 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_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: "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: "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..00c9d07af7 100644 --- a/crates/gpui/src/platform/blade/shaders.wgsl +++ b/crates/gpui/src/platform/blade/shaders.wgsl @@ -922,59 +922,23 @@ fn fs_shadow(input: ShadowVarying) -> @location(0) vec4 { return blend_color(input.color, alpha); } -// --- path rasterization --- // +// --- paths --- // struct PathVertex { xy_position: vec2, - st_position: vec2, content_mask: Bounds, } -var b_path_vertices: array; - -struct PathRasterizationVarying { - @builtin(position) position: vec4, - @location(0) st_position: vec2, - //TODO: use `clip_distance` once Naga supports it - @location(3) clip_distances: vec4, -} - -@vertex -fn vs_path_rasterization(@builtin(vertex_index) vertex_id: u32) -> PathRasterizationVarying { - let v = b_path_vertices[vertex_id]; - - 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); - return out; -} - -@fragment -fn fs_path_rasterization(input: PathRasterizationVarying) -> @location(0) f32 { - let dx = dpdx(input.st_position); - let dy = dpdy(input.st_position); - if (any(input.clip_distances < vec4(0.0))) { - return 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); -} - -// --- paths --- // struct PathSprite { bounds: Bounds, color: Background, - tile: AtlasTile, } +var b_path_vertices: array; var b_path_sprites: array; struct PathVarying { @builtin(position) position: vec4, - @location(0) tile_position: vec2, + @location(0) clip_distances: vec4, @location(1) @interpolate(flat) instance_id: u32, @location(2) @interpolate(flat) color_solid: vec4, @location(3) @interpolate(flat) color0: vec4, @@ -983,13 +947,12 @@ struct PathVarying { @vertex fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> PathVarying { - let unit_vertex = vec2(f32(vertex_id & 1u), 0.5 * f32(vertex_id & 2u)); + let v = b_path_vertices[vertex_id]; let sprite = b_path_sprites[instance_id]; - // Don't apply content mask because it was already accounted for when rasterizing the path. var out = PathVarying(); - out.position = to_device_position(unit_vertex, sprite.bounds); - out.tile_position = to_tile_position(unit_vertex, sprite.tile); + out.position = to_device_position_impl(v.xy_position); + out.clip_distances = distance_from_clip_rect_impl(v.xy_position, v.content_mask); out.instance_id = instance_id; let gradient = prepare_gradient_color( @@ -1006,13 +969,15 @@ fn vs_path(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) insta @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); + if any(input.clip_distances < vec4(0.0)) { + return vec4(0.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); + return blend_color(color, 1.0); } // --- underlines --- // diff --git a/crates/gpui/src/platform/mac/metal_atlas.rs b/crates/gpui/src/platform/mac/metal_atlas.rs index 366f2dcc3c..0c8e1d3703 100644 --- a/crates/gpui/src/platform/mac/metal_atlas.rs +++ b/crates/gpui/src/platform/mac/metal_atlas.rs @@ -13,14 +13,12 @@ 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, })) } @@ -28,10 +26,7 @@ impl MetalAtlas { 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() - } - + #[allow(dead_code)] pub(crate) fn allocate( &self, size: Size, @@ -40,12 +35,12 @@ impl MetalAtlas { self.0.lock().allocate(size, texture_kind) } + #[allow(dead_code)] 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(); @@ -57,9 +52,7 @@ 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 +87,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 +120,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 +164,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 +183,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 +199,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,7 +208,6 @@ struct MetalAtlasTexture { id: AtlasTextureId, allocator: BucketedAtlasAllocator, metal_texture: AssertSend, - msaa_texture: AssertSend>, live_atlas_keys: u32, } diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index 3cdc2dd2cf..8936cf242c 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -1,27 +1,28 @@ 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, PathVertex, 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, MTLDrawPrimitivesIndirectArguments, MTLPixelFormat, + MTLResourceOptions, NSRange, +}; 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 @@ -31,9 +32,6 @@ pub(crate) type PointF = crate::Point; const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib")); #[cfg(feature = "runtime_shaders")] const SHADERS_SOURCE_FILE: &str = include_str!(concat!(env!("OUT_DIR"), "/stitched_shaders.metal")); -// Use 4x MSAA, all devices support it. -// https://developer.apple.com/documentation/metal/mtldevice/1433355-supportstexturesamplecount -const PATH_SAMPLE_COUNT: u32 = 4; pub type Context = Arc>; pub type Renderer = MetalRenderer; @@ -98,8 +96,7 @@ pub(crate) struct MetalRenderer { layer: metal::MetalLayer, presents_with_transaction: bool, command_queue: CommandQueue, - paths_rasterization_pipeline_state: metal::RenderPipelineState, - path_sprites_pipeline_state: metal::RenderPipelineState, + path_pipeline_state: metal::RenderPipelineState, shadows_pipeline_state: metal::RenderPipelineState, quads_pipeline_state: metal::RenderPipelineState, underlines_pipeline_state: metal::RenderPipelineState, @@ -111,6 +108,8 @@ pub(crate) struct MetalRenderer { instance_buffer_pool: Arc>, sprite_atlas: Arc, core_video_texture_cache: core_video::metal_texture_cache::CVMetalTextureCache, + sample_count: u64, + msaa_texture: Option, } impl MetalRenderer { @@ -169,22 +168,19 @@ impl MetalRenderer { MTLResourceOptions::StorageModeManaged, ); - let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state( + let sample_count = [4, 2, 1] + .into_iter() + .find(|count| device.supports_texture_sample_count(*count)) + .unwrap_or(1); + + let path_pipeline_state = build_pipeline_state( &device, &library, - "paths_rasterization", - "path_rasterization_vertex", - "path_rasterization_fragment", - MTLPixelFormat::R16Float, - PATH_SAMPLE_COUNT, - ); - let path_sprites_pipeline_state = build_pipeline_state( - &device, - &library, - "path_sprites", - "path_sprite_vertex", - "path_sprite_fragment", + "paths", + "path_vertex", + "path_fragment", MTLPixelFormat::BGRA8Unorm, + sample_count, ); let shadows_pipeline_state = build_pipeline_state( &device, @@ -193,6 +189,7 @@ impl MetalRenderer { "shadow_vertex", "shadow_fragment", MTLPixelFormat::BGRA8Unorm, + sample_count, ); let quads_pipeline_state = build_pipeline_state( &device, @@ -201,6 +198,7 @@ impl MetalRenderer { "quad_vertex", "quad_fragment", MTLPixelFormat::BGRA8Unorm, + sample_count, ); let underlines_pipeline_state = build_pipeline_state( &device, @@ -209,6 +207,7 @@ impl MetalRenderer { "underline_vertex", "underline_fragment", MTLPixelFormat::BGRA8Unorm, + sample_count, ); let monochrome_sprites_pipeline_state = build_pipeline_state( &device, @@ -217,6 +216,7 @@ impl MetalRenderer { "monochrome_sprite_vertex", "monochrome_sprite_fragment", MTLPixelFormat::BGRA8Unorm, + sample_count, ); let polychrome_sprites_pipeline_state = build_pipeline_state( &device, @@ -225,6 +225,7 @@ impl MetalRenderer { "polychrome_sprite_vertex", "polychrome_sprite_fragment", MTLPixelFormat::BGRA8Unorm, + sample_count, ); let surfaces_pipeline_state = build_pipeline_state( &device, @@ -233,20 +234,21 @@ impl MetalRenderer { "surface_vertex", "surface_fragment", MTLPixelFormat::BGRA8Unorm, + sample_count, ); 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(); + let msaa_texture = create_msaa_texture(&device, &layer, sample_count); Self { device, layer, presents_with_transaction: false, command_queue, - paths_rasterization_pipeline_state, - path_sprites_pipeline_state, + path_pipeline_state, shadows_pipeline_state, quads_pipeline_state, underlines_pipeline_state, @@ -257,6 +259,8 @@ impl MetalRenderer { instance_buffer_pool, sprite_atlas, core_video_texture_cache, + sample_count, + msaa_texture, } } @@ -289,6 +293,8 @@ impl MetalRenderer { setDrawableSize: size ]; } + + self.msaa_texture = create_msaa_texture(&self.device, &self.layer, self.sample_count); } pub fn update_transparency(&self, _transparent: bool) { @@ -375,25 +381,23 @@ impl MetalRenderer { let command_queue = self.command_queue.clone(); let command_buffer = command_queue.new_command_buffer(); 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); + if let Some(msaa_texture_ref) = self.msaa_texture.as_deref() { + color_attachment.set_texture(Some(msaa_texture_ref)); + color_attachment.set_load_action(metal::MTLLoadAction::Clear); + color_attachment.set_store_action(metal::MTLStoreAction::MultisampleResolve); + color_attachment.set_resolve_texture(Some(drawable.texture())); + } else { + color_attachment.set_load_action(metal::MTLLoadAction::Clear); + color_attachment.set_texture(Some(drawable.texture())); + 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); @@ -425,7 +429,6 @@ impl MetalRenderer { ), PrimitiveBatch::Paths(paths) => self.draw_paths( paths, - &path_tiles, instance_buffer, &mut instance_offset, viewport_size, @@ -493,106 +496,6 @@ impl MetalRenderer { Ok(command_buffer.to_owned()) } - fn rasterize_paths( - &self, - paths: &[Path], - instance_buffer: &mut InstanceBuffer, - instance_offset: &mut usize, - command_buffer: &metal::CommandBufferRef, - ) -> Option> { - self.sprite_atlas.clear_textures(AtlasTextureKind::Path); - - 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(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); - } - - 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, - ); - command_encoder.end_encoding(); - *instance_offset = next_offset; - } - - Some(tiles) - } - fn draw_shadows( &self, shadows: &[Shadow], @@ -718,7 +621,6 @@ impl MetalRenderer { fn draw_paths( &self, paths: &[Path], - tiles_by_path_id: &HashMap, instance_buffer: &mut InstanceBuffer, instance_offset: &mut usize, viewport_size: Size, @@ -728,100 +630,108 @@ impl MetalRenderer { return true; } - 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 _, - ); + command_encoder.set_render_pipeline_state(&self.path_pipeline_state); - 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(); + unsafe { + let base_addr = instance_buffer.metal_buffer.contents(); + let mut p = (base_addr as *mut u8).add(*instance_offset); + let mut draw_indirect_commands = Vec::with_capacity(paths.len()); - 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 { + // copy vertices + let vertices_offset = (p as usize) - (base_addr as usize); + let mut first_vertex = 0; + for (i, path) in paths.iter().enumerate() { + if (p as usize) - (base_addr as usize) + + (mem::size_of::>() * path.vertices.len()) + > 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, - ); + for v in &path.vertices { + *(p as *mut PathVertex) = PathVertex { + xy_position: v.xy_position, + content_mask: ContentMask { + bounds: path.content_mask.bounds, + }, + }; + p = p.add(mem::size_of::>()); } - command_encoder.draw_primitives_instanced( - metal::MTLPrimitiveType::Triangle, - 0, - 6, - sprites.len() as u64, - ); - *instance_offset = next_offset; - sprites.clear(); + draw_indirect_commands.push(MTLDrawPrimitivesIndirectArguments { + vertexCount: path.vertices.len() as u32, + instanceCount: 1, + vertexStart: first_vertex, + baseInstance: i as u32, + }); + first_vertex += path.vertices.len() as u32; } + + // copy sprites + let sprites_offset = (p as u64) - (base_addr as u64); + if (p as usize) - (base_addr as usize) + (mem::size_of::() * paths.len()) + > instance_buffer.size + { + return false; + } + for path in paths { + *(p as *mut PathSprite) = PathSprite { + bounds: path.bounds, + color: path.color, + }; + p = p.add(mem::size_of::()); + } + + // copy indirect commands + let icb_bytes_len = mem::size_of_val(draw_indirect_commands.as_slice()); + let icb_offset = (p as u64) - (base_addr as u64); + if (p as usize) - (base_addr as usize) + icb_bytes_len > instance_buffer.size { + return false; + } + ptr::copy_nonoverlapping( + draw_indirect_commands.as_ptr() as *const u8, + p, + icb_bytes_len, + ); + p = p.add(icb_bytes_len); + + // draw path + command_encoder.set_vertex_buffer( + PathInputIndex::Vertices as u64, + Some(&instance_buffer.metal_buffer), + vertices_offset as u64, + ); + + command_encoder.set_vertex_bytes( + PathInputIndex::ViewportSize as u64, + mem::size_of_val(&viewport_size) as u64, + &viewport_size as *const Size as *const _, + ); + + command_encoder.set_vertex_buffer( + PathInputIndex::Sprites as u64, + Some(&instance_buffer.metal_buffer), + sprites_offset, + ); + + command_encoder.set_fragment_buffer( + PathInputIndex::Sprites as u64, + Some(&instance_buffer.metal_buffer), + sprites_offset, + ); + + for i in 0..paths.len() { + command_encoder.draw_primitives_indirect( + metal::MTLPrimitiveType::Triangle, + &instance_buffer.metal_buffer, + icb_offset + + (i * std::mem::size_of::()) as u64, + ); + } + + *instance_offset = (p as usize) - (base_addr as usize); } + true } @@ -1143,6 +1053,7 @@ fn build_pipeline_state( vertex_fn_name: &str, fragment_fn_name: &str, pixel_format: metal::MTLPixelFormat, + sample_count: u64, ) -> metal::RenderPipelineState { let vertex_fn = library .get_function(vertex_fn_name, None) @@ -1155,6 +1066,7 @@ fn build_pipeline_state( descriptor.set_label(label); descriptor.set_vertex_function(Some(vertex_fn.as_ref())); descriptor.set_fragment_function(Some(fragment_fn.as_ref())); + descriptor.set_sample_count(sample_count); let color_attachment = descriptor.color_attachments().object_at(0).unwrap(); color_attachment.set_pixel_format(pixel_format); color_attachment.set_blending_enabled(true); @@ -1170,50 +1082,45 @@ fn build_pipeline_state( .expect("could not create render pipeline state") } -fn build_path_rasterization_pipeline_state( - device: &metal::DeviceRef, - library: &metal::LibraryRef, - label: &str, - vertex_fn_name: &str, - fragment_fn_name: &str, - pixel_format: metal::MTLPixelFormat, - path_sample_count: u32, -) -> 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())); - if path_sample_count > 1 { - descriptor.set_raster_sample_count(path_sample_count as _); - descriptor.set_alpha_to_coverage_enabled(true); - } - let color_attachment = descriptor.color_attachments().object_at(0).unwrap(); - color_attachment.set_pixel_format(pixel_format); - color_attachment.set_blending_enabled(true); - color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add); - color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add); - color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One); - color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One); - color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::One); - color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One); - - device - .new_render_pipeline_state(&descriptor) - .expect("could not create render pipeline state") -} - // Align to multiples of 256 make Metal happy. fn align_offset(offset: &mut usize) { *offset = (*offset).div_ceil(256) * 256; } +fn create_msaa_texture( + device: &metal::Device, + layer: &metal::MetalLayer, + sample_count: u64, +) -> Option { + let viewport_size = layer.drawable_size(); + let width = viewport_size.width.ceil() as u64; + let height = viewport_size.height.ceil() as u64; + + if width == 0 || height == 0 { + return None; + } + + if sample_count <= 1 { + return None; + } + + let texture_descriptor = metal::TextureDescriptor::new(); + texture_descriptor.set_texture_type(metal::MTLTextureType::D2Multisample); + + // MTLStorageMode default is `shared` only for Apple silicon GPUs. Use `private` for Apple and Intel GPUs both. + // Reference: https://developer.apple.com/documentation/metal/choosing-a-resource-storage-mode-for-apple-gpus + texture_descriptor.set_storage_mode(metal::MTLStorageMode::Private); + + texture_descriptor.set_width(width); + texture_descriptor.set_height(height); + texture_descriptor.set_pixel_format(layer.pixel_format()); + texture_descriptor.set_usage(metal::MTLTextureUsage::RenderTarget); + texture_descriptor.set_sample_count(sample_count); + + let metal_texture = device.new_texture(&texture_descriptor); + Some(metal_texture) +} + #[repr(C)] enum ShadowInputIndex { Vertices = 0, @@ -1255,9 +1162,10 @@ enum SurfaceInputIndex { } #[repr(C)] -enum PathRasterizationInputIndex { +enum PathInputIndex { Vertices = 0, - AtlasTextureSize = 1, + ViewportSize = 1, + Sprites = 2, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -1265,7 +1173,6 @@ enum PathRasterizationInputIndex { 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..5f0dc3323d 100644 --- a/crates/gpui/src/platform/mac/shaders.metal +++ b/crates/gpui/src/platform/mac/shaders.metal @@ -698,76 +698,27 @@ fragment float4 polychrome_sprite_fragment( return color; } -struct PathRasterizationVertexOutput { +struct PathVertexOutput { 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; uint sprite_id [[flat]]; float4 solid_color [[flat]]; float4 color0 [[flat]]; float4 color1 [[flat]]; + float4 clip_distance; }; -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]; +vertex PathVertexOutput path_vertex( + uint vertex_id [[vertex_id]], + constant PathVertex_ScaledPixels *vertices [[buffer(PathInputIndex_Vertices)]], + uint sprite_id [[instance_id]], + constant PathSprite *sprites [[buffer(PathInputIndex_Sprites)]], + constant Size_DevicePixels *input_viewport_size [[buffer(PathInputIndex_ViewportSize)]]) { + PathVertex_ScaledPixels v = vertices[vertex_id]; + float2 vertex_position = float2(v.xy_position.x, v.xy_position.y); + float2 viewport_size = float2((float)input_viewport_size->width, + (float)input_viewport_size->height); 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); + float4 device_position = float4(vertex_position / viewport_size * float2(2., -2.) + float2(-1., 1.), 0., 1.); GradientColor gradient = prepare_fill_color( sprite.color.tag, @@ -777,30 +728,32 @@ vertex PathSpriteVertexOutput path_sprite_vertex( sprite.color.colors[1].color ); - return PathSpriteVertexOutput{ + return PathVertexOutput{ device_position, - tile_position, sprite_id, gradient.solid, gradient.color0, - gradient.color1 + gradient.color1, + {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_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.)); +fragment float4 path_fragment( + PathVertexOutput input [[stage_in]], + constant PathSprite *sprites [[buffer(PathInputIndex_Sprites)]]) { + if (any(input.clip_distance < float4(0.0))) { + return float4(0.0); + } + 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; } diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index 1b88415d3b..65ee10a13f 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::Polychrome, }, tile_id: TileId(tile_id), padding: 0, diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 4eaef64afa..681444a473 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::{ AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Hsla, Pixels, - Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree, point, + Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree, }; use std::{fmt::Debug, iter::Peekable, ops::Range, slice}; @@ -43,13 +43,7 @@ impl Scene { self.surfaces.clear(); } - #[cfg_attr( - all( - any(target_os = "linux", target_os = "freebsd"), - not(any(feature = "x11", feature = "wayland")) - ), - allow(dead_code) - )] + #[allow(dead_code)] pub fn paths(&self) -> &[Path] { &self.paths } @@ -689,6 +683,7 @@ pub struct Path { start: Point

, current: Point

, contour_count: usize, + base_scale: f32, } impl Path { @@ -707,25 +702,35 @@ impl Path { content_mask: Default::default(), color: Default::default(), contour_count: 0, + base_scale: 1.0, } } - /// Scale this path by the given factor. - pub fn scale(&self, factor: f32) -> Path { + /// Set the base scale of the path. + pub fn scale(mut self, factor: f32) -> Self { + self.base_scale = factor; + self + } + + /// Apply a scale to the path. + pub(crate) fn apply_scale(&self, factor: f32) -> Path { Path { id: self.id, order: self.order, - bounds: self.bounds.scale(factor), - content_mask: self.content_mask.scale(factor), + bounds: self.bounds.scale(self.base_scale * factor), + content_mask: self.content_mask.scale(self.base_scale * factor), vertices: self .vertices .iter() - .map(|vertex| vertex.scale(factor)) + .map(|vertex| vertex.scale(self.base_scale * factor)) .collect(), - start: self.start.map(|start| start.scale(factor)), - current: self.current.scale(factor), + start: self + .start + .map(|start| start.scale(self.base_scale * factor)), + current: self.current.scale(self.base_scale * factor), contour_count: self.contour_count, color: self.color, + base_scale: 1.0, } } @@ -740,10 +745,7 @@ impl Path { pub fn line_to(&mut self, to: Point) { 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.start, self.current, to)); } self.current = to; } @@ -752,25 +754,15 @@ impl Path { pub fn curve_to(&mut self, to: Point, ctrl: Point) { 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.start, self.current, to)); } - self.push_triangle( - (self.current, ctrl, to), - (point(0., 0.), point(0.5, 0.), point(1., 1.)), - ); + self.push_triangle((self.current, ctrl, to)); self.current = to; } /// Push a triangle to the Path. - pub fn push_triangle( - &mut self, - xy: (Point, Point, Point), - st: (Point, Point, Point), - ) { + pub fn push_triangle(&mut self, xy: (Point, Point, Point)) { self.bounds = self .bounds .union(&Bounds { @@ -788,17 +780,14 @@ impl Path { self.vertices.push(PathVertex { xy_position: xy.0, - st_position: st.0, content_mask: Default::default(), }); self.vertices.push(PathVertex { xy_position: xy.1, - st_position: st.1, content_mask: Default::default(), }); self.vertices.push(PathVertex { xy_position: xy.2, - st_position: st.2, content_mask: Default::default(), }); } @@ -814,7 +803,6 @@ impl From> for Primitive { #[repr(C)] pub(crate) struct PathVertex { pub(crate) xy_position: Point

, - pub(crate) st_position: Point, pub(crate) content_mask: ContentMask

, } @@ -822,7 +810,6 @@ impl PathVertex { pub fn scale(&self, factor: f32) -> PathVertex { PathVertex { xy_position: self.xy_position.scale(factor), - st_position: self.st_position, content_mask: self.content_mask.scale(factor), } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index be3b753d6a..8c01b8afcf 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2633,7 +2633,7 @@ impl Window { path.color = color.opacity(opacity); self.next_frame .scene - .insert_primitive(path.scale(scale_factor)); + .insert_primitive(path.apply_scale(scale_factor)); } /// Paint an underline into the scene for the next frame at the current z-index. diff --git a/docs/src/linux.md b/docs/src/linux.md index ca65da2969..896bfdaf3f 100644 --- a/docs/src/linux.md +++ b/docs/src/linux.md @@ -148,7 +148,7 @@ On some systems the file `/etc/prime-discrete` can be used to enforce the use of On others, you may be able to the environment variable `DRI_PRIME=1` when running Zed to force the use of the discrete GPU. -If you're using an AMD GPU and Zed crashes when selecting long lines, try setting the `ZED_PATH_SAMPLE_COUNT=0` environment variable. (See [#26143](https://github.com/zed-industries/zed/issues/26143)) +If you're using an AMD GPU and Zed crashes when selecting long lines, try setting the `ZED_SAMPLE_COUNT=0` environment variable. (See [#26143](https://github.com/zed-industries/zed/issues/26143)) If you're using an AMD GPU, you might get a 'Broken Pipe' error. Try using the RADV or Mesa drivers. (See [#13880](https://github.com/zed-industries/zed/issues/13880))