gpui: Improve path rendering & global multisample anti-aliasing (#29718)

Sunli and Jason Lee created

Currently, the rendering path required creating a texture for each path,
which wasted a large amount of video memory. In our application, simply
drawing some charts resulted in video memory usage as high as 5G.

I removed the step of creating path textures and directly drew the paths
on the rendering target, adding post-processing global multi-sampling
anti-aliasing. Drawing paths no longer requires allocating any
additional video memory and also improves the performance of path
rendering.

Release Notes:

- N/A

---------

Co-authored-by: Jason Lee <huacnlee@gmail.com>

Change summary

Cargo.lock                                       |   6 
Cargo.toml                                       |  12 
crates/gpui/build.rs                             |   2 
crates/gpui/examples/painting.rs                 |  23 
crates/gpui/src/path_builder.rs                  |   5 
crates/gpui/src/platform.rs                      |   1 
crates/gpui/src/platform/blade/blade_atlas.rs    |  80 ---
crates/gpui/src/platform/blade/blade_renderer.rs | 374 ++++++++------
crates/gpui/src/platform/blade/shaders.wgsl      |  57 -
crates/gpui/src/platform/mac/metal_atlas.rs      |  35 -
crates/gpui/src/platform/mac/metal_renderer.rs   | 433 +++++++----------
crates/gpui/src/platform/mac/shaders.metal       | 103 +---
crates/gpui/src/platform/test/window.rs          |   2 
crates/gpui/src/scene.rs                         |  61 -
crates/gpui/src/window.rs                        |   2 
docs/src/linux.md                                |   2 
16 files changed, 479 insertions(+), 719 deletions(-)

Detailed changes

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",

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"] }

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(),

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<Pixels>, Background)>,
     lines: Vec<Vec<Point<Pixels>>>,
@@ -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<Self>) -> impl IntoElement {
+    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> 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)),

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

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)]

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<BladeAtlasState>);
 
 struct PendingUpload {
@@ -27,7 +25,6 @@ struct BladeAtlasState {
     tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
     initializations: Vec<AtlasTextureId>,
     uploads: Vec<PendingUpload>,
-    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<gpu::TextureView>,
 }
 
 impl BladeAtlas {
-    pub(crate) fn new(gpu: &Arc<gpu::Context>, path_sample_count: u32) -> Self {
+    pub(crate) fn new(gpu: &Arc<gpu::Context>) -> 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<DevicePixels>,
-        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<BladeAtlasTexture>,
     polychrome_textures: AtlasTextureList<BladeAtlasTexture>,
-    path_textures: AtlasTextureList<BladeAtlasTexture>,
 }
 
 impl ops::Index<AtlasTextureKind> for BladeAtlasStorage {
@@ -349,7 +289,6 @@ impl ops::Index<AtlasTextureKind> 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<AtlasTextureKind> 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<AtlasTextureId> 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<gpu::Texture>,
-    msaa_view: Option<gpu::TextureView>,
     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 {

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<ScaledPixels>,
     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<gpu::SyncPoint>,
     pipelines: BladePipelines,
     instance_belt: BufferBelt,
-    path_tiles: HashMap<PathId, AtlasTile>,
     atlas: Arc<BladeAtlas>,
     atlas_sampler: gpu::Sampler,
     #[cfg(target_os = "macos")]
     core_video_texture_cache: CVMetalTextureCache,
-    path_sample_count: u32,
+    sample_count: u32,
+    texture_msaa: Option<gpu::Texture>,
+    texture_view_msaa: Option<gpu::TextureView>,
 }
 
 impl BladeRenderer {
@@ -331,6 +333,18 @@ impl BladeRenderer {
         window: &I,
         config: BladeSurfaceConfig,
     ) -> anyhow::Result<Self> {
+        // 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<ScaledPixels>]) {
-        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),
+
+                        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,
                                 },
+                            }));
+
+                            sprites.push(PathSprite {
+                                bounds: path.bounds,
                                 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);
+                            });
+                        }
+
+                        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::<DrawIndirectArgs>()) 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))
+}

crates/gpui/src/platform/blade/shaders.wgsl 🔗

@@ -922,59 +922,23 @@ fn fs_shadow(input: ShadowVarying) -> @location(0) vec4<f32> {
     return blend_color(input.color, alpha);
 }
 
-// --- path rasterization --- //
+// --- paths --- //
 
 struct PathVertex {
     xy_position: vec2<f32>,
-    st_position: vec2<f32>,
     content_mask: Bounds,
 }
-var<storage, read> b_path_vertices: array<PathVertex>;
-
-struct PathRasterizationVarying {
-    @builtin(position) position: vec4<f32>,
-    @location(0) st_position: vec2<f32>,
-    //TODO: use `clip_distance` once Naga supports it
-    @location(3) clip_distances: vec4<f32>,
-}
-
-@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<f32>(0.0))) {
-        return 0.0;
-    }
-
-    let gradient = 2.0 * input.st_position.xx * vec2<f32>(dx.x, dy.x) - vec2<f32>(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<storage, read> b_path_vertices: array<PathVertex>;
 var<storage, read> b_path_sprites: array<PathSprite>;
 
 struct PathVarying {
     @builtin(position) position: vec4<f32>,
-    @location(0) tile_position: vec2<f32>,
+    @location(0) clip_distances: vec4<f32>,
     @location(1) @interpolate(flat) instance_id: u32,
     @location(2) @interpolate(flat) color_solid: vec4<f32>,
     @location(3) @interpolate(flat) color0: vec4<f32>,
@@ -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>(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<f32> {
-    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<f32>(0.0)) {
+        return vec4<f32>(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 --- //

crates/gpui/src/platform/mac/metal_atlas.rs 🔗

@@ -13,14 +13,12 @@ use std::borrow::Cow;
 pub(crate) struct MetalAtlas(Mutex<MetalAtlasState>);
 
 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<metal::Texture> {
-        self.0.lock().texture(id).msaa_texture.clone()
-    }
-
+    #[allow(dead_code)]
     pub(crate) fn allocate(
         &self,
         size: Size<DevicePixels>,
@@ -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<Device>,
     monochrome_textures: AtlasTextureList<MetalAtlasTexture>,
     polychrome_textures: AtlasTextureList<MetalAtlasTexture>,
-    path_textures: AtlasTextureList<MetalAtlasTexture>,
     tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
-    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<metal::Texture>,
-    msaa_texture: AssertSend<Option<metal::Texture>>,
     live_atlas_keys: u32,
 }
 

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<f32>;
 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<Mutex<InstanceBufferPool>>;
 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<Mutex<InstanceBufferPool>>,
     sprite_atlas: Arc<MetalAtlas>,
     core_video_texture_cache: core_video::metal_texture_cache::CVMetalTextureCache,
+    sample_count: u64,
+    msaa_texture: Option<metal::Texture>,
 }
 
 impl MetalRenderer {
@@ -169,22 +168,19 @@ impl MetalRenderer {
             MTLResourceOptions::StorageModeManaged,
         );
 
-        let paths_rasterization_pipeline_state = build_path_rasterization_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(
+        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,
-            "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<ScaledPixels>],
-        instance_buffer: &mut InstanceBuffer,
-        instance_offset: &mut usize,
-        command_buffer: &metal::CommandBufferRef,
-    ) -> Option<HashMap<PathId, AtlasTile>> {
-        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<DevicePixels> 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<ScaledPixels>],
-        tiles_by_path_id: &HashMap<PathId, AtlasTile>,
         instance_buffer: &mut InstanceBuffer,
         instance_offset: &mut usize,
         viewport_size: Size<DevicePixels>,
@@ -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<DevicePixels> 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());
+
+            // 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::<PathVertex<ScaledPixels>>() * path.vertices.len())
+                    > instance_buffer.size
+                {
+                    return false;
+                }
 
-        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),
+                for v in &path.vertices {
+                    *(p as *mut PathVertex<ScaledPixels>) = PathVertex {
+                        xy_position: v.xy_position,
+                        content_mask: ContentMask {
+                            bounds: path.content_mask.bounds,
                         },
-                        color: path.color,
-                        tile: (*tile).clone(),
-                    });
-                    paths_and_tiles.next();
-                    continue;
+                    };
+                    p = p.add(mem::size_of::<PathVertex<ScaledPixels>>());
                 }
+
+                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;
             }
 
-            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),
-                );
+            // copy sprites
+            let sprites_offset = (p as u64) - (base_addr as u64);
+            if (p as usize) - (base_addr as usize) + (mem::size_of::<PathSprite>() * 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::<PathSprite>());
+            }
 
-                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<DevicePixels> 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));
+            // 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);
 
-                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;
-                }
+            // draw path
+            command_encoder.set_vertex_buffer(
+                PathInputIndex::Vertices as u64,
+                Some(&instance_buffer.metal_buffer),
+                vertices_offset as u64,
+            );
 
-                let buffer_contents = unsafe {
-                    (instance_buffer.metal_buffer.contents() as *mut u8).add(*instance_offset)
-                };
+            command_encoder.set_vertex_bytes(
+                PathInputIndex::ViewportSize as u64,
+                mem::size_of_val(&viewport_size) as u64,
+                &viewport_size as *const Size<DevicePixels> as *const _,
+            );
 
-                unsafe {
-                    ptr::copy_nonoverlapping(
-                        sprites.as_ptr() as *const u8,
-                        buffer_contents,
-                        sprite_bytes_len,
-                    );
-                }
+            command_encoder.set_vertex_buffer(
+                PathInputIndex::Sprites as u64,
+                Some(&instance_buffer.metal_buffer),
+                sprites_offset,
+            );
 
-                command_encoder.draw_primitives_instanced(
+            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,
-                    0,
-                    6,
-                    sprites.len() as u64,
+                    &instance_buffer.metal_buffer,
+                    icb_offset
+                        + (i * std::mem::size_of::<MTLDrawPrimitivesIndirectArguments>()) as u64,
                 );
-                *instance_offset = next_offset;
-                sprites.clear();
             }
+
+            *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,48 +1082,43 @@ 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");
+// Align to multiples of 256 make Metal happy.
+fn align_offset(offset: &mut usize) {
+    *offset = (*offset).div_ceil(256) * 256;
+}
 
-    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);
+fn create_msaa_texture(
+    device: &metal::Device,
+    layer: &metal::MetalLayer,
+    sample_count: u64,
+) -> Option<metal::Texture> {
+    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;
     }
-    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")
-}
+    if sample_count <= 1 {
+        return None;
+    }
 
-// Align to multiples of 256 make Metal happy.
-fn align_offset(offset: &mut usize) {
-    *offset = (*offset).div_ceil(256) * 256;
+    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)]
@@ -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<ScaledPixels>,
     pub color: Background,
-    pub tile: AtlasTile,
 }
 
 #[derive(Clone, Debug, Eq, PartialEq)]

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<float> 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;
 }
 

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,

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<ScaledPixels>] {
         &self.paths
     }
@@ -689,6 +683,7 @@ pub struct Path<P: Clone + Debug + Default + PartialEq> {
     start: Point<P>,
     current: Point<P>,
     contour_count: usize,
+    base_scale: f32,
 }
 
 impl Path<Pixels> {
@@ -707,25 +702,35 @@ impl Path<Pixels> {
             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<ScaledPixels> {
+    /// 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<ScaledPixels> {
         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<Pixels> {
     pub fn line_to(&mut self, to: Point<Pixels>) {
         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<Pixels> {
     pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
         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<Pixels>, Point<Pixels>, Point<Pixels>),
-        st: (Point<f32>, Point<f32>, Point<f32>),
-    ) {
+    pub fn push_triangle(&mut self, xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>)) {
         self.bounds = self
             .bounds
             .union(&Bounds {
@@ -788,17 +780,14 @@ impl Path<Pixels> {
 
         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<Path<ScaledPixels>> for Primitive {
 #[repr(C)]
 pub(crate) struct PathVertex<P: Clone + Debug + Default + PartialEq> {
     pub(crate) xy_position: Point<P>,
-    pub(crate) st_position: Point<f32>,
     pub(crate) content_mask: ContentMask<P>,
 }
 
@@ -822,7 +810,6 @@ impl PathVertex<Pixels> {
     pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
         PathVertex {
             xy_position: self.xy_position.scale(factor),
-            st_position: self.st_position,
             content_mask: self.content_mask.scale(factor),
         }
     }

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.

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))